From fa917897bb81a87682abdc3c15e961b4bc78889c Mon Sep 17 00:00:00 2001 From: Joni Freeman Date: Thu, 24 Nov 2011 09:09:41 +0200 Subject: [PATCH 0001/1949] JField is no longer a JValue This means more type safety since it is no longer possible to create invalid JSON where JFields are added directly into JArrays for instance. Most noticeable consequence of this change is that map, transform, find and filter come in two versions: def map(f: JValue => JValue): JValue def mapField(f: JField => JField): JValue def transform(f: PartialFunction[JValue, JValue]): JValue def transformField(f: PartialFunction[JField, JField]): JValue def find(p: JValue => Boolean): Option[JValue] def findField(p: JField => Boolean): Option[JField] ... Use *Field functions to traverse fields in the JSON, and use the functions without 'Field' in the name to traverse values in the JSON. --- core/json/README.md | 64 +++--- .../main/scala/net/liftweb/json/Diff.scala | 25 +-- .../scala/net/liftweb/json/Extraction.scala | 29 ++- .../main/scala/net/liftweb/json/JsonAST.scala | 184 +++++++++++++----- .../scala/net/liftweb/json/JsonParser.scala | 50 ++--- .../main/scala/net/liftweb/json/Merge.scala | 10 +- .../src/main/scala/net/liftweb/json/Xml.scala | 12 +- .../scala/net/liftweb/json/DiffExamples.scala | 4 - .../scala/net/liftweb/json/Examples.scala | 17 +- .../liftweb/json/ExtractionExamplesSpec.scala | 1 - .../scala/net/liftweb/json/JValueGen.scala | 2 +- .../scala/net/liftweb/json/JsonAstSpec.scala | 10 +- .../scala/net/liftweb/json/LottoExample.scala | 4 - .../scala/net/liftweb/json/XmlExamples.scala | 20 +- 14 files changed, 257 insertions(+), 175 deletions(-) diff --git a/core/json/README.md b/core/json/README.md index 327f33f0db..725f298fee 100644 --- a/core/json/README.md +++ b/core/json/README.md @@ -10,10 +10,11 @@ a JSON document as a syntax tree. case class JDouble(num: Double) extends JValue case class JInt(num: BigInt) extends JValue case class JBool(value: Boolean) extends JValue - case class JField(name: String, value: JValue) extends JValue case class JObject(obj: List[JField]) extends JValue case class JArray(arr: List[JValue]) extends JValue + type JField = (String, JValue) + All features are implemented in terms of above AST. Functions are used to transform the AST itself, or to transform the AST between different formats. Common transformations are summarized in a following picture. @@ -77,6 +78,25 @@ Applicative style parsing with Scalaz Migration from older versions ============================= +2.x (FIXME: add correct version once released) -> +------------------------------------------------- + +JField is no longer a JValue. This means more type safety since it is no longer possible +to create invalid JSON where JFields are added directly into JArrays for instance. Most +noticeable consequence of this change is that map, transform, find and filter come in +two versions: + + def map(f: JValue => JValue): JValue + def mapField(f: JField => JField): JValue + def transform(f: PartialFunction[JValue, JValue]): JValue + def transformField(f: PartialFunction[JField, JField]): JValue + def find(p: JValue => Boolean): Option[JValue] + def findField(p: JField => Boolean): Option[JField] + ... + +Use *Field functions to traverse fields in the JSON, and use the functions without 'Field' +in the name to traverse values in the JSON. + 2.2 -> ------ @@ -98,7 +118,7 @@ Any valid json can be parsed into internal AST format. scala> import net.liftweb.json._ scala> parse(""" { "numbers" : [1, 2, 3, 4] } """) res0: net.liftweb.json.JsonAST.JValue = - JObject(List(JField(numbers,JArray(List(JInt(1), JInt(2), JInt(3), JInt(4)))))) + JObject(List((numbers,JArray(List(JInt(1), JInt(2), JInt(3), JInt(4)))))) Producing JSON ============== @@ -241,8 +261,8 @@ Please see more examples in src/test/scala/net/liftweb/json/MergeExamples.scala scala> val Diff(changed, added, deleted) = mergedLotto diff lotto1 changed: net.liftweb.json.JsonAST.JValue = JNothing added: net.liftweb.json.JsonAST.JValue = JNothing - deleted: net.liftweb.json.JsonAST.JValue = JObject(List(JField(lotto,JObject(List(JField(winners, - JArray(List(JObject(List(JField(winner-id,JInt(54)), JField(numbers,JArray( + deleted: net.liftweb.json.JsonAST.JValue = JObject(List((lotto,JObject(List((winners, + JArray(List(JObject(List((winner-id,JInt(54)), (numbers,JArray( List(JInt(52), JInt(3), JInt(12), JInt(11), JInt(18), JInt(22)))))))))))))) @@ -271,7 +291,7 @@ Please see more examples in src/test/scala/net/liftweb/json/JsonQueryExamples.sc } """) - scala> for { JField("age", JInt(age)) <- json } yield age + scala> for { JObject(o) <- json; JField("age", JInt(age)) <- o } yield age res0: List[BigInt] = List(5, 3) scala> for { @@ -322,7 +342,7 @@ Json AST can be queried using XPath like functions. Following REPL session shows scala> json \\ "spouse" res0: net.liftweb.json.JsonAST.JValue = JObject(List( - JField(person,JObject(List(JField(name,JString(Marilyn)), JField(age,JInt(33))))))) + (person,JObject(List((name,JString(Marilyn)), (age,JInt(33))))))) scala> compact(render(res0)) res1: String = {"person":{"name":"Marilyn","age":33}} @@ -330,7 +350,7 @@ Json AST can be queried using XPath like functions. Following REPL session shows scala> compact(render(json \\ "name")) res2: String = {"name":"Joe","name":"Marilyn"} - scala> compact(render((json remove { _ == JField("name", JString("Marilyn")) }) \\ "name")) + scala> compact(render((json removeField { _ == JField("name", JString("Marilyn")) }) \\ "name")) res3: String = {"name":"Joe"} scala> compact(render(json \ "person" \ "name")) @@ -339,24 +359,24 @@ Json AST can be queried using XPath like functions. Following REPL session shows scala> compact(render(json \ "person" \ "spouse" \ "person" \ "name")) res5: String = "Marilyn" - scala> json find { + scala> json findField { case JField("name", _) => true case _ => false } - res6: Option[net.liftweb.json.JsonAST.JValue] = Some(JField(name,JString(Joe))) + res6: Option[net.liftweb.json.JsonAST.JValue] = Some((name,JString(Joe))) - scala> json filter { + scala> json filterField { case JField("name", _) => true case _ => false } res7: List[net.liftweb.json.JsonAST.JValue] = List(JField(name,JString(Joe)), JField(name,JString(Marilyn))) - scala> json transform { - case JField("name", JString(s)) => JField("NAME", JString(s.toUpperCase)) + scala> json transformField { + case ("name", JString(s)) => ("NAME", JString(s.toUpperCase)) } - res8: net.liftweb.json.JsonAST.JValue = JObject(List(JField(person,JObject(List( - JField(NAME,JString(JOE)), JField(age,JInt(35)), JField(spouse,JObject(List( - JField(person,JObject(List(JField(NAME,JString(MARILYN)), JField(age,JInt(33))))))))))))) + res8: net.liftweb.json.JsonAST.JValue = JObject(List((person,JObject(List( + (NAME,JString(JOE)), (age,JInt(35)), (spouse,JObject(List( + (person,JObject(List((NAME,JString(MARILYN)), (age,JInt(33))))))))))))) scala> json.values res8: scala.collection.immutable.Map[String,Any] = Map(person -> Map(name -> Joe, age -> 35, spouse -> Map(person -> Map(name -> Marilyn, age -> 33)))) @@ -379,7 +399,7 @@ Indexed path expressions work too and values can be unboxed using type expressio """) scala> (json \ "children")(0) - res0: net.liftweb.json.JsonAST.JValue = JObject(List(JField(name,JString(Mary)), JField(age,JInt(5)))) + res0: net.liftweb.json.JsonAST.JValue = JObject(List((name,JString(Mary)), (age,JInt(5)))) scala> (json \ "children")(1) \ "name" res1: net.liftweb.json.JsonAST.JValue = JString(Mazy) @@ -390,8 +410,6 @@ Indexed path expressions work too and values can be unboxed using type expressio scala> json \ "children" \\ classOf[JString] res3: List[net.liftweb.json.JsonAST.JString#Values] = List(Mary, Mazy) - scala> json \ "children" \ classOf[JField] - res4: List[net.liftweb.json.JsonAST.JField#Values] = List((name,Mary), (age,5), (name,Mazy), (age,3)) Extracting values ================= @@ -440,8 +458,8 @@ Use back ticks. Use transform function to postprocess AST. scala> case class Person(firstname: String) - scala> json transform { - case JField("first-name", x) => JField("firstname", x) + scala> json transformField { + case ("first-name", x) => ("firstname", x) } Extraction function tries to find the best matching constructor when case class has auxiliary @@ -645,9 +663,9 @@ decides to use JSON array because there's more than one user-element in XML. The XML document which happens to have just one user-element will generate a JSON document without JSON array. This is rarely a desired outcome. These both problems can be fixed by following transformation function. - scala> json transform { - case JField("id", JString(s)) => JField("id", JInt(s.toInt)) - case JField("user", x: JObject) => JField("user", JArray(x :: Nil)) + scala> json transformField { + case ("id", JString(s)) => ("id", JInt(s.toInt)) + case ("user", x: JObject) => ("user", JArray(x :: Nil)) } Other direction is supported too. Converting JSON to XML: diff --git a/core/json/src/main/scala/net/liftweb/json/Diff.scala b/core/json/src/main/scala/net/liftweb/json/Diff.scala index 805119fd9f..0284826338 100644 --- a/core/json/src/main/scala/net/liftweb/json/Diff.scala +++ b/core/json/src/main/scala/net/liftweb/json/Diff.scala @@ -30,6 +30,14 @@ case class Diff(changed: JValue, added: JValue, deleted: JValue) { } Diff(applyTo(changed), applyTo(added), applyTo(deleted)) } + + private[json] def toField(name: String): Diff = { + def applyTo(x: JValue) = x match { + case JNothing => JNothing + case _ => JObject((name, x)) + } + Diff(applyTo(changed), applyTo(added), applyTo(deleted)) + } } /** Computes a diff between two JSONs. @@ -39,17 +47,15 @@ object Diff { *

* Example:

    * val Diff(c, a, d) = ("name", "joe") ~ ("age", 10) diff ("fname", "joe") ~ ("age", 11)
-   * c = JObject(JField("age",JInt(11)) :: Nil)
-   * a = JObject(JField("fname",JString("joe")) :: Nil)
-   * d = JObject(JField("name",JString("joe")) :: Nil)
+   * c = JObject(("age",JInt(11)) :: Nil)
+   * a = JObject(("fname",JString("joe")) :: Nil)
+   * d = JObject(("name",JString("joe")) :: Nil)
    * 
*/ def diff(val1: JValue, val2: JValue): Diff = (val1, val2) match { case (x, y) if x == y => Diff(JNothing, JNothing, JNothing) case (JObject(xs), JObject(ys)) => diffFields(xs, ys) case (JArray(xs), JArray(ys)) => diffVals(xs, ys) - case (JField(xn, xv), JField(yn, yv)) if (xn == yn) => diff(xv, yv) map (JField(xn, _)) - case (x @ JField(xn, xv), y @ JField(yn, yv)) if (xn != yn) => Diff(JNothing, y, x) case (JInt(x), JInt(y)) if (x != y) => Diff(JInt(y), JNothing, JNothing) case (JDouble(x), JDouble(y)) if (x != y) => Diff(JDouble(y), JNothing, JNothing) case (JString(x), JString(y)) if (x != y) => Diff(JString(y), JNothing, JNothing) @@ -60,14 +66,11 @@ object Diff { private def diffFields(vs1: List[JField], vs2: List[JField]) = { def diffRec(xleft: List[JField], yleft: List[JField]): Diff = xleft match { case Nil => Diff(JNothing, if (yleft.isEmpty) JNothing else JObject(yleft), JNothing) - case x :: xs => yleft find (_.name == x.name) match { + case x :: xs => yleft find (_._1 == x._1) match { case Some(y) => - val Diff(c1, a1, d1) = diff(x, y) + val Diff(c1, a1, d1) = diff(x._2, y._2).toField(y._1) val Diff(c2, a2, d2) = diffRec(xs, yleft filterNot (_ == y)) - Diff(c1 ++ c2, a1 ++ a2, d1 ++ d2) map { - case f: JField => JObject(f :: Nil) - case x => x - } + Diff(c1 merge c2, a1 merge a2, d1 merge d2) case None => val Diff(c, a, d) = diffRec(xs, yleft) Diff(c, a, JObject(x :: Nil) merge d) diff --git a/core/json/src/main/scala/net/liftweb/json/Extraction.scala b/core/json/src/main/scala/net/liftweb/json/Extraction.scala index 2cf10eaa47..fe1d0c711f 100644 --- a/core/json/src/main/scala/net/liftweb/json/Extraction.scala +++ b/core/json/src/main/scala/net/liftweb/json/Extraction.scala @@ -62,7 +62,7 @@ object Extraction { */ def decompose(a: Any)(implicit formats: Formats): JValue = { def prependTypeHint(clazz: Class[_], o: JObject) = - JField(formats.typeHintFieldName, JString(formats.typeHints.hintFor(clazz))) ++ o + JObject(JField(formats.typeHintFieldName, JString(formats.typeHints.hintFor(clazz))) :: o.obj) def mkObject(clazz: Class[_], fields: List[JField]) = formats.typeHints.containsHint_?(clazz) match { case true => prependTypeHint(clazz, JObject(fields)) @@ -100,7 +100,7 @@ object Extraction { .getOrElse(JField(n, JNothing)) } } getOrElse Nil - val uniqueFields = fields filterNot (f => args.find(_.name == f.name).isDefined) + val uniqueFields = fields filterNot (f => args.find(_._1 == f._1).isDefined) mkObject(x.getClass, uniqueFields ++ args) } } @@ -119,8 +119,9 @@ object Extraction { case JDouble(num) => Map(path -> num.toString) case JInt(num) => Map(path -> num.toString) case JBool(value) => Map(path -> value.toString) - case JField(name, value) => flatten0(path + escapePath(name), value) - case JObject(obj) => obj.foldLeft(Map[String, String]()) { (map, field) => map ++ flatten0(path + ".", field) } + case JObject(obj) => obj.foldLeft(Map[String, String]()) { case (map, (name, value)) => + map ++ flatten0(path + "." + escapePath(name), value) + } case JArray(arr) => arr.length match { case 0 => Map(path -> "[]") case _ => arr.foldLeft((Map[String, String](), 0)) { @@ -207,8 +208,7 @@ object Extraction { if (constructor.choices.size == 1) constructor.choices.head // optimized common case else { val argNames = json match { - case JObject(fs) => fs.map(_.name) - case JField(name, _) => List(name) + case JObject(fs) => fs.map(_._1) case x => Nil } constructor.bestMatching(argNames) @@ -266,7 +266,7 @@ object Extraction { } def mkWithTypeHint(typeHint: String, fields: List[JField], typeInfo: TypeInfo) = { - val obj = JObject(fields filterNot (_.name == formats.typeHintFieldName)) + val obj = JObject(fields filterNot (_._1 == formats.typeHintFieldName)) val deserializer = formats.typeHints.deserialize if (!deserializer.isDefinedAt(typeHint, obj)) { val concreteClass = formats.typeHints.classFor(typeHint) getOrElse fail("Do not know how to deserialize '" + typeHint + "'") @@ -281,7 +281,6 @@ object Extraction { else json match { case JNull => null case JObject(TypeHint(t, fs)) => mkWithTypeHint(t, fs, constructor.targetType) - case JField(_, JObject(TypeHint(t, fs))) => mkWithTypeHint(t, fs, constructor.targetType) case _ => instantiate } } @@ -290,9 +289,9 @@ object Extraction { def unapply(fs: List[JField]): Option[(String, List[JField])] = if (formats.typeHints == NoTypeHints) None else { - val grouped = fs groupBy (_.name == formats.typeHintFieldName) + val grouped = fs groupBy (_._1 == formats.typeHintFieldName) if (grouped.isDefinedAt(true)) - Some((grouped(true).head.value.values.toString, grouped.get(false).getOrElse(Nil))) + Some((grouped(true).head._2.values.toString, grouped.get(false).getOrElse(Nil))) else None } } @@ -313,7 +312,7 @@ object Extraction { case Value(targetType) => convert(root, targetType, formats) case c: Constructor => newInstance(c, root) case Cycle(targetType) => build(root, mappingOf(targetType)) - case Arg(path, m, optional) => mkValue(fieldValue(root), m, path, optional) + case Arg(path, m, optional) => mkValue(root, m, path, optional) case Col(targetType, m) => val custom = formats.customDeserializer(formats) val c = targetType.clazz @@ -324,7 +323,7 @@ object Extraction { else if (classOf[Seq[_]].isAssignableFrom(c)) newCollection(root, m, a => List(a: _*)) else fail("Expected collection but got " + m + " for class " + c) case Dict(m) => root match { - case JObject(xs) => Map(xs.map(x => (x.name, build(x.value, m))): _*) + case JObject(xs) => Map(xs.map(x => (x._1, build(x._2, m))): _*) case x => fail("Expected object but got " + x) } } @@ -357,11 +356,6 @@ object Extraction { } } - def fieldValue(json: JValue): JValue = json match { - case JField(_, value) => value - case x => x - } - build(json, mapping) } @@ -399,7 +393,6 @@ object Extraction { case j: JArray if (targetType == classOf[JArray]) => j case JNull => null case JNothing => fail("Did not find value which can be converted into " + targetType.getName) - case JField(_, x) => convert(x, targetType, formats) case _ => val custom = formats.customDeserializer(formats) val typeInfo = TypeInfo(targetType, None) diff --git a/core/json/src/main/scala/net/liftweb/json/JsonAST.scala b/core/json/src/main/scala/net/liftweb/json/JsonAST.scala index 9faad3b4b1..d44ee27b1d 100644 --- a/core/json/src/main/scala/net/liftweb/json/JsonAST.scala +++ b/core/json/src/main/scala/net/liftweb/json/JsonAST.scala @@ -32,7 +32,7 @@ object JsonAST { object JValue extends Merge.Mergeable /** - * Data type for Json AST. + * Data type for JSON AST. */ sealed abstract class JValue extends Diff.Diffable { type Values @@ -44,24 +44,27 @@ object JsonAST { * json \ "name" * */ - def \(nameToFind: String): JValue = { - val p = (json: JValue) => json match { - case JField(name, value) if name == nameToFind => true - case _ => false - } - findDirect(children, p) match { + def \(nameToFind: String): JValue = + findDirectByName(List(this), nameToFind) match { case Nil => JNothing - case JField(_, x) :: Nil => x case x :: Nil => x case x => JArray(x) } + + private def findDirectByName(xs: List[JValue], name: String): List[JValue] = xs.flatMap { + case JObject(l) => l.filter { + case (n, _) if n == name => true + case _ => false + } map (_._2) + case JArray(l) => findDirectByName(l, name) + case _ => Nil } private def findDirect(xs: List[JValue], p: JValue => Boolean): List[JValue] = xs.flatMap { case JObject(l) => l.filter { - case x if p(x) => true + case (n, x) if p(x) => true case _ => false - } + } map (_._2) case JArray(l) => findDirect(l, p) case x if p(x) => x :: Nil case _ => Nil @@ -75,16 +78,16 @@ object JsonAST { */ def \\(nameToFind: String): JValue = { def find(json: JValue): List[JField] = json match { - case JObject(l) => l.foldLeft(List[JField]())((a, e) => a ::: find(e)) - case JArray(l) => l.foldLeft(List[JField]())((a, e) => a ::: find(e)) - case field @ JField(name, value) if name == nameToFind => field :: find(value) - case JField(_, value) => find(value) + case JObject(l) => l.foldLeft(List[JField]()) { + case (a, (name, value)) => + if (name == nameToFind) a ::: List((name, value)) ::: find(value) else a ::: find(value) + } + case JArray(l) => l.foldLeft(List[JField]())((a, json) => a ::: find(json)) case _ => Nil } find(this) match { - case JField(_, x) :: Nil => x - case x :: Nil => x - case x => JObject(x) + case (_, x) :: Nil => x + case xs => JObject(xs) } } @@ -135,10 +138,9 @@ object JsonAST { * JArray(JInt(1) :: JInt(2) :: Nil).children == List(JInt(1), JInt(2)) * */ - def children = this match { - case JObject(l) => l + def children: List[JValue] = this match { + case JObject(l) => l map (_._2) case JArray(l) => l - case JField(n, v) => List(v) case _ => Nil } @@ -149,37 +151,83 @@ object JsonAST { def rec(acc: A, v: JValue) = { val newAcc = f(acc, v) v match { - case JObject(l) => l.foldLeft(newAcc)((a, e) => e.fold(a)(f)) + case JObject(l) => l.foldLeft(newAcc) { case (a, (name, value)) => value.fold(a)(f) } case JArray(l) => l.foldLeft(newAcc)((a, e) => e.fold(a)(f)) - case JField(_, value) => value.fold(newAcc)(f) case _ => newAcc } } rec(z, this) } + /** Return a combined value by folding over JSON by applying a function f + * for each field. The initial value is z. + */ + def foldField[A](z: A)(f: (A, JField) => A): A = { + def rec(acc: A, v: JValue) = { + v match { + case JObject(l) => l.foldLeft(acc) { + case (a, field@(name, value)) => value.foldField(f(a, field))(f) + } + case JArray(l) => l.foldLeft(acc)((a, e) => e.foldField(a)(f)) + case _ => acc + } + } + rec(z, this) + } + /** Return a new JValue resulting from applying the given function f - * to each element in JSON. + * to each value in JSON. *

* Example:

-     * JArray(JInt(1) :: JInt(2) :: Nil) map { case JInt(x) => JInt(x+1); case x => x }
+     * JArray(JInt(1) :: JInt(2) :: Nil) map {
+     *   case JInt(x) => JInt(x+1)
+     *   case x => x
+     * }
      * 
*/ def map(f: JValue => JValue): JValue = { def rec(v: JValue): JValue = v match { - case JObject(l) => f(JObject(l.map(f => rec(f) match { - case x: JField => x - case x => JField(f.name, x) - }))) + case JObject(l) => f(JObject(l.map { case (n, v) => (n, rec(v)) })) case JArray(l) => f(JArray(l.map(rec))) - case JField(name, value) => f(JField(name, rec(value))) case x => f(x) } rec(this) } + /** Return a new JValue resulting from applying the given function f + * to each field in JSON. + *

+ * Example:

+     * JObject(("age", JInt(10)) :: Nil) map {
+     *   case ("age", JInt(x)) => ("age", JInt(x+1))
+     *   case x => x
+     * }
+     * 
+ */ + def mapField(f: JField => JField): JValue = { + def rec(v: JValue): JValue = v match { + case JObject(l) => JObject(l.map { case (n, v) => f(n, rec(v)) }) + case JArray(l) => JArray(l.map(rec)) + case x => x + } + rec(this) + } + + /** Return a new JValue resulting from applying the given partial function f + * to each field in JSON. + *

+ * Example:

+     * JObject(("age", JInt(10)) :: Nil) transformField {
+     *   case ("age", JInt(x)) => ("age", JInt(x+1))
+     * }
+     * 
+ */ + def transformField(f: PartialFunction[JField, JField]): JValue = mapField { x => + if (f.isDefinedAt(x)) f(x) else x + } + /** Return a new JValue resulting from applying the given partial function f - * to each element in JSON. + * to each value in JSON. *

* Example:

      * JArray(JInt(1) :: JInt(2) :: Nil) transform { case JInt(x) => JInt(x+1) }
@@ -218,6 +266,22 @@ object JsonAST {
       rep(l, this)
     }
 
+    /** Return the first field from JSON which matches the given predicate.
+     * 

+ * Example:

+     * JObject(("age", JInt(2))) findField { case (n, v) => n == "age" }
+     * 
+ */ + def findField(p: JField => Boolean): Option[JField] = { + def find(json: JValue): Option[JField] = json match { + case JObject(fs) if (fs find p).isDefined => return fs find p + case JObject(fs) => fs.flatMap { case (n, v) => find(v) }.headOption + case JArray(l) => l.flatMap(find _).headOption + case _ => None + } + find(this) + } + /** Return the first element from JSON which matches the given predicate. *

* Example:

@@ -228,16 +292,27 @@ object JsonAST {
       def find(json: JValue): Option[JValue] = {
         if (p(json)) return Some(json)
         json match {
-          case JObject(l) => l.flatMap(find _).headOption
+          case JObject(fs) => fs.flatMap { case (n, v) => find(v) }.headOption
           case JArray(l) => l.flatMap(find _).headOption
-          case JField(_, value) => find(value)
           case _ => None
         }
       }
       find(this)
     }
 
-    /** Return a List of all elements which matches the given predicate.
+    /** Return a List of all fields which matches the given predicate.
+     * 

+ * Example:

+     * JObject(("age", JInt(10)) :: Nil) filterField {
+     *   case ("age", JInt(x)) if x > 18 => true
+     *   case _          => false
+     * }
+     * 
+ */ + def filterField(p: JField => Boolean): List[JField] = + foldField(List[JField]())((acc, e) => if (p(e)) e :: acc else acc).reverse + + /** Return a List of all values which matches the given predicate. *

* Example:

      * JArray(JInt(1) :: JInt(2) :: Nil) filter { case JInt(x) => x > 1; case _ => false }
@@ -258,19 +333,29 @@ object JsonAST {
       def append(value1: JValue, value2: JValue): JValue = (value1, value2) match {
         case (JNothing, x) => x
         case (x, JNothing) => x
-        case (JObject(xs), x: JField) => JObject(xs ::: List(x))
-        case (x: JField, JObject(xs)) => JObject(x :: xs)
         case (JArray(xs), JArray(ys)) => JArray(xs ::: ys)
         case (JArray(xs), v: JValue) => JArray(xs ::: List(v))
         case (v: JValue, JArray(xs)) => JArray(v :: xs)
-        case (f1: JField, f2: JField) => JObject(f1 :: f2 :: Nil)
-        case (JField(n, v1), v2: JValue) => JField(n, append(v1, v2))
         case (x, y) => JArray(x :: y :: Nil)
       }
       append(this, other)
     }
 
-    /** Return a JSON where all elements matching the given predicate are removed.
+    /** Return a JSON where all fields matching the given predicate are removed.
+     * 

+ * Example:

+     * JObject(("age", JInt(10)) :: Nil) removeField {
+     *   case ("age", _) => true
+     *   case _          => false
+     * }
+     * 
+ */ + def removeField(p: JField => Boolean): JValue = this mapField { + case x if p(x) => (x._1, JNothing) + case x => x + } + + /** Return a JSON where all values matching the given predicate are removed. *

* Example:

      * JArray(JInt(1) :: JInt(2) :: JNull :: Nil) remove { _ == JNull }
@@ -365,26 +450,32 @@ object JsonAST {
     type Values = Boolean
     def values = value
   }
-  case class JField(name: String, value: JValue) extends JValue {
-    type Values = (String, value.Values)
-    def values = (name, value.values)
-    override def apply(i: Int): JValue = value(i)
-  }
+  
   case class JObject(obj: List[JField]) extends JValue {
     type Values = Map[String, Any]
-    def values = Map() ++ obj.map(_.values : (String, Any))
+    def values = obj.map { case (n, v) => (n, v.values) } toMap
 
     override def equals(that: Any): Boolean = that match {
       case o: JObject => Set(obj.toArray: _*) == Set(o.obj.toArray: _*)
       case _ => false
     }
   }
+  case object JObject {
+    def apply(fs: JField*): JObject = JObject(fs.toList)
+  }
+
   case class JArray(arr: List[JValue]) extends JValue {
     type Values = List[Any]
     def values = arr.map(_.values)
     override def apply(i: Int): JValue = arr(i)
   }
 
+  type JField = (String, JValue)
+  object JField {
+    def apply(name: String, value: JValue) = (name, value)
+    def unapply(f: JField): Option[(String, JValue)] = Some(f)
+  }
+
   /** Renders JSON.
    * @see Printer#compact
    * @see Printer#pretty
@@ -400,14 +491,13 @@ object JsonAST {
     case JString(null) => text("null")
     case JString(s)    => text("\"" + quote(s) + "\"")
     case JArray(arr)   => text("[") :: series(trimArr(arr).map(render)) :: text("]")
-    case JField(n, v)  => text("\"" + n + "\":") :: render(v)
     case JObject(obj)  =>
-      val nested = break :: fields(trimObj(obj).map(f => text("\"" + f.name + "\":") :: render(f.value)))
+      val nested = break :: fields(trimObj(obj).map { case (name, value) => text("\"" + name + "\":") :: render(value) })
       text("{") :: nest(2, nested) :: break :: text("}")
   }
 
   private def trimArr(xs: List[JValue]) = xs.filter(_ != JNothing)
-  private def trimObj(xs: List[JField]) = xs.filter(_.value != JNothing)
+  private def trimObj(xs: List[JField]) = xs.filter(_._2 != JNothing)
   private def series(docs: List[Document]) = fold(punctuate(text(","), docs))
   private def fields(docs: List[Document]) = fold(punctuate(text(",") :: break, docs))
   private def fold(docs: List[Document]) = docs.foldLeft[Document](empty)(_ :: _)
diff --git a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala
index ca495795f8..6fda6e0366 100644
--- a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala
+++ b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala
@@ -145,36 +145,36 @@ object JsonParser {
 
     // This is a slightly faster way to correct order of fields and arrays than using 'map'.
     def reverse(v: JValue): JValue = v match {
-      case JObject(l) => JObject(l.map(reverse).asInstanceOf[List[JField]].reverse)
+      case JObject(l) => JObject((l.map { case (n, v) => (n, reverse(v)) }).reverse)
       case JArray(l) => JArray(l.map(reverse).reverse)
-      case JField(name, value) => JField(name, reverse(value))
       case x => x
     }
 
-    def closeBlock(v: JValue) {
+    def closeBlock(v: Any) {
+      @inline def toJValue(x: Any) = x match {
+        case json: JValue => json
+        case _ => p.fail("unexpected field " + x)
+      }
+
       vals.peekOption match {
-        case Some(f: JField) =>
-          val field = vals.pop(classOf[JField])
-          val newField = JField(field.name, v)
+        case Some((name: String, value)) =>
+          vals.pop(classOf[JField])
           val obj = vals.peek(classOf[JObject])
-          vals.replace(JObject(newField :: obj.obj))
-        case Some(o: JObject) => v match {
-          case x: JField => vals.replace(JObject(x :: o.obj))
-          case _ => p.fail("expected field but got " + v)
-        }
-        case Some(a: JArray) => vals.replace(JArray(v :: a.arr))
+          vals.replace(JObject((name, toJValue(v)) :: obj.obj))
+        case Some(o: JObject) => 
+          vals.replace(JObject(vals.peek(classOf[JField]) :: o.obj))
+        case Some(a: JArray) => vals.replace(JArray(toJValue(v) :: a.arr))
         case Some(x) => p.fail("expected field, array or object but got " + x)
-        case None => root = Some(reverse(v))
+        case None => root = Some(reverse(toJValue(v)))
       }
     }
 
     def newValue(v: JValue) {
-      vals.peek(classOf[JValue]) match {
-        case f: JField =>
+      vals.peekAny match {
+        case (name: String, value) =>
           vals.pop(classOf[JField])
-          val newField = JField(f.name, v)
           val obj = vals.peek(classOf[JObject])
-          vals.replace(JObject(newField :: obj.obj))
+          vals.replace(JObject((name, v) :: obj.obj))
         case a: JArray => vals.replace(JArray(v :: a.arr))
         case _ => p.fail("expected field or array")
       }
@@ -190,7 +190,7 @@ object JsonParser {
         case DoubleVal(x)     => newValue(JDouble(x))
         case BoolVal(x)       => newValue(JBool(x))
         case NullVal          => newValue(JNull)
-        case CloseObj         => closeBlock(vals.pop(classOf[JValue]))
+        case CloseObj         => closeBlock(vals.popAny)
         case OpenArr          => vals.push(JArray(Nil))
         case CloseArr         => closeBlock(vals.pop(classOf[JArray]))
         case End              =>
@@ -204,14 +204,16 @@ object JsonParser {
 
   private class ValStack(parser: Parser) {
     import java.util.LinkedList
-    private[this] val stack = new LinkedList[JValue]()
+    private[this] val stack = new LinkedList[Any]()
 
-    def pop[A <: JValue](expectedType: Class[A]) = convert(stack.poll, expectedType)
-    def push(v: JValue) = stack.addFirst(v)
-    def peek[A <: JValue](expectedType: Class[A]) = convert(stack.peek, expectedType)
-    def replace[A <: JValue](newTop: JValue) = stack.set(0, newTop)
+    def popAny = stack.poll
+    def pop[A](expectedType: Class[A]) = convert(stack.poll, expectedType)
+    def push(v: Any) = stack.addFirst(v)
+    def peekAny = stack.peek
+    def peek[A](expectedType: Class[A]) = convert(stack.peek, expectedType)
+    def replace[A](newTop: Any) = stack.set(0, newTop)
 
-    private def convert[A <: JValue](x: JValue, expectedType: Class[A]): A = {
+    private def convert[A](x: Any, expectedType: Class[A]): A = {
       if (x == null) parser.fail("expected object or array")
       try { x.asInstanceOf[A] } catch { case _: ClassCastException => parser.fail("unexpected " + x) }
     }
diff --git a/core/json/src/main/scala/net/liftweb/json/Merge.scala b/core/json/src/main/scala/net/liftweb/json/Merge.scala
index dcbb0ee5bd..e95cf6fce9 100644
--- a/core/json/src/main/scala/net/liftweb/json/Merge.scala
+++ b/core/json/src/main/scala/net/liftweb/json/Merge.scala
@@ -35,8 +35,6 @@ private [json] trait LowPriorityMergeDep {
     private def merge(val1: JValue, val2: JValue): JValue = (val1, val2) match {
       case (JObject(xs), JObject(ys)) => JObject(Merge.mergeFields(xs, ys))
       case (JArray(xs), JArray(ys)) => JArray(Merge.mergeVals(xs, ys))
-      case (JField(n1, v1), JField(n2, v2)) if n1 == n2 => JField(n1, merge(v1, v2))
-      case (f1: JField, f2: JField) => f2
       case (JNothing, x) => x
       case (x, JNothing) => x
       case (_, y) => y
@@ -61,7 +59,7 @@ object Merge {
    * 

* Example:

    * val m = ("name", "joe") ~ ("age", 10) merge ("name", "joe") ~ ("iq", 105)
-   * m: JObject(List(JField(name,JString(joe)), JField(age,JInt(10)), JField(iq,JInt(105))))
+   * m: JObject(List((name,JString(joe)), (age,JInt(10)), (iq,JInt(105))))
    * 
*/ def merge[A <: JValue, B <: JValue, R <: JValue] @@ -70,9 +68,9 @@ object Merge { private[json] def mergeFields(vs1: List[JField], vs2: List[JField]): List[JField] = { def mergeRec(xleft: List[JField], yleft: List[JField]): List[JField] = xleft match { case Nil => yleft - case JField(xn, xv) :: xs => yleft find (_.name == xn) match { - case Some(y @ JField(yn, yv)) => - JField(xn, merge(xv, yv)) :: mergeRec(xs, yleft filterNot (_ == y)) + case (xn, xv) :: xs => yleft find (_._1 == xn) match { + case Some(y @ (yn, yv)) => + (xn, merge(xv, yv)) :: mergeRec(xs, yleft filterNot (_ == y)) case None => JField(xn, xv) :: mergeRec(xs, yleft) } } diff --git a/core/json/src/main/scala/net/liftweb/json/Xml.scala b/core/json/src/main/scala/net/liftweb/json/Xml.scala index 720076c855..8f22693bc7 100644 --- a/core/json/src/main/scala/net/liftweb/json/Xml.scala +++ b/core/json/src/main/scala/net/liftweb/json/Xml.scala @@ -107,11 +107,11 @@ object Xml { case XLeaf((name, value), attrs) => (value, attrs) match { case (_, Nil) => toJValue(value) case (XValue(""), xs) => JObject(mkFields(xs)) - case (_, xs) => JObject(JField(name, toJValue(value)) :: mkFields(xs)) + case (_, xs) => JObject((name, toJValue(value)) :: mkFields(xs)) } case XNode(xs) => JObject(mkFields(xs)) case XArray(elems) => JArray(elems.map(toJValue)) - } + } def mkFields(xs: List[(String, XElem)]) = xs.flatMap { case (name, value) => (value, toJValue(value)) match { @@ -145,7 +145,7 @@ object Xml { case List(x @ XLeaf(_, _ :: _)) => toJValue(x) case List(x) => JObject(JField(nameOf(xml.head), toJValue(x)) :: Nil) case x => JArray(x.map(toJValue)) - } + } } /** Convert given JSON to XML. @@ -169,9 +169,8 @@ object Xml { */ def toXml(json: JValue): NodeSeq = { def toXml(name: String, json: JValue): NodeSeq = json match { - case JObject(fields) => new XmlNode(name, fields flatMap { f => toXml(f.name, f.value) }) + case JObject(fields) => new XmlNode(name, fields flatMap { case (n, v) => toXml(n, v) }) case JArray(xs) => xs flatMap { v => toXml(name, v) } - case JField(n, v) => new XmlNode(name, toXml(n, v)) case JInt(x) => new XmlElem(name, x.toString) case JDouble(x) => new XmlElem(name, x.toString) case JString(x) => new XmlElem(name, x) @@ -181,8 +180,7 @@ object Xml { } json match { - case JField(n, v) => toXml(n, v) - case JObject(fields) => fields flatMap { f => toXml(f.name, f.value) } + case JObject(fields) => fields flatMap { case (name, value) => toXml(name, value) } case x => toXml("root", x) } } diff --git a/core/json/src/test/scala/net/liftweb/json/DiffExamples.scala b/core/json/src/test/scala/net/liftweb/json/DiffExamples.scala index 85eb4d959d..c103b213e6 100644 --- a/core/json/src/test/scala/net/liftweb/json/DiffExamples.scala +++ b/core/json/src/test/scala/net/liftweb/json/DiffExamples.scala @@ -19,10 +19,6 @@ package json import org.specs.Specification - -/** - * System under specification for Diff Examples. - */ object DiffExamples extends Specification("Diff Examples") { import MergeExamples.{scala1, scala2, lotto1, lotto2, mergedLottoResult} diff --git a/core/json/src/test/scala/net/liftweb/json/Examples.scala b/core/json/src/test/scala/net/liftweb/json/Examples.scala index 97b6a38dcf..61d3a358a9 100644 --- a/core/json/src/test/scala/net/liftweb/json/Examples.scala +++ b/core/json/src/test/scala/net/liftweb/json/Examples.scala @@ -19,7 +19,6 @@ package json import org.specs.Specification - object Examples extends Specification { import JsonAST.concat import JsonDSL._ @@ -40,26 +39,26 @@ object Examples extends Specification { } "Transformation example" in { - val uppercased = parse(person).transform { case JField(n, v) => JField(n.toUpperCase, v) } + val uppercased = parse(person).transformField { case JField(n, v) => JField(n.toUpperCase, v) } val rendered = compact(render(uppercased)) rendered mustEqual """{"PERSON":{"NAME":"Joe","AGE":35,"SPOUSE":{"PERSON":{"NAME":"Marilyn","AGE":33}}}}""" } "Remove example" in { - val json = parse(person) remove { _ == JField("name", "Marilyn") } + val json = parse(person) removeField { _ == JField("name", "Marilyn") } compact(render(json \\ "name")) mustEqual """{"name":"Joe"}""" } "Queries on person example" in { val json = parse(person) - val filtered = json filter { + val filtered = json filterField { case JField("name", _) => true case _ => false } filtered mustEqual List(JField("name", JString("Joe")), JField("name", JString("Marilyn"))) - val found = json find { + val found = json findField { case JField("name", _) => true case _ => false } @@ -68,10 +67,10 @@ object Examples extends Specification { "Object array example" in { val json = parse(objArray) - compact(render(json \ "children" \ "name")) mustEqual """["name":"Mary","name":"Mazy"]""" + compact(render(json \ "children" \ "name")) mustEqual """["Mary","Mazy"]""" compact(render((json \ "children")(0) \ "name")) mustEqual "\"Mary\"" compact(render((json \ "children")(1) \ "name")) mustEqual "\"Mazy\"" - (for { JField("name", JString(y)) <- json } yield y) mustEqual List("joe", "Mary", "Mazy") + (for { JObject(o) <- json; JField("name", JString(y)) <- o } yield y) mustEqual List("joe", "Mary", "Mazy") } "Unbox values using XPath-like type expression" in { @@ -109,13 +108,13 @@ object Examples extends Specification { } "JSON building example" in { - val json = concat(JField("name", JString("joe")), JField("age", JInt(34))) ++ concat(JField("name", JString("mazy")), JField("age", JInt(31))) + val json = JObject(("name", JString("joe")), ("age", JInt(34))) ++ JObject(("name", ("mazy")), ("age", JInt(31))) compact(render(json)) mustEqual """[{"name":"joe","age":34},{"name":"mazy","age":31}]""" } "JSON building with implicit primitive conversions example" in { import Implicits._ - val json = concat(JField("name", "joe"), JField("age", 34)) ++ concat(JField("name", "mazy"), JField("age", 31)) + val json = JObject(("name", "joe"), ("age", 34)) ++ JObject(("name", "mazy"), ("age", 31)) compact(render(json)) mustEqual """[{"name":"joe","age":34},{"name":"mazy","age":31}]""" } diff --git a/core/json/src/test/scala/net/liftweb/json/ExtractionExamplesSpec.scala b/core/json/src/test/scala/net/liftweb/json/ExtractionExamplesSpec.scala index a42eacc472..fe125cdd9f 100644 --- a/core/json/src/test/scala/net/liftweb/json/ExtractionExamplesSpec.scala +++ b/core/json/src/test/scala/net/liftweb/json/ExtractionExamplesSpec.scala @@ -72,7 +72,6 @@ object ExtractionExamples extends Specification("Extraction Examples Specificati JInt(1).extract[Int] mustEqual 1 JInt(1).extract[String] mustEqual "1" - JField("foo", JInt(1)).extract[Int] mustEqual 1 } "Primitive extraction example" in { diff --git a/core/json/src/test/scala/net/liftweb/json/JValueGen.scala b/core/json/src/test/scala/net/liftweb/json/JValueGen.scala index 8f037c7f44..fa1745a56c 100644 --- a/core/json/src/test/scala/net/liftweb/json/JValueGen.scala +++ b/core/json/src/test/scala/net/liftweb/json/JValueGen.scala @@ -39,7 +39,7 @@ trait JValueGen { def genJValueClass: Gen[Class[_ <: JValue]] = oneOf( JNull.getClass.asInstanceOf[Class[JValue]], JNothing.getClass.asInstanceOf[Class[JValue]], classOf[JInt], - classOf[JDouble], classOf[JBool], classOf[JString], classOf[JField], classOf[JArray], classOf[JObject]) + classOf[JDouble], classOf[JBool], classOf[JString], classOf[JArray], classOf[JObject]) def listSize = choose(0, 5).sample.get } diff --git a/core/json/src/test/scala/net/liftweb/json/JsonAstSpec.scala b/core/json/src/test/scala/net/liftweb/json/JsonAstSpec.scala index 808ca3dfd4..1ad42c1bdf 100644 --- a/core/json/src/test/scala/net/liftweb/json/JsonAstSpec.scala +++ b/core/json/src/test/scala/net/liftweb/json/JsonAstSpec.scala @@ -21,10 +21,6 @@ import org.specs.{ScalaCheck, Specification} import org.scalacheck._ import org.scalacheck.Prop.{forAll, forAllNoShrink} - -/** - * System under specification for JSON AST. - */ object JsonAstSpec extends Specification("JSON AST Specification") with JValueGen with ScalaCheck { "Functor identity" in { val identityProp = (json: JValue) => json == (json map identity) @@ -94,12 +90,11 @@ object JsonAstSpec extends Specification("JSON AST Specification") with JValueGe forAll(removeNothingProp) must pass } - "Remove removes only matching elements (in case of a field, its value is set to JNothing)" in { + "Remove removes only matching elements" in { forAllNoShrink(genJValue, genJValueClass) { (json: JValue, x: Class[_ <: JValue]) => { val removed = json remove typePredicate(x) val Diff(c, a, d) = json diff removed val elemsLeft = removed filter { - case JField(_, JNothing) => false case _ => true } c == JNothing && a == JNothing && elemsLeft.forall(_.getClass != x) @@ -109,9 +104,8 @@ object JsonAstSpec extends Specification("JSON AST Specification") with JValueGe "Replace one" in { val anyReplacement = (x: JValue, replacement: JObject) => { def findOnePath(jv: JValue, l: List[String]): List[String] = jv match { - case JField(name, value) => findOnePath(value, name :: l) case JObject(fl) => fl match { - case field :: xs => findOnePath(field, l) + case field :: xs => findOnePath(field._2, l) case Nil => l } case _ => l diff --git a/core/json/src/test/scala/net/liftweb/json/LottoExample.scala b/core/json/src/test/scala/net/liftweb/json/LottoExample.scala index 040fe069e2..e6f51a4772 100644 --- a/core/json/src/test/scala/net/liftweb/json/LottoExample.scala +++ b/core/json/src/test/scala/net/liftweb/json/LottoExample.scala @@ -19,10 +19,6 @@ package json import org.specs.Specification - -/** - * System under specification for Lotto Examples. - */ object LottoExample extends Specification("Lotto Examples") { import JsonDSL._ diff --git a/core/json/src/test/scala/net/liftweb/json/XmlExamples.scala b/core/json/src/test/scala/net/liftweb/json/XmlExamples.scala index ec01269d75..77bf3a7806 100644 --- a/core/json/src/test/scala/net/liftweb/json/XmlExamples.scala +++ b/core/json/src/test/scala/net/liftweb/json/XmlExamples.scala @@ -19,10 +19,6 @@ package json import org.specs.Specification - -/** - * System under specification for Xml Examples. - */ object XmlExamples extends Specification("XML Examples") { import JsonDSL._ import Xml._ @@ -34,14 +30,14 @@ object XmlExamples extends Specification("XML Examples") { } "Conversion transformation example 1" in { - val json = toJson(users1).transform { + val json = toJson(users1).transformField { case JField("id", JString(s)) => JField("id", JInt(s.toInt)) } compact(render(json)) mustEqual """{"users":{"count":"2","user":[{"disabled":"true","id":1,"name":"Harry"},{"id":2,"name":"David","nickname":"Dave"}]}}""" } "Conversion transformation example 2" in { - val json = toJson(users2).transform { + val json = toJson(users2).transformField { case JField("id", JString(s)) => JField("id", JInt(s.toInt)) case JField("user", x: JObject) => JField("user", JArray(x :: Nil)) } @@ -58,7 +54,7 @@ object XmlExamples extends Specification("XML Examples") { val printer = new scala.xml.PrettyPrinter(100,2) val lotto: JObject = LottoExample.json - val xml = toXml(lotto.transform { + val xml = toXml(lotto.transformField { case JField("winning-numbers", JArray(nums)) => JField("winning-numbers", flattenArray(nums)) case JField("numbers", JArray(nums)) => JField("numbers", flattenArray(nums)) }) @@ -152,11 +148,11 @@ object XmlExamples extends Specification("XML Examples") { // default conversion rules. The transformation function 'attrToObject' makes following conversion: // { ..., "fieldName": "", "attrName":"someValue", ...} -> // { ..., "fieldName": { "attrName": f("someValue") }, ... } - def attrToObject(fieldName: String, attrName: String, f: JString => JValue)(json: JValue) = json.transform { - case JField(n, v: JString) if n == attrName => JObject(JField(n, f(v)) :: Nil) - case JField(n, JString("")) if n == fieldName => JNothing - } transform { - case JField(n, x: JObject) if n == attrName => JField(fieldName, x) + def attrToObject(fieldName: String, attrName: String, f: JString => JValue)(json: JValue) = json.transformField { + case (n, v: JString) if n == attrName => JField(fieldName, JObject(JField(n, f(v)) :: Nil)) + case (n, JString("")) if n == fieldName => JField(n, JNothing) + } transformField { + case (n, x: JObject) if n == attrName => JField(fieldName, x) } "Example with multiple attributes, multiple nested elements " in { From 852b135e4ba36ba6644c8b18c0f018d98d8a4b17 Mon Sep 17 00:00:00 2001 From: David Pollak Date: Mon, 9 Jan 2012 14:41:19 -0800 Subject: [PATCH 0002/1949] Closes #1173. Allow data-lift-content-id attribute in body tag to identify the id of the actual start of content --- web/webkit/src/main/scala/net/liftweb/http/Templates.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/web/webkit/src/main/scala/net/liftweb/http/Templates.scala b/web/webkit/src/main/scala/net/liftweb/http/Templates.scala index 6114ab84e9..56debeccc4 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Templates.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Templates.scala @@ -131,6 +131,7 @@ object Templates { case e: Elem if e.label == "html" => e.child.flatMap { case e: Elem if e.label == "body" => { + e.attribute("data-lift-content-id").headOption.map(_.text) orElse e.attribute("class").flatMap { ns => { val clz = ns.text.charSplit(' ') From 1444c640bc8798829aa467fa6e197a0e8bb09e07 Mon Sep 17 00:00:00 2001 From: jeppenejsum Date: Thu, 23 Feb 2012 17:31:01 +0100 Subject: [PATCH 0003/1949] Fix problem with jsonCall implicits not in scope --- .../main/scala/net/liftweb/util/BasicTypesHelpers.scala | 2 +- .../scala/net/liftweb/util/BasicTypesHelpersSpec.scala | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/core/util/src/main/scala/net/liftweb/util/BasicTypesHelpers.scala b/core/util/src/main/scala/net/liftweb/util/BasicTypesHelpers.scala index 7ac90cd4b0..f908ea88cd 100644 --- a/core/util/src/main/scala/net/liftweb/util/BasicTypesHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/BasicTypesHelpers.scala @@ -37,7 +37,7 @@ object AvoidTypeErasureIssues1 { /** * Automagically vend a AvoidTypeErasureIssues1 */ - implicit def vendOne(): AvoidTypeErasureIssues1 = new AvoidTypeErasureIssues1 {} + implicit val vendOne: AvoidTypeErasureIssues1 = new AvoidTypeErasureIssues1 {} } /** diff --git a/core/util/src/test/scala/net/liftweb/util/BasicTypesHelpersSpec.scala b/core/util/src/test/scala/net/liftweb/util/BasicTypesHelpersSpec.scala index f384db5282..0e5d2fbc1d 100644 --- a/core/util/src/test/scala/net/liftweb/util/BasicTypesHelpersSpec.scala +++ b/core/util/src/test/scala/net/liftweb/util/BasicTypesHelpersSpec.scala @@ -179,5 +179,13 @@ object BasicTypesHelpersSpec extends Specification("BasicTypesHelpers Specificat } } + "AvoidTypeErasure implicit value" should { + "be in scope" in { + def f(i:Int)(implicit d: AvoidTypeErasureIssues1) = i+1 + + f(2) must_== 3 + } + } + } From 47cd758a4fc468327049923b90bab70fe8e11419 Mon Sep 17 00:00:00 2001 From: David Pollak Date: Mon, 27 Feb 2012 10:55:27 -0800 Subject: [PATCH 0004/1949] Fixed a bug where the distance from an empty string causes an exception --- core/util/src/main/scala/net/liftweb/util/LD.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/util/src/main/scala/net/liftweb/util/LD.scala b/core/util/src/main/scala/net/liftweb/util/LD.scala index 7799812e57..a10dd16542 100644 --- a/core/util/src/main/scala/net/liftweb/util/LD.scala +++ b/core/util/src/main/scala/net/liftweb/util/LD.scala @@ -91,7 +91,10 @@ object LD { new ListBuffer)) } - matrix(y1, 0, (1 to x.length).toList).last + matrix(y1, 0, (1 to x.length).toList) match { + case Nil => 10000 + case xs => xs.last + } } } From 648e52c6441096aba736d8c2879896773baa8fb0 Mon Sep 17 00:00:00 2001 From: David Pollak Date: Mon, 27 Feb 2012 10:58:26 -0800 Subject: [PATCH 0005/1949] Closes #1225 --- core/util/src/main/scala/net/liftweb/util/LD.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/util/src/main/scala/net/liftweb/util/LD.scala b/core/util/src/main/scala/net/liftweb/util/LD.scala index a10dd16542..31b1abc082 100644 --- a/core/util/src/main/scala/net/liftweb/util/LD.scala +++ b/core/util/src/main/scala/net/liftweb/util/LD.scala @@ -92,7 +92,7 @@ object LD { } matrix(y1, 0, (1 to x.length).toList) match { - case Nil => 10000 + case Nil => 100000 case xs => xs.last } } From b4c0809958426494dd02a52d0cc31f2f955e970c Mon Sep 17 00:00:00 2001 From: David Pollak Date: Mon, 27 Feb 2012 11:19:53 -0800 Subject: [PATCH 0006/1949] Addresses #1198. Made certain Box methods call-by-name --- .../main/scala/net/liftweb/common/Box.scala | 59 ++++++++----------- 1 file changed, 24 insertions(+), 35 deletions(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index 05d636121a..f3d310810a 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -302,12 +302,6 @@ sealed abstract class Box[+A] extends Product { */ def isA[B](cls: Class[B]): Box[B] = Empty - /** - * If the partial function is defined at the current Box's value - * apply the partial function. - */ - def collect[B](pf: PartialFunction[A, B]): Box[B] - /** * Return a Full[B] if the contents of this Box is of type B, otherwise return Empty */ @@ -353,7 +347,7 @@ sealed abstract class Box[+A] extends Product { * @param msg the failure message * @return a Failure with the message if this Box is Empty */ - def ?~(msg: String): Box[A] = this + def ?~(msg: => String): Box[A] = this /** * Transform an Empty to a ParamFailure with the specified typesafe @@ -361,12 +355,12 @@ sealed abstract class Box[+A] extends Product { * @param errorCode a value indicating the error * @return a ParamFailure with the specified value */ - def ~>[T](errorCode: T): Box[A] = this + def ~>[T](errorCode: => T): Box[A] = this /** * Alias for ?~ */ - def failMsg(msg: String): Box[A] = ?~(msg) + def failMsg(msg: => String): Box[A] = ?~(msg) /** * Transform an EmptyBox to a Failure with the specified message and chain @@ -374,12 +368,12 @@ sealed abstract class Box[+A] extends Product { * @param msg the failure message * @return a Failure with the message if this Box is an Empty Box. Chain the messages if it is already a Failure */ - def ?~!(msg: String): Box[A] = ?~(msg) + def ?~!(msg: => String): Box[A] = ?~(msg) /** * Alias for ?~! */ - def compoundFailMsg(msg: String): Box[A] = ?~!(msg) + def compoundFailMsg(msg: => String): Box[A] = ?~!(msg) /** * Filter this box on the specified predicate, returning a Failure with the specified @@ -394,7 +388,7 @@ sealed abstract class Box[+A] extends Product { * This method calls the specified function with the value contained in this Box * @return the result of the function or a default value */ - def run[T](in: T)(f: (T, A) => T) = in + def run[T](in: => T)(f: (T, A) => T) = in /** * Perform a side effect by passing this Box to the specified function @@ -453,6 +447,17 @@ sealed abstract class Box[+A] extends Product { * Fill with the Box's value */ def toLeft[B](right: => B): Either[A, B] = Right(right) + + + /** + * If the partial function is defined at the current Box's value + * apply the partial function. + */ + final def collect[B](pf: PartialFunction[A, B]): Box[B] = { + flatMap(value => + if (pf.isDefinedAt(value)) Full(pf(value)) + else Empty) + } } /** @@ -497,7 +502,7 @@ final case class Full[+A](value: A) extends Box[A] { override def toOption: Option[A] = Some(value) - override def run[T](in: T)(f: (T, A) => T) = f(in, value) + override def run[T](in: => T)(f: (T, A) => T) = f(in, value) /** * An Either that is a Left with the given argument @@ -532,15 +537,6 @@ final case class Full[+A](value: A) extends Box[A] { override def ===[B >: A](to: B): Boolean = value == to override def dmap[B](dflt: => B)(f: A => B): B = f(value) - - /** - * If the partial function is defined at the current Box's value - * apply the partial function. - */ - final def collect[B](pf: PartialFunction[A, B]): Box[B] = { - if (pf.isDefinedAt(value)) Full(pf(value)) - else Empty - } } /** @@ -576,19 +572,12 @@ sealed abstract class EmptyBox extends Box[Nothing] { override def filter(p: Nothing => Boolean): Box[Nothing] = this - override def ?~(msg: String): Failure = Failure(msg, Empty, Empty) + override def ?~(msg: => String): Failure = Failure(msg, Empty, Empty) - override def ?~!(msg: String): Failure = Failure(msg, Empty, Empty) + override def ?~!(msg: => String): Failure = Failure(msg, Empty, Empty) - override def ~>[T](errorCode: T): ParamFailure[T] = + override def ~>[T](errorCode: => T): ParamFailure[T] = ParamFailure("", Empty, Empty, errorCode) - - - /** - * If the partial function is defined at the current Box's value - * apply the partial function. - */ - final override def collect[B](pf: PartialFunction[Nothing, B]): Box[B] = this } /** @@ -674,11 +663,11 @@ sealed case class Failure(msg: String, exception: Box[Throwable], chain: Box[Fai case _ => false } - override def ?~(msg: String): Failure = this + override def ?~(msg: => String): Failure = this - override def ?~!(msg: String): Failure = Failure(msg, Empty, Full(this)) + override def ?~!(msg: => String): Failure = Failure(msg, Empty, Full(this)) - override def ~>[T](errorCode: T): ParamFailure[T] = ParamFailure(msg, exception, chain, errorCode) + override def ~>[T](errorCode: => T): ParamFailure[T] = ParamFailure(msg, exception, chain, errorCode) } /** From eeadd5d4e9d9ed825000be3a94f181d92491d4f2 Mon Sep 17 00:00:00 2001 From: David Pollak Date: Mon, 27 Feb 2012 17:00:39 -0800 Subject: [PATCH 0007/1949] WIP for getting the renderversion right for Ajax calls --- .../src/main/scala/net/liftweb/http/CometActor.scala | 4 ++-- .../src/main/scala/net/liftweb/http/LiftServlet.scala | 3 ++- .../src/main/scala/net/liftweb/http/LiftSession.scala | 7 ++++++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala b/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala index a3a087c582..89ac98a6af 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala @@ -540,7 +540,7 @@ trait CometActor extends LiftActor with LiftCometActor with BindHelpers { private def lastRendering: RenderOut = if (dontCacheRendering) { val ret = (render ++ jsonInCode): RenderOut - theSession.updateFunctionMap(S.functionMap, spanId, lastRenderTime) + theSession.updateFunctionMap(S.functionMap, uniqueId, lastRenderTime) ret } else { _realLastRendering @@ -1118,7 +1118,7 @@ trait CometActor extends LiftActor with LiftCometActor with BindHelpers { lastRendering = render ++ jsonInCode } - theSession.updateFunctionMap(S.functionMap, spanId, lastRenderTime) + theSession.updateFunctionMap(S.functionMap, uniqueId, lastRenderTime) val rendered: AnswerRender = AnswerRender(new XmlOrJsCmd(spanId, lastRendering, buildSpan _, notices toList), diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala index 606efbd02c..0bc89b42c7 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala @@ -371,7 +371,7 @@ class LiftServlet extends Loggable { } finally { if (S.functionMap.size > 0) { - liftSession.updateFunctionMap(S.functionMap, S.uri, millis) + liftSession.updateFunctionMap(S.functionMap, S.renderVersion, millis) S.clearFunctionMap } liftSession.notices = S.getNotices @@ -481,6 +481,7 @@ class LiftServlet extends Loggable { Full(ret) } finally { + println("Hey Ajax boy... upding function map "+S.functionMap) liftSession.updateFunctionMap(S.functionMap, RenderVersion.get, millis) } } diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index 536def8a8b..61cfec4b8a 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -695,8 +695,13 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, * Updates the internal functions mapping */ def updateFunctionMap(funcs: Map[String, S.AFuncHolder], uniqueId: String, when: Long): Unit = synchronized { + println("Updating function map for unique id "+uniqueId+" Map "+funcs) + Thread.dumpStack() + funcs.foreach { - case (name, func) => messageCallback(name) = func.duplicate(uniqueId) + case (name, func) => + messageCallback(name) = + if (func.owner == Full(uniqueId)) func else func.duplicate(uniqueId) } } From 42ca1b10fef2d32e6891f21f282821436db75ded Mon Sep 17 00:00:00 2001 From: Joni Freeman Date: Tue, 28 Feb 2012 05:40:40 +0200 Subject: [PATCH 0008/1949] Workaround for issue #1169 --- core/json/src/main/scala/net/liftweb/json/ScalaSig.scala | 1 + .../src/test/scala/net/liftweb/json/ExtractionBugs.scala | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/core/json/src/main/scala/net/liftweb/json/ScalaSig.scala b/core/json/src/main/scala/net/liftweb/json/ScalaSig.scala index c3e8e76867..d7adc5dfe5 100644 --- a/core/json/src/main/scala/net/liftweb/json/ScalaSig.scala +++ b/core/json/src/main/scala/net/liftweb/json/ScalaSig.scala @@ -65,6 +65,7 @@ private[json] object ScalaSigReader { case TypeRefType(ThisType(_), symbol, _) if isPrimitive(symbol) => symbol case TypeRefType(_, _, TypeRefType(ThisType(_), symbol, _) :: xs) => symbol case TypeRefType(_, symbol, Nil) => symbol + case TypeRefType(_, _, args) if typeArgIndex >= args.length => findPrimitive(args(0)) case TypeRefType(_, _, args) => args(typeArgIndex) match { case ref @ TypeRefType(_, _, _) => findPrimitive(ref) diff --git a/core/json/src/test/scala/net/liftweb/json/ExtractionBugs.scala b/core/json/src/test/scala/net/liftweb/json/ExtractionBugs.scala index 3b42d0df21..2712bdaa0e 100644 --- a/core/json/src/test/scala/net/liftweb/json/ExtractionBugs.scala +++ b/core/json/src/test/scala/net/liftweb/json/ExtractionBugs.scala @@ -52,6 +52,13 @@ object ExtractionBugs extends Specification("Extraction bugs Specification") { parse("""{"nums":[10]}""").extract[HasCompanion] mustEqual HasCompanion(List(10)) } + "Issue 1169" in { + val json = JsonParser.parse("""{"data":[{"one":1, "two":2}]}""") + json.extract[Response] mustEqual Response(List(Map("one" -> 1, "two" -> 2))) + } + + case class Response(data: List[Map[String, Int]]) + case class OptionOfInt(opt: Option[Int]) case class PMap(m: Map[String, List[String]]) From 7cec2f4e54d4b2b4ca0826070be782847a63c423 Mon Sep 17 00:00:00 2001 From: David Pollak Date: Mon, 27 Feb 2012 21:13:05 -0800 Subject: [PATCH 0009/1949] Addresses #1193. correct garbage collection of Ajax-created functions --- .../scala/net/liftweb/http/LiftServlet.scala | 118 +++++++++--------- .../scala/net/liftweb/http/LiftSession.scala | 17 +-- 2 files changed, 69 insertions(+), 66 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala index 0bc89b42c7..d561cd4020 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala @@ -429,74 +429,76 @@ class LiftServlet extends Loggable { requestState: Req): Box[LiftResponse] = { extractVersion(requestState.path.partPath) { - LiftRules.cometLogger.debug("AJAX Request: " + liftSession.uniqueId + " " + requestState.params) - tryo { - LiftSession.onBeginServicing.foreach(_(liftSession, requestState)) - } - - val ret = try { - requestState.param("__lift__GC") match { - case Full(_) => - liftSession.updateFuncByOwner(RenderVersion.get, millis) - Full(JavaScriptResponse(js.JsCmds.Noop)) + LiftRules.cometLogger.debug("AJAX Request: " + liftSession.uniqueId + " " + requestState.params) + tryo { + LiftSession.onBeginServicing.foreach(_(liftSession, requestState)) + } - case _ => - try { - val what = flatten(try { - liftSession.runParams(requestState) - } catch { - case ResponseShortcutException(_, Full(to), _) => - import js.JsCmds._ - List(RedirectTo(to)) - }) - - val what2 = what.flatMap { - case js: JsCmd => List(js) - case jv: JValue => List(jv) - case n: NodeSeq => List(n) - case js: JsCommands => List(js) - case r: LiftResponse => List(r) - case s => Nil - } + val ret = try { + requestState.param("__lift__GC") match { + case Full(_) => + liftSession.updateFuncByOwner(RenderVersion.get, millis) + Full(JavaScriptResponse(js.JsCmds.Noop)) - val ret: LiftResponse = what2 match { - case (json: JsObj) :: Nil => JsonResponse(json) - case (jv: JValue) :: Nil => JsonResponse(jv) - case (js: JsCmd) :: xs => { - (JsCommands(S.noticesToJsCmd :: Nil) & - ((js :: xs).flatMap { - case js: JsCmd => List(js) - case _ => Nil - }.reverse) & - S.jsToAppend).toResponse + case _ => + try { + val what = flatten(try { + liftSession.runParams(requestState) + } catch { + case ResponseShortcutException(_, Full(to), _) => + import js.JsCmds._ + List(RedirectTo(to)) + }) + + val what2 = what.flatMap { + case js: JsCmd => List(js) + case jv: JValue => List(jv) + case n: NodeSeq => List(n) + case js: JsCommands => List(js) + case r: LiftResponse => List(r) + case s => Nil } - case (n: Node) :: _ => XmlResponse(n) - case (ns: NodeSeq) :: _ => XmlResponse(Group(ns)) - case (r: LiftResponse) :: _ => r - case _ => JsCommands(S.noticesToJsCmd :: JsCmds.Noop :: S.jsToAppend).toResponse - } + val ret: LiftResponse = what2 match { + case (json: JsObj) :: Nil => JsonResponse(json) + case (jv: JValue) :: Nil => JsonResponse(jv) + case (js: JsCmd) :: xs => { + (JsCommands(S.noticesToJsCmd :: Nil) & + ((js :: xs).flatMap { + case js: JsCmd => List(js) + case _ => Nil + }.reverse) & + S.jsToAppend).toResponse + } - LiftRules.cometLogger.debug("AJAX Response: " + liftSession.uniqueId + " " + ret) + case (n: Node) :: _ => XmlResponse(n) + case (ns: NodeSeq) :: _ => XmlResponse(Group(ns)) + case (r: LiftResponse) :: _ => r + case _ => JsCommands(S.noticesToJsCmd :: JsCmds.Noop :: S.jsToAppend).toResponse + } - Full(ret) - } finally { - println("Hey Ajax boy... upding function map "+S.functionMap) - liftSession.updateFunctionMap(S.functionMap, RenderVersion.get, millis) - } + LiftRules.cometLogger.debug("AJAX Response: " + liftSession.uniqueId + " " + ret) + + Full(ret) + } finally { + if (S.functionMap.size > 0) { + liftSession.updateFunctionMap(S.functionMap, RenderVersion.get, millis) + S.clearFunctionMap + } + } + } + } catch { + case foc: LiftFlowOfControlException => throw foc + case e => NamedPF.applyBox((Props.mode, requestState, e), LiftRules.exceptionHandler.toList); } - } catch { - case foc: LiftFlowOfControlException => throw foc - case e => NamedPF.applyBox((Props.mode, requestState, e), LiftRules.exceptionHandler.toList); - } - tryo { - LiftSession.onEndServicing.foreach(_(liftSession, requestState, ret)) - } - ret + tryo { + LiftSession.onEndServicing.foreach(_(liftSession, requestState, ret)) + } + ret } } - /** +/** * An actor that manages continuations from container (Jetty style) */ class ContinuationActor(request: Req, session: LiftSession, diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index 61cfec4b8a..0b498c77ea 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -400,12 +400,16 @@ private[http] object RenderVersion { func <- sess.findFunc(v).collect { case f: S.PageStateHolder => f } - } yield { - val tret = ver.doWith(v)(func.runInContext(f)) + } yield { + val tret = ver.doWith(v) { + val ret = func.runInContext(f) - if (S.functionMap.size > 0) { - sess.updateFunctionMap(S.functionMap, this.get, millis) - S.clearFunctionMap + + if (S.functionMap.size > 0) { + sess.updateFunctionMap(S.functionMap, this.get, millis) + S.clearFunctionMap + } + ret } tret } @@ -695,9 +699,6 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, * Updates the internal functions mapping */ def updateFunctionMap(funcs: Map[String, S.AFuncHolder], uniqueId: String, when: Long): Unit = synchronized { - println("Updating function map for unique id "+uniqueId+" Map "+funcs) - Thread.dumpStack() - funcs.foreach { case (name, func) => messageCallback(name) = From 43ee3440f5df21739ef6d8c25dd647c5d28f8090 Mon Sep 17 00:00:00 2001 From: Dave Whittaker Date: Tue, 20 Dec 2011 14:15:27 -0500 Subject: [PATCH 0010/1949] Wrap db access in inTrasaction{} blocks so that a new transaction is started if one does not already exist. --- .../net/liftweb/squerylrecord/CRUDify.scala | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/persistence/squeryl-record/src/main/scala/net/liftweb/squerylrecord/CRUDify.scala b/persistence/squeryl-record/src/main/scala/net/liftweb/squerylrecord/CRUDify.scala index 03d5c51175..57a64f2cbe 100644 --- a/persistence/squeryl-record/src/main/scala/net/liftweb/squerylrecord/CRUDify.scala +++ b/persistence/squeryl-record/src/main/scala/net/liftweb/squerylrecord/CRUDify.scala @@ -42,11 +42,15 @@ trait CRUDify[K, T <: Record[T] with KeyedEntity[K]] extends Crudify { override def computeFieldFromPointer(instance: TheCrudType, pointer: FieldPointerType): Box[FieldPointerType] = instance.fieldByName(pointer.name) - override def findForParam(in: String): Box[TheCrudType] = { - table.lookup(idFromString(in)) - } + override def findForParam(in: String): Box[TheCrudType] = + inTransaction{ + table.lookup(idFromString(in)) + } - override def findForList(start: Long, count: Int) = from(table)(t => select(t)).page(start.toInt, count).toList + override def findForList(start: Long, count: Int) = + inTransaction{ + from(table)(t => select(t)).page(start.toInt, count).toList + } override def create = createRecord @@ -58,10 +62,14 @@ trait CRUDify[K, T <: Record[T] with KeyedEntity[K]] extends Crudify { def save = { if (in.isPersisted) { - table.update(in) + inTransaction{ + table.update(in) + } } else { - table.insert(in) + inTransaction { + table.insert(in) + } } true } From 1ae400ac6b3cd3ff14bfd849a486910b2a6caa51 Mon Sep 17 00:00:00 2001 From: Tim Nelson Date: Fri, 24 Feb 2012 14:54:57 -0600 Subject: [PATCH 0011/1949] Issue 1179 - Invalidate cached object in MongoRefField upon setting the value --- .../mongodb/record/field/MongoRefField.scala | 18 ++++++++++++---- .../record/field/MongoRefListField.scala | 12 ++++++++++- .../mongodb/record/MongoFieldSpec.scala | 1 - .../mongodb/record/MongoRecordSpec.scala | 21 +++++++++++++++++++ 4 files changed, 46 insertions(+), 6 deletions(-) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoRefField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoRefField.scala index 8aadb8db3f..c268250e1d 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoRefField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoRefField.scala @@ -42,13 +42,18 @@ trait MongoRefField[RefType <: MongoRecord[RefType], MyType] extends TypedField[ /** The MongoMetaRecord of the referenced object **/ def refMeta: MongoMetaRecord[RefType] - /* - * get the referenced object - */ + /** + * Find the referenced object + */ + def find = valueBox.flatMap(v => refMeta.findAny(v)) + + /** + * Get the cacheable referenced object + */ def obj = synchronized { if (!_calcedObj) { _calcedObj = true - this._obj = valueBox.flatMap(v => refMeta.findAny(v)) + this._obj = find } _obj } @@ -63,6 +68,11 @@ trait MongoRefField[RefType <: MongoRecord[RefType], MyType] extends TypedField[ private var _obj: Box[RefType] = Empty private var _calcedObj = false + override def setBox(in: Box[MyType]): Box[MyType] = synchronized { + _calcedObj = false // invalidate the cache + super.setBox(in) + } + /** Options for select list **/ def options: List[(Box[MyType], String)] = Nil diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoRefListField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoRefListField.scala index c8abb1e34d..41028f9398 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoRefListField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoRefListField.scala @@ -41,13 +41,18 @@ abstract class MongoRefListField[OwnerType <: BsonRecord[OwnerType], RefType <: /** The MongoMetaRecord of the referenced object **/ def refMeta: MongoMetaRecord[RefType] + /** + * Find the referenced objects + */ + def findAll = refMeta.findAllByList(this.value) + /* * get the referenced objects */ def objs = synchronized { if (!_calcedObjs) { _calcedObjs = true - this._objs = refMeta.findAllByList(this.value) + this._objs = findAll } _objs } @@ -61,6 +66,11 @@ abstract class MongoRefListField[OwnerType <: BsonRecord[OwnerType], RefType <: private var _objs: List[RefType] = Nil private var _calcedObjs = false + + override def setBox(in: Box[MyType]): Box[MyType] = synchronized { + _calcedObjs = false // invalidate the cache + super.setBox(in) + } } class ObjectIdRefListField[OwnerType <: BsonRecord[OwnerType], RefType <: MongoRecord[RefType]]( diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala index e28664fd58..42fe833769 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala @@ -21,7 +21,6 @@ package record import java.util.{Date, UUID} import java.util.regex.Pattern -import com.mongodb.DBRef import org.bson.types.ObjectId import org.specs.Specification diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala index 657a093988..035fcfc908 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala @@ -548,17 +548,38 @@ object MongoRecordSpec extends Specification("MongoRecord Specification") with M .mandatoryIntRefListField(List(ntr.id.is)) .mandatoryLongRefListField(List(btr.id.is)) + // single objects rftr.mandatoryObjectIdRefField.obj mustEqual Full(fttr) rftr.mandatoryUUIDRefField.obj mustEqual Full(ltr) rftr.mandatoryStringRefField.obj mustEqual Full(mtr) rftr.mandatoryIntRefField.obj mustEqual Full(ntr) rftr.mandatoryLongRefField.obj mustEqual Full(btr) + val fttr2 = FieldTypeTestRecord.createRecord.save + + rftr.mandatoryObjectIdRefField.cached_? mustEqual true + rftr.mandatoryObjectIdRefField(fttr2.id.is) + rftr.mandatoryObjectIdRefField.cached_? mustEqual false + rftr.mandatoryObjectIdRefField.find mustEqual Full(fttr2) + rftr.mandatoryObjectIdRefField.obj mustEqual Full(fttr2) + rftr.mandatoryObjectIdRefField.cached_? mustEqual true + + // lists rftr.mandatoryObjectIdRefListField.objs mustEqual List(fttr) rftr.mandatoryUUIDRefListField.objs mustEqual List(ltr) rftr.mandatoryStringRefListField.objs mustEqual List(mtr) rftr.mandatoryIntRefListField.objs mustEqual List(ntr) rftr.mandatoryLongRefListField.objs mustEqual List(btr) + + val fttr3 = FieldTypeTestRecord.createRecord.save + val objList = List(fttr2, fttr3) + + rftr.mandatoryObjectIdRefListField.cached_? mustEqual true + rftr.mandatoryObjectIdRefListField(objList.map(_.id.is)) + rftr.mandatoryObjectIdRefListField.cached_? mustEqual false + rftr.mandatoryObjectIdRefListField.findAll mustEqual objList + rftr.mandatoryObjectIdRefListField.objs mustEqual objList + rftr.mandatoryObjectIdRefListField.cached_? mustEqual true } "use defaultValue when field is not present in the database" in { From d505b3651887b27f53095bc1ef1a78506f4b4fe4 Mon Sep 17 00:00:00 2001 From: Indrajit Raychaudhuri Date: Wed, 22 Feb 2012 18:44:00 +0530 Subject: [PATCH 0012/1949] Move to SBT 0.11.2 --- build.sbt | 19 ++ liftsh | 2 +- liftsh.cmd | 2 +- .../mongodb/record/MongoFieldSpec.scala | 2 +- project/Build.scala | 197 ++++++++++++++++++ project/Dependencies.scala | 84 ++++++++ project/build.properties | 10 +- project/build/LiftFrameworkProject.scala | 115 ---------- project/plugins/Plugins.scala | 10 - project/project/Plugins.scala | 7 + project/sbt-launch-0.11.2.jar | Bin 0 -> 1041753 bytes project/sbt-launch-0.7.7.jar | Bin 952175 -> 0 bytes 12 files changed, 311 insertions(+), 137 deletions(-) create mode 100644 build.sbt create mode 100644 project/Build.scala create mode 100644 project/Dependencies.scala delete mode 100644 project/build/LiftFrameworkProject.scala delete mode 100644 project/plugins/Plugins.scala create mode 100644 project/project/Plugins.scala create mode 100644 project/sbt-launch-0.11.2.jar delete mode 100644 project/sbt-launch-0.7.7.jar diff --git a/build.sbt b/build.sbt new file mode 100644 index 0000000000..15a5686419 --- /dev/null +++ b/build.sbt @@ -0,0 +1,19 @@ +import Dependencies._ + +version in ThisBuild := "2.5-SNAPSHOT" + +crossScalaVersions in ThisBuild := Seq("2.9.1", "2.9.0-1", "2.9.0", /*"2.8.2", */"2.8.1", "2.8.0") + +libraryDependencies in ThisBuild <++= scalaVersion { sv => Seq(/*specs2, */specs(sv), scalacheck(sv)) } + +pomExtra in ThisBuild ~= { _ ++ ( + + http://github.com/lift/framework + scm:git:git@github.com:lift/framework.git + + + + indrajitr + Indrajit Raychaudhuri + + )} diff --git a/liftsh b/liftsh index 31488f642a..f7d861f971 100755 --- a/liftsh +++ b/liftsh @@ -14,4 +14,4 @@ DEFAULT_OPTS="-Dsbt.intransitive=true -Djava.util.logging.config.file=logging.pr cd `dirname $0` # Call with INTERNAL_OPTS followed by LIFTSH_OPTS (or DEFAULT_OPTS). java aways takes the last option when duplicate. -exec java ${INTERNAL_OPTS} ${LIFTSH_OPTS:-${DEFAULT_OPTS}} -jar project/sbt-launch-0.7.7.jar "$@" +exec java ${INTERNAL_OPTS} ${LIFTSH_OPTS:-${DEFAULT_OPTS}} -jar project/sbt-launch-0.11.2.jar "$@" diff --git a/liftsh.cmd b/liftsh.cmd index 7aeda5610f..c5b8f80374 100644 --- a/liftsh.cmd +++ b/liftsh.cmd @@ -11,4 +11,4 @@ if "%LIFTSH_OPTS%"=="" ( ) @REM Call with INTERNAL_OPTS followed by LIFTSH_OPTS (or DEFAULT_OPTS). java aways takes the last option when duplicate. -java %INTERNAL_OPTS% %LIFTSH_OPTS% -jar "%~dp0\project\sbt-launch-0.7.7.jar" %* +java %INTERNAL_OPTS% %LIFTSH_OPTS% -jar "%~dp0\project\sbt-launch-0.11.2.jar" %* diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala index 42fe833769..98babc7d28 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala @@ -25,7 +25,7 @@ import org.bson.types.ObjectId import org.specs.Specification import common._ -import json.{Num => _, _} +import json._ import BsonDSL._ import util.Helpers.randomString import http.{LiftSession, S} diff --git a/project/Build.scala b/project/Build.scala new file mode 100644 index 0000000000..22e44e6f4f --- /dev/null +++ b/project/Build.scala @@ -0,0 +1,197 @@ +/* + * Copyright 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. + */ + +import java.util.{Calendar => Cal} +import sbt._ +import Keys._ +import net.liftweb.sbt._ +import Dependencies._ + + +object BuildDef extends Build { + + lazy val liftProjects = core ++ web ++ persistence + + lazy val framework = + liftProject("lift-framework", file(".")) + .aggregate(liftProjects: _*) + .settings(publishArtifact := false) + + // Core Projects + // ------------- + lazy val core: Seq[ProjectReference] = + Seq(common, actor, json, json_scalaz, json_ext, util) + + lazy val common = + coreProject("common") + .settings( + description := "Common Libraties and Utilities", + libraryDependencies ++= Seq(slf4j_api, logback, slf4j_log4j12, commons_codec)) + + lazy val actor = + coreProject("actor") + .dependsOn(common) + .settings( + description := "Simple Actor") + + lazy val json = + coreProject("json") + .settings( + description := "JSON Library", + libraryDependencies <++= scalaVersion { sv => Seq(scalap(sv), paranamer) }) + + lazy val json_scalaz = + coreProject("json-scalaz") + .dependsOn(json) + .settings( + description := "JSON Library based on Scalaz", + libraryDependencies <+= scalaVersion(scalaz)) + + lazy val json_ext = + coreProject("json-ext") + .dependsOn(common, json) + .settings( + description := "Extentions to JSON Library", + libraryDependencies ++= Seq(commons_codec, joda_time)) + + lazy val util = + coreProject("util") + .dependsOn(actor, json) + .settings( + description := "Utilities Library", + parallelExecution in Test := false, + libraryDependencies ++= Seq(joda_time, commons_codec, javamail, log4j, htmlparser)) + + + // Web Projects + // ------------ + lazy val web: Seq[ProjectReference] = + Seq(testkit, webkit, wizard) + + lazy val testkit = + webProject("testkit") + .dependsOn(util) + .settings( + description := "Testkit for Webkit Library", + libraryDependencies ++= Seq(commons_httpclient, servlet_api)) + + lazy val webkit = + webProject("webkit") + .dependsOn(util, testkit % "provided") + .settings( + description := "Webkit Library", + packageOptions in packageBin += Package.ManifestAttributes("Build-Time" -> Cal.getInstance.getTimeInMillis.toString), + parallelExecution in Test := false, + libraryDependencies <++= scalaVersion { sv => + Seq(commons_fileupload, servlet_api, specs(sv).copy(configurations = Some("provided")), jetty6, jwebunit) + }, + initialize in Test <<= (sourceDirectory in Test) { src => + System.setProperty("net.liftweb.webapptest.src.test.webapp", (src / "webapp").absString) + }) + + lazy val wizard = + webProject("wizard") + .dependsOn(webkit, db) + .settings( + description := "Wizard Library") + + + // Persistence Projects + // -------------------- + lazy val persistence: Seq[ProjectReference] = + Seq(db, proto, jpa, mapper, record, couchdb, squeryl_record, mongodb, mongodb_record, ldap) + + lazy val db = + persistenceProject("db") + .dependsOn(util) + + lazy val proto = + persistenceProject("proto") + .dependsOn(webkit) + + lazy val jpa = + persistenceProject("jpa") + .dependsOn(webkit) + .settings(libraryDependencies ++= Seq(scalajpa, persistence_api)) + + lazy val mapper = + persistenceProject("mapper") + .dependsOn(db, proto) + .settings( + description := "Mapper Library", + parallelExecution in Test := false, + libraryDependencies ++= Seq(h2, derby), + initialize in Test <<= (crossTarget in Test) { ct => + System.setProperty("derby.stream.error.file", (ct / "derby.log").absolutePath) + }) + + lazy val record = + persistenceProject("record") + .dependsOn(proto, db) + + lazy val couchdb = + persistenceProject("couchdb") + .dependsOn(record) + .settings( + libraryDependencies += dispatch_http) + + lazy val squeryl_record = + persistenceProject("squeryl-record") + .dependsOn(record, db) + .settings(libraryDependencies ++= Seq(h2, squeryl)) + + lazy val mongodb = + persistenceProject("mongodb") + .dependsOn(json_ext) + .settings( + parallelExecution in Test := false, + libraryDependencies += mongo_driver) + + lazy val mongodb_record = + persistenceProject("mongodb-record") + .dependsOn(record, mongodb) + .settings(parallelExecution in Test := false) + + lazy val ldap = + persistenceProject("ldap") + .dependsOn(mapper) + .settings( + libraryDependencies += apacheds, + initialize in Test <<= (crossTarget in Test) { ct => + System.setProperty("apacheds.working.dir", (ct / "apacheds").absolutePath) + }) + + def coreProject = liftProject("core") _ + def webProject = liftProject("web") _ + def persistenceProject = liftProject("persistence") _ + + /** Project definition helper that simplifies creation of `ProjectReference`. + * + * It is a convenience method to create a Lift `ProjectReference` module by having the boilerplate for most common + * activities tucked in. + * + * @param base the base path location of project module. + * @param prefix the prefix of project module. + * @param module the name of the project module. Typically, a project id is of the form lift-`module`. + */ + def liftProject(base: String, prefix: String = "lift-")(module: String): Project = + liftProject( + id = if(module.startsWith(prefix)) module else prefix + module, + base = file(base) / module.stripPrefix(prefix)) + + def liftProject(id: String, base: File): Project = + Project(id, base).settings(LiftBuildPlugin.liftBuildSettings: _*) +} diff --git a/project/Dependencies.scala b/project/Dependencies.scala new file mode 100644 index 0000000000..00ae817f47 --- /dev/null +++ b/project/Dependencies.scala @@ -0,0 +1,84 @@ +/* + * Copyright 2011 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 sbt._ +import Keys._ +import net.liftweb.sbt.LiftBuildPlugin.selectDynamic + + +object Dependencies { + + lazy val slf4jVersion = "1.6.4" + + lazy val scalacheckVersion = selectDynamic("1.9", "2.8.0" -> "1.7", "2.8.1" -> "1.8", "2.8.2" -> "1.8") _ + lazy val specsVersion = selectDynamic("1.6.8", "2.8.0" -> "1.6.5", "2.9.1" -> "1.6.9") _ + + lazy val scalazGroup = selectDynamic("org.scalaz", "2.8.0" -> "com.googlecode.scalaz") _ + lazy val scalazVersion = selectDynamic("6.0.2", "2.8.0" -> "5.0", "2.9.0" -> "6.0.RC2") _ + + type DynModuleID = String => ModuleID + + // Compile scope: + // Scope available in all classpath, transitive by default. + lazy val commons_codec = "commons-codec" % "commons-codec" % "1.4" + lazy val commons_fileupload = "commons-fileupload" % "commons-fileupload" % "1.2.2" + lazy val commons_httpclient = "commons-httpclient" % "commons-httpclient" % "3.1" + lazy val dispatch_http = "net.databinder" %% "dispatch-http" % "0.7.8" + lazy val javamail = "javax.mail" % "mail" % "1.4.4" // TODO: "[1.4.1,)" + lazy val joda_time = "joda-time" % "joda-time" % "1.6.2" + lazy val htmlparser = "nu.validator.htmlparser" % "htmlparser" % "1.2.1" + lazy val mongo_java_driver = "org.mongodb" % "mongo-java-driver" % "2.6.5" + lazy val paranamer = "com.thoughtworks.paranamer" % "paranamer" % "2.4.1" + lazy val scalajpa = "org.scala-libs" %% "scalajpa" % "1.4" + lazy val scalap: DynModuleID = "org.scala-lang" % "scalap" % _ + lazy val scalaz_core: DynModuleID = sv => scalazGroup(sv) %% "scalaz-core" % scalazVersion(sv) + lazy val slf4j_api = "org.slf4j" % "slf4j-api" % slf4jVersion + lazy val squeryl = "org.squeryl" %% "squeryl" % "0.9.4" + + // Aliases + lazy val mongo_driver = mongo_java_driver + lazy val scalaz = scalaz_core + + + // Provided scope: + // Scope provided by container, available only in compile and test classpath, non-transitive by default. + lazy val logback = "ch.qos.logback" % "logback-classic" % "1.0.0" % "provided" + lazy val log4j = "log4j" % "log4j" % "1.2.16" % "provided" + lazy val slf4j_log4j12 = "org.slf4j" % "slf4j-log4j12" % slf4jVersion % "provided" + lazy val persistence_api = "javax.persistence" % "persistence-api" % "1.0" % "provided" + lazy val servlet_api = "javax.servlet" % "servlet-api" % "2.5" % "provided" + + + // Runtime scope: + // Scope provided in runtime, available only in runtime and test classpath, not compile classpath, non-transitive by default. + lazy val derby = "org.apache.derby" % "derby" % "10.7.1.1" % "runtime" //% "optional" + lazy val h2database = "com.h2database" % "h2" % "1.2.147" % "runtime" //% "optional" + + // Aliases + lazy val h2 = h2database + + + // Test scope: + // Scope available only in test classpath, non-transitive by default. + // TODO: See if something alternative with lesser footprint can be used instead of mega heavy apacheds + lazy val apacheds = "org.apache.directory.server" % "apacheds-server-integ" % "1.5.5" % "test" + lazy val jetty6 = "org.mortbay.jetty" % "jetty" % "6.1.26" % "test" + lazy val jwebunit = "net.sourceforge.jwebunit" % "jwebunit-htmlunit-plugin" % "2.5" % "test" + lazy val scalacheck: DynModuleID = "org.scala-tools.testing" %% "scalacheck" % scalacheckVersion(_) % "test" + lazy val specs: DynModuleID = "org.scala-tools.testing" %% "specs" % specsVersion(_) % "test" + lazy val specs2 = "org.specs2" %% "specs2" % "1.5" % "test" + +} diff --git a/project/build.properties b/project/build.properties index fb32670144..f4ff7a5afa 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1,9 +1 @@ -# -#Fri Jan 27 12:14:38 PST 2012 -project.name=lift-framework -project.organization=net.liftweb -project.version=2.5-SNAPSHOT -sbt.version=0.7.7 -def.scala.version=2.7.7 -build.scala.versions=2.9.1 2.8.1 2.8.0 2.9.0-1 2.9.0 -project.initialize=false +sbt.version=0.11.2 diff --git a/project/build/LiftFrameworkProject.scala b/project/build/LiftFrameworkProject.scala deleted file mode 100644 index 6b38fb7710..0000000000 --- a/project/build/LiftFrameworkProject.scala +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2010-2011 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 java.util.Calendar -import java.util.jar.Attributes.Name -import com.weiglewilczek.bnd4sbt.BNDPlugin -import net.liftweb.sbt._ -import sbt._ - - -class LiftFrameworkProject(info: ProjectInfo) extends ParentProject(info) with LiftParentProject { - - import CompileScope._ - import ProvidedScope._ - - - // Core projects - // ------------- - lazy val common = coreProject("common", slf4j_api, logback, log4j)() - lazy val actor = coreProject("actor")(common) - lazy val json = coreProject("json", paranamer, scalap)() - lazy val json_scalaz = coreProject("json-scalaz", scalaz)(json) - lazy val json_ext = coreProject("json-ext", commons_codec, joda_time)(common, json) - lazy val util = coreProject("util", joda_time, commons_codec, javamail, logback, log4j, htmlparser)(actor, json) - - - // Web projects - // ------------ - lazy val testkit = webProject("testkit", commons_httpclient, servlet_api)(util) - lazy val webkit = webkitProject("webkit", commons_fileupload, servlet_api, TestScope.jetty6, TestScope.jwebunit)(util, testkit) - lazy val wizard = webProject("wizard")(webkit, db) - - - // Persistence projects - // -------------------- - lazy val db = persistenceProject("db",logback,TestScope.mockito_all)(util) - lazy val proto = persistenceProject("proto")(webkit) - lazy val jpa = persistenceProject("jpa", scalajpa, persistence_api)(webkit) - lazy val mapper = persistenceProject("mapper", RuntimeScope.h2database, RuntimeScope.derby)(db, proto) - lazy val record = persistenceProject("record")(proto, db) // db to be removed in v 2.5 (ticket 997) - lazy val ldap = persistenceProject("ldap", TestScope.apacheds)(mapper) - lazy val couchdb = persistenceProject("couchdb", dispatch_http)(record) - lazy val squeryl_record = persistenceProject("squeryl-record", RuntimeScope.h2database, squeryl)(record, db) - lazy val mongodb = persistenceProject("mongodb", mongo_driver)(json_ext) - lazy val mongodb_record = persistenceProject("mongodb-record")(record, mongodb) - - - // Framework apidocs - // ----------------- - lazy val framework_doc = project(".", "lift-framework-doc", new DefaultProject(_) with LiftDefaultDocProject) - - - private def coreProject = frameworkProject("core") _ - private def webProject = frameworkProject("web") _ - private def persistenceProject = frameworkProject("persistence") _ - - private def frameworkProject(base: String)(path: String, libs: ModuleID*)(deps: Project*) = - project(base / path, "lift-" + path, new FrameworkProject(_, libs: _*), deps: _*) - - // Webkit Project has testkit dependency in non-default scope -- needs special treatment - // so that it doesn't fall over. - // Ref: http://groups.google.com/group/simple-build-tool/browse_thread/thread/40d8ecafbf03f901 - private def webkitProject(path: String, libs: ModuleID*)(deps: Project*) = - project("web" / path, "lift-" + path, new FrameworkProject(_, libs: _*) { - - // Specs needed in 'provided' scope, this will lead to duplications in testclasspath though - override def libraryDependencies = - super.libraryDependencies ++ Seq("org.scala-tools.testing" % specsArtifact % specsVersion % "provided") - - // Move testkit dependency from 'compile' (default) to 'provided' scope - override def deliverProjectDependencies = - testkit.projectID % "provided" :: super.deliverProjectDependencies.toList - testkit.projectID - - // System properties necessary during test - System.setProperty("net.liftweb.webapptest.src.test.webapp", (testSourcePath / "webapp").absString) - }, deps: _*) - - - // Default base - // ------------ - class FrameworkProject(info: ProjectInfo, libs: ModuleID*) extends DefaultProject(info) with BNDPlugin with LiftDefaultProject { - - override def libraryDependencies = super.libraryDependencies ++ libs - - override def packageOptions = - ManifestAttributes((new Name("Build-Time"), Calendar.getInstance.getTimeInMillis.toString)) :: super.packageOptions.toList - - // FIXME: Build fails with -Xcheckinit -Xwarninit - override def compileOptions = super.compileOptions.toList -- compileOptions("-Xcheckinit", "-Xwarninit").toList - - // OSGi Attributes - override def bndExportPackage = Seq("net.liftweb.*;version=\"%s\"".format(projectVersion.value)) - override def bndImportPackage = "net.liftweb.*;version=\"%s\"".format(projectVersion.value) :: super.bndImportPackage.toList - - // BNDPlugin should include mainResourcesOutputPath too, to include the generated resources - override def bndIncludeResource = super.bndIncludeResource ++ Seq(mainResourcesOutputPath.absolutePath) - - // System properties necessary during test TODO: Figure out how to make this a subdir of persistence/ldap/ - System.setProperty("apacheds.working.dir", (outputPath / "apacheds").absolutePath) - } - -} diff --git a/project/plugins/Plugins.scala b/project/plugins/Plugins.scala deleted file mode 100644 index 29261f351a..0000000000 --- a/project/plugins/Plugins.scala +++ /dev/null @@ -1,10 +0,0 @@ -import sbt._ -class Plugins(info: ProjectInfo) extends PluginDefinition(info) { - - // Add ScalaToolsSnapshots - lazy val snapshots = ScalaToolsSnapshots - - // Add plugin - lazy val liftsbt = "net.liftweb" % "lift-sbt" % "2.5-SNAPSHOT" - lazy val bnd4sbt = "com.weiglewilczek.bnd4sbt" % "bnd4sbt" % "1.0.2" -} diff --git a/project/project/Plugins.scala b/project/project/Plugins.scala new file mode 100644 index 0000000000..a9adf85bbe --- /dev/null +++ b/project/project/Plugins.scala @@ -0,0 +1,7 @@ +import sbt._ + +object PluginDef extends Build { + lazy val root = Project("plugins", file(".")) dependsOn(buildPlugin, gpgPlugin) + lazy val buildPlugin = uri("git://github.com/indrajitr/sbt-lift-build-plugin") + lazy val gpgPlugin = uri("git://github.com/sbt/xsbt-gpg-plugin") +} diff --git a/project/sbt-launch-0.11.2.jar b/project/sbt-launch-0.11.2.jar new file mode 100644 index 0000000000000000000000000000000000000000..84fd9b7b62926f4f3f9db1864fe5c0d534cdb073 GIT binary patch literal 1041753 zcmbrl1yEdV*CiS{KyY^r?(Ul4?lkW1?yikPaEIWo!JWpP;7;QZT!I99soWl%YA~Ao0-j^ z{(A|^zlZX_y$QkJUmaa6S&W^GO|8sXtUbJ0WIVhn*_ci3j9pz*)$P^hzoG@8SgDDL zi}&Fgh(pUj5q?E0(Mkm%q~R9u@3tz(qcO@z*AZ8}5B5SnMX&__#Q*ZApY)(@8E1%G zacc5>^FqkCvvtP2_xK})5cK=i~m zNVZ5<0JAh=W}>W{2?(yJ(j z)0g%xU+f($tkv{AZv#Lx>#FeCc_X<@Ajzbv9$OW67qi1>Zw@)c#oa>D522E>bXFb5 zGKQ*ZwLnHhnr#*rI+|=cw6g7C$(KrMN-uz& zt2(;PH6G1Xn~Sbn+4vd8)DllUd=Fc$@4gr94=)cJ6TWwGz84?sb*is-saY;=cX16~ zrWpJT$b2P!Xwn)q=zx1EUv$BNlrNg#cgilZ9R-=M7@`=il!-L(36wWfY)TjsNwjI= zQIv;rV3sJDBrV#gzh9(^XHw$GnM6^JQ>lqwUv$ z=Kfe8`{{}c=UJP`!WlKj*W3?rJN4S0DDhfMe)JK&hi-4=5fppmHK=s;5r1aS0sqUu z0K0+FW{j8ku$-6r@WjTo+y>d6W*cz0?iz^SJH*5uknoZHEppwg3*^uK7RxgB$Z(N# z?Mak!&7YTejq31*X_z7b)ib^OjgeB2A*Q`e8z=lRgfYJVys@18wvZrUtPn&^gPuO* zvb4DCti7-UuU*}j+^7{*W~P$htX(ZCkAwV}$4GG9#F*t2^8tGNmuB;&x)e}?C#A)S z(&k~Mv@|}4vYvGnQ*}UWyyY!dXEv{Zx`~8@++9YMc**Xd`ocq%NwKo9g+!H`umDYa zHjdRjwbZ?NHLGF`Bt!hx6z{Rwh-o!Iq`DAEK(vJA?HgSjI*yV$-sK3b8*vw&INsJS`KW_DBa_(dbeGzO2BVKyFHa`={JcMp^{=$@lmTd&a7^u7FX%lEo( z{A1U(o2S6x!yd(jc*teOlN9bHXg>0D)+`-06o>kXk+=VzdD?-6Az2M8w`O2f#xbL1 zPK>AG>MYQ{f=_(1JZV79do3^_b8jiYBd1U0 zs($iQikz%QqPdv~#8=2)Jk`PXQAe7TDxkT|oL*uoYo{Gas_>O;lR%?mgWa50;A82z zhiBz4MK;mo5=qN^22Bh{9|0^XzuJ_#tbXH?5jxeCzV&vJUl#7wjb}c)AY{~EVk#Z7 zCrLsvoEqGQ2`<}@KC$Ka3$zhv`Ox~*62JQTnkFgggRn9=X;7*XBXLMRqdc7I?LQEz zFw0p@Lu$EYBTDh(RtPE(Lgc4(f^Awwrx!b>7n59@&-CoUe2th$_-Vr)7!30|O(I+b zjn<`5yVZSN!6$xL-TX)NCV+FA94c?A1N}lR&_#d2O$!dV48u+Gv87>`i?~ z3lCN%=tCFswQ5Q45jnc%RU6uUe)UdC{nocZ)8+u3I){>>`6`+%hrzgoPE*}eZxJRF zfn=7^8-fC%u~8rZh@Yi6umxt!vZ!=Hn6u{nj8KTku5kaI^0r+TaqP{mfGO_{@4Mx^ zy5gCtf?PJYz4wKiOx=zhs$TTK1(i9yZI7N zcwhADCMI93MK=|+qTh(8c)WMx7MfdbSKPCR(E+35r}4cX-GtC-GrJbwvl)*Vh7_pB zsTb!$#}>%z@85THIj7HbczLzDdPND@RQDRKa(j5zj4q$%xui+Y2uZ1 zM3$br@SexuC1R-@Nz8ex;YH~TRBVwuxZ8B9MJLfAP)SX4%;T$=SwQQz%#rKbnQsxn zq`9ii?l0ZFoHwfi^SPLk4Az}6ahT}cXS+K$Hv$OwSS16Ai9deSZHl_Y!#Q9c>bdx35J3K6kkDoJ`5{Dijh>22-`Z44mwj4j%eC8|OB%{xGe z`pRGu+h8N_19viFHB*+j_BLS;GFuD~zmO2;5s&GG1raA1^x@Bcd{B zY853|SQD-8FaDe&f=!bqBA3>03w!Z6I8PiAtaUKEycAAwBcvke_!ZO?mvK8@bDW)7 z2y=IXv+`NLr64p43Z#1@&JIYY6&^V;RpcA1Y(!F7?o!H%S+bu-(!9=iw=f6zD~Wxf zOBQ_PD?N$)*@)ztsh3sxw134HL~+{cO{vb=B^V=HV`=^f;e2JIE!IA@NlUblLb6@m zrNmK-xxc9nq5*;6-7Z$PryF+ybb3Cqs8uIrHzjMqkPT2oE7L}+&>xy+UKJTIhLPj! zsH=c98u0WBi3G%9z$W;Gg&674^m;OaYZPgF+`BXSgAJjBozLPQP}x|*LvROAytJEk zu$^HIN?e2k*JQ&S<;d?+F|87cD4ae;)66!9>R=^$UY5B4);;85eqcSRZzAXOl1l6X zZf-?yZpjPPfa4bKA)Zgu(Q0D;MApvOOxHHMeKTrTBT=MgSj+rG$EUnuAfayrnITwN z@LeqNu0+4Do&6xJ$8et9#KYhF;>t_M(S9^Igq9_G_(fSp9kGI7c8@oo4{zh9Gakwk zDiq=?&etK{XcLa&*BCP&wWjy>lplJYHe$c(M>D8am4}Hhc3NXUTTwg|i4uMPOawk4 z2Qida@36#0rKYNl>9WyW*52KwAC(b5THAcG!Ds{Ui`i!iq-n;#bRqDs1j-PfSsYC& zvr?;wA_**B#_j22!X?G-HO2nO(^PPMg*Q};)lbV{$mij~ti-s(-pGVm%Yk2n`}65+ z3ZS13-yO?MA2haLa>RRq3r%nwX35Mg855U$=$SHMk!6r;{f8CVm&B9iyDetvYcPCH zTb)0KM3Vpw*P!#wYdk#awVixhK&5ayyrb7&fU{dyy?71b(T1t_Nw0@ z6eo#DL$47)_Zg+onFT?5FCTLR)N>!v5NWZxg8q4_W$rSp`|1qtkt#5Ni0KnL+~e&_ zNF|nTu=`3R&dvI7QzmnD2u9Mf5!q7_gkMxI0n_QDB3XySphDPIsK!Ptv&mOdhmyo? z&)T@lE|`CMWRHDRk1d1`AL#M_Q;+{c?$>t}D?Z z`rB|4*Ns(E64eBFh21EW>5{&hj2dcv#YkpFGzmE=lsqYD!%liCFZzXCo%UtsZ4 zvNE#LJI(d+pK;6vg~Ae;FGUUj@>%OiTH&vL$lzpMv3;Ckw;k>VjKExAwr%&pZZJDV z^a%k*ph3`*NDfU(kUfAD^dA^l=y-%2G(3*kkWS2Iw#i2g~IB zWQ2Q>CI%6`MhBOTvLE>^6uQU}>%bh>Vg)`5!hw`O^5IUD*i3hiGT5&X1#NbbX_2#i z9%6GEENa}g=*{M76!TSDJU`P5^PST&h&dlMww)c^oH)-kTGDRHTmKv@tw}Ohs&aF< z7xl5a2jF^C8`Q_dCoR62^s612SM3wX+Gt>R-F=6Y1M3^d3vLRyQf6>C4!UG5Q)~s= zlQiAtK2{bZ-~;PfkkrE|rE5eWw5=KWz>@|eqI$uYHGf;H&hcB9@M?y{*oa%NOW7wE zZcV3okLiX9?+KZQh~%umy*yKIdItDTYp&q@6P1IG$v$Z|!eTP&=tZAUn2YN2&DqW) zC{oge`YO-g&LbLMHFLJ1;k@!YCxx$6ZUx9EMcXPT46ZAF^tFhTan5lWQ^QBcMpSFJTUGgJGfsabl(izVh8F_6 z$t)0?u)E}hvoAi-fz&?=tq~j3%2ZXpET;Dio)=#VCC*@fg7bP!PhD4LSC(2cct)w^ zRT{;uQB?S<`R>!mNPF#VR`C`Gm!T<0LV$s_c%USP2s4pDoSf+TX(mk&o{J_Yx7i0z z(6K5hodIW*u)CQ-eB$IZx`rKDw@%rVfP0DlEaO1&S!$u9@E4x-ejRoZZMmx$%<>Pv z_!bkHYgW9uMi%0#D)oVT21768Zp~-S5E1>Iox?aLItdnc{18!kZ2Sn3YKJ8)`p)@S zaZn{Inc>J{d*$~cdfLSoJKEGJCdJow?l#rR%?q9Ig?*5DvXiDdv_r6)iP40qw7|eB z|B2~K;(6qk#^q}%ZmPG@=>gK!JEtz#tUK93sYiEQU*5RAmVE#>)pq|M8G9(*!-mA$ z(lHpjT&Yv4T3e?m;sw}y%!fkaRucx4Z|OA}qxS&K!Om-jdMdSEiD$tz^NqHyLGM#Q zOZWuw?eVt;SCU-zmZ_qeL-C}7kDg0>&TstLI#StSC6N#R#1JyK&} zyBSK}?q6Z>Z|$@NPctOg-2?~R;E#u zzU`i!TRiTq?>ZqzNSBKMQEW}I=$^`r3b#SGGS(3L%#Sz@lRdXc5upD@1@~B6a@&{h zRgl(pV_EweUUg}L_$FHx5Y9pN-c#Qc+NcJQdsWns2Uo@Rl z^ozxN$E19{a3LY_5Bcx>kuuYNJ}BL2;8Zy;31P?i(R25P50<3Dc7+ktH3+^PGLWMz(o5`#Z&y>7f)qlH#c(^2WfM=zmr``B^PT;YX@UHk-tM;3u99^MPqw& z4P!fZ^M4iBZVg>mEFjt&f9tBeq!!ry0Eg|^f_n@Q67-dm@^e+Y5ytFi@*ka+>A_Qz z{OMHa=*pzUes?m4eS`*;j0NNRAt`Ak=Gr%q62|v<^ZZS1IOAk;U<&Hd=>>;>$Fp0$ zYpLVw^Cs#C?wwq;`)~+dJ=O}s4FUp!B148cYYD_1#uBTm08$;ogPmh53Fi!-O=*`_ zF9FtoU#Gm2_L3B>3+tx;g|7B9NC@MPI;U&{x;{n+)m@bpI2psE!fMz9z7yE=T`e3` z1{4g-rN`Ne!YTKvgo&va8;J2z1REpQnJ9z5f%d$#L&?bK$f(HTjFACSTxOJJ*VQ1* zmAd{xC}bLE=>fV}>rt`-V?Yss!N4s6QTx&mB7xsn?g#?`v-AZMl9d7t|- z&N&{hHCC+GbbaHs$_!i088a9*uO-)RW0bH0liUHPbeC=&{aW0(a;8O)?WAFDi7!Rf z+*y3ze)-PMvQlY06l87|J}|+5q;-feV|rQZ#!hfmZ9sa?JGmo?NemB;wjx*IfJ`69 zuI}v1B;I&zR<$k9n1$e7aIq)+xzYJWv{*uL`~Dt#nc4pMJu#nY9F9d80(Xg5r#%2K zaDJqvIPz&?Ba$|w)>=1;)3Skqf5!R5U68}HxXXBCGE0N+9_ECUjHsrDbT9?SVVv#O z@3UiFt|=xLF&g`AR$cRf@8prI@Vd)hywka!2=8@3BCdvxRz*dkQ8aOPr;!wg&Ag-6 zE)+0BZnkN?Nxm-P(l9?4#}EC{frL{~iFXMqaRRYSx0Z~Ngq4YHUx_lD!-CSadR6B% zjXCciz1mVQfn7v6#Fa-ve% zcpXM(e&co$OHfI3+NOqrPPRTu6_e?+M%K@rFOovIvGopby&ntwQNlYFQ45X&7Sh39;x7WS>pcMDa+zBwDW-i>D%Bt~ybtw`>MadmH8r=3^-$^xCD zo5s!s;KQHYgTn6jbWqAz3T>m{AAQ7-OBSqS-z+d38Str&O!cuuXSk~+Py3Y!A}9AL zZiuAvDXxgr5Guq%U!o`=PdiOKBu~@dYYwA`-Q0){W-baA7Ul4dmAHdXn=6Z)wbWA5 ztWOk{itXvM_=B;2Qssh0A%ev(r9$JW8)vde+!f`ncJ9%o*XApHP*a6En!@mj+f3X;7ObU=>g8T+&dwRJBs0B8ls|n=d^%+ zzCXygCpcS0+tXa6uU$&!1kR5}| zuEW!7#_Rn1mhT1M(U$LY`;p$s7)QQ8QlCwS-q1&S67_2e>@OPEnAj{;j}_7H%Gdey z0j0Y~^Z^CCt@Ht9yS?gNg}Vv#Z^gU4z)H0WgA#mtuJU~i>rz?a)U=8wy^?RT3iLIq zC#tC}!x-BEgZFS(sMio; z3LwR8Q!)~VT*3(NfNi>f5FOm5U7TP~#0`3JeKKMwsC$ ztj`*`ces`71$KMx%GXbOfHdq9Nw{c-@lAySVjKsDYHfyIKRyeR;%uClCHoG?>nux= zerHo$CZjYU6KCTr3b_RsVtA<*4Lznv%3ZZd%CVKIaKzg->1gPNve-1Gc`BV=Wk{Z0 z1LMHp7Xrb`pRg}6eLP%ik!4)b5d1iuEB81Z^EUYWc`J?^0)0?~*EaMNuF{`5A7)Fl zB;k<7?G?q01ILY@b)-MDF?|~Ayfu}2(I=YKlOFcjugyr431#>D^#p%*sVQ;TGtH}J z_E-qjjG*98T~iE9HK-3;kx2hWq4wHX@0vezCy?4*^`c+hS~p?dGT#M4XZJe4Yk&pc zmt2>-{H`D(6!YZr=Z*TPJd51ibp+jvb`P!Db3aOIsD*9)z=EssUK-W)(PKxA`IZIl zZ}6_Vs3)01!@96>W|LN#xr%9`Y0+&W58ZS~W8 z`ZzOA$*1c8ju_z#$$(=po#pQKc7r?wi1N zA0R4v?gd$+B=bn}>ft@bfW8lP?E__OO@*0a0!_QL`r>Awvzty|U9gg@XPw9KCAQ>K&esyn37spM~YsV}1hMsS;@y?o>-ABuLr5{!NY3^L-U-#(o z_FUiUUgsBps}-u~m!E7SXAa@gx6j>EYsxn|F&N&zFyCB$(u85QJ?k-$)(vYi$Ee6| zUEO4woU^0jlrOG&lBs&Qzj3~ETa$JvE0{guoP!s#K*Ad%x-9&(Y4%$<~5fmNrXNq`gb zv7T20FW6T}jz~+lCTIT%Zc6li4jiN1>EI1DBgMsOJc>DG>KJM=KF909{ zpYc=`v)zk>-y-|@N9$=*9h*6SW|TT&Qk=7IoR_^^xw~2oC7*son6c81B{VSgN2kb> zX}aVw-pe;;(OG`doCE~(gNlKGxZHAF7lbFM$ zNKdTdk|)Eok)Nd}+cTG~8OD<$m&%;RWO+z_I{Ou1I~#lOfiZ)jm)qH~_60I{E?+8? zpcHxqNWgKG+gSTpwDO3y|B!jf){y~k6^1OrYyI)9d6XpBVww>i`u2ElM&#}8=IGlq z#+#w&;45Bk-`k$KK8kBFgS^l-?XT+r6oys~K>zV{vKQhuib=(VY=xrQuZqHj=t6b< z&sO8mbm|hN6Dd!vH&BS|(_icVU@T{xZ8J7rLO!mw>jzFliP7{sk7aPN4P7GD#nB*< zIAH<#TqYM@W+bLWmZ~FP%_$gvHTx2at50dXI*rxYz=(99H@~3jaD#@AX$?q!cGarQ zc5UQ{&jB12jMdfftS?~t=an}(0fgDsebN5%x?17fEb4{f<4ql<7&R60x8C*=g`2hv zYPQ|?^U9*4P3ciF-6+Pt5f)WSr|;;^D^*LEe1QcW;gvWQ)AL&WYB9t0`~*nYHT%FW zX2&VoJHR}gMY>kkw8|Y;IaHkoz>%bNKh=q2>jRC0IPq36pBK&H9(4~Ek154gcy1dE z|5ltx*QeH-cOfs9n>OV4uyyS>|3~yKC1$@!9EhTaDMOEen`z`_*hY|cFk5v3tQ0z_ zSmb8oEi{VypX;L1{a#$EbX+RikNp)A7Z%H{yC;RGoxC{u^8WNr1Rnfvtl(h5LgJWm8bKN9TsP9vEH8#1t?OfJ37+dKQgq|g*C52jwGWzGe&o*gR0t8tDE|5B59(f6m zgj@P)Rt@#4o?(9^^nRCP|DEKP4D8WAmE}gA{d^)hVw<$i+L_tA!SI%(K)t_)k{Wf7 z|HX&9mN$T-A$5hE;A7YL-I87ezWM&|P~WC>tSC`unmZ>%-O-*TQ975%IjZ#n>sPnC zcheTk(6CHQ!i77 zwwvv~e>2DA(%urcWz?M4nLrDFB9P?W#%bL1shx9SvWQB3WShe||HkM#Wp=tC!&G+8 zxEk-%k(6v6X0I~qgghb8rcWpsTvbR+gk`K7G*kz-F65!;d;5{(!prUxuf6>?uGNi` zQ7^L{Dc|$lW2a@U)pr!dmQH1{PG|UTsEKxcF<&nQnzCQl3tD)jWk)7l#p-?JT;#HBI+Vl?yW@k7o{R7}=9sG7?^#yqgqnjq2)5M);OGOL-f?b0 zSnH?J+OVQg`qJl@-~0IGA0hWSm)qJI`R~x|^M4X@|8B)e-qBLY$<5l)!S!DfF8te+ z?0_;__>BF0Xb)I$5D0lhYwb)_t)-z|%rff$Le4C z5UEwa8@Km#X>G)td08h2YBX(g+nRB|wlOK=!^Su<&TM9=(A=yUc%i=%tPU^uH zRS^A1f7l(q-`1To$2}#u4Q`*UwGSf3&GS$DTA()}X%km1j`8IkQ>kbYQ z5tj;|!>cZZp!ct|r3Oo0qL1@@jH9j3Cb-MY){;OhSul%n6zn$o*7XjKmT}IXLpI5u zQ{-3T^0_XT*OM8Kpmm}Wfu77J{Rg0lgDFuZs1F|`{#xh$y=Ro;-zaTGM;CizJ5_T# zV>fFLbD+!LQ&&ej4|A9Q#!Fd23H%k!U#?&=diSAU9MuQyMMu!I3r~pb6B=fM9R6w2 z9F?v0o#9E8IA&nbmvLSR(*>gqnZqght*+M0<<>IX55Q_4t-Jbt@=s?M~!QH#!6RWFoqcCWo)@I_+0y9?vDFKqf4 zw2SWYamKx3_oD9Pd3Kz!=T+r0iL!38_7ev+QL2rfG7E#Y*~JKFGpAP^q~SZS*V3Qb zqieN8It0ITBU*j4!bHUCeOENvUQw66LsscGyIS>!EE3f4 zL*IkA#ol5J8U59bfY=D6le2RhNH|wd5e5E|#*_`)bNwm$a(+Yp+v*J|Sp_P4icehs zpXzvMeK8r3NtLrI`p;TTcxim*n7;W&y8FWKjMM6~SU;0wjGa8xDQ<>3ID6C;ti~yU zdH&Vk>I!7s_g;&4TT6SkY1V2N!t3ydFO-^2V1U@UkrdHSE*CLsOu$Em-3t)@Ay9p9 ztKx_hkvSW8Lo*SQkdsJO0o*`$9m^_?qBiPbCg4I&yP|rH!{FF8d&5xbQR)9iCO>2{ zY+`Yv{u>i@yIGx%T&7F!*y!-nipUN|Md*VD;k{-X#o(qrZ_SHnL0nnQ5K7JqxR6f! zR5%b}Gc0FI2VKZ3jx)_Vg6FPdi#!5bbvw~DMG(HL1L~hxXznU7X!sikeg82Vng2~J znEscc`+qDb{A+>XUx}EiuIsoVjwdv~8Rw~gbf{;Qlm;tZvA4N!V9;T0oy~21+$cg1 zL$wPva%z{u>tW-I$t>D8L(3uv#p3-TiaJsjnE$6QK{V&OgKm|%eDsEgV>{>4Z#$>` z!xKjlV9dOBVNBb|870?*snAEqV|xqM(%E#%EvA39 z?0n2WND{;@ih0LhZeCi;A_NLx2nTcQ#Ht_`m6|)QPj}dQ3m9rC;PU%6x(*y2r^(BHjEYFw#J()EW=5mRvF=mskFt)NImnKet`+`!KIi<8IV|>1CW||II@mcHoBb~>P_C-J9M}jez@Th8 zA@$z4)Z#PtBsTML`mb@IL2t2C%amq#riJ4gH}3(`47}<`LSJqh^!IP^vHmB zo~y_7^Y7PdcnF>Z`a1cIbJ*uU)M+2Y)WP%^80;%4-v;=_B$$`&%ZhA-6nc*;*}PQU z*LNyOigE9Y%-3`}9ok4*+g!&#j~-&+4lwL%RF}x?$*=S$Vp(c}>IHzzne<03XM#`VCqDvIWb^)11 z_$Kqi{&cdX5Ild-RG9iz9;cChN+LFyvjj|g*4^#cAY`D~)Rsy)K8!UGjTG*1L;JZp zCIwff`ITEClfPDu?lO|rF0!5+iL47_uwK#w8?cH5UJ)hI2K#*>#~pj#kGuh0O{8ET z_X!ROd>bzD?WYVXC;B5~F%9oO*v#0?_h>9Y8fqJ9e6R?F^trd|@cJ-L)Im&dM zb~`+(yZrKD(v&W)(;928p5wR=+rHLjzDPkadkcx**odXPb6-uV;>7wz+w zOS%XAB*QIvjh;!cgzGbgb_GrRDf9$jz&Rx3LhrN6osH~w%PHr@KmA+rHV)&tuoSNi zovXO5>8`lzV|oYX=0DT}x_eZaf!rF=RKa#b0#+J}u2OZ3Z3C~1`phDJ+OWmk?!r}q z@eZS(l@&CN%42Y7yY+X?^#J+xc2=)am4PoDQ4T$M+*|G|K7#-jKtCdd z%#7M}8H)eD*vu`d&lX%kKDWwXGsSX#aClerd_|YamX|^q>l!SLUW|$#x|0wbfC>Bwlw4?bx)3}Ki0Kh z6aEwDBf$p|pua6h@_(eTf3~jupL~MArx5ZCJW*!!@2+EKenxB!M(Unxlq93MQECtwA5P^*cF%<^Tj|{6XCJL>^CU)(MKbV`RpZ$ zEDH2^w84e>Q?*CaY}_~RQ}mLyCp{djEYZXHPocV(!-7$C<7VK43HineU71FD2<4`V zl-s+WFMh|xH!km?mi$#EjIQ40ToNZ~t9eWvAL<7e@#sP&z$&`n+64)l9N z_>QY-!pezcg}1<3Y^*g92~t5m;?kRSYCvH-Q@3bkf8HwxbDQ7O&eu-%@bb0{tEh1b zr0}7YO|9=mnraIDs@J3`L^1D3Ir-^KBtuN&BrZje%dfbT5kD_|%p2-JYzcHj`Q)w# zRw2eGGFwSGo}7$Y2iJ!u^H7U!yWCiVeu*E&h2&barO;l~>zOVzn)~P<{_z&`0_UWH z+99vC2l1Mutk~pl}1)ft+^>rQNO!NPvr&} ziw9gJ#XWeLg?Q>kmkse$Cp5z-ZlF7QW`7iQ9YBM6J2vssg9)EXi99ZOTNqr_OQeq` zlBYY{N7Ez~VM`sop^cCp3%DpIuL9eant|83v^&q9H9ATf8Li|A1@|cjT08i#!uyAm zV$X)_^F`{coED5y262Z!=6abMxl_4bDiR9XH#+$=GEG2_;>HTb$!0s=b7Lx3r)XK$ zay#;>g9<*nNDVnn<_2Lltq4H0iJH%ISzU57%-=)z&3V5e>j@wnWWZVC-)+zI&)zU} zd)e>T(sj?e5QE}uEj!b4lPyMPHQo~0?=&AVPC^)}H?~JuHLTB#3Q9`S`<#lCI!#wp z9028qimG2FSv(0G$e9%nsA=s#WmSv`EfQ|g=tjqX#}DLVm<%69Hckk9ZJlUs%8yT6K+#%@Q;bQ7dw z;*S*fMT8B5*d$k&H`OdI(7OdB*ahw+p}a!>lf!&wSQ=md_8NcwBM%Gx|Igw7c18%ndtm|R zes7)1cv9!ue(nqEbD|yF-xUUN6weq4NfBbs`G(3AX>naVpD}8`4@y{u!+T$C2fKjjmAwkUF<1uGR~X^x^nS%)DLTI<@CdD6HF$*1uM12=>sJV- zq1!0A8bio0ysAJjthwq&NTBUf*f~eoF1wP#?lIg+pnF+H2-Ms8g!L8%&R2g_LwJt^ z=W9H2Abcsm5<_^e0p|lBT@eBec35a%sKNuwuM)7{EW_V{JArDCtqAW4o+ z^H+4oi9!t=_r|NyLIWJ+Myr%?O`Ng`lNv_9MysT7LmVh=&IyxT#*jw1F>=Nj9PR4# zp_0Znd7Si#T?B1Cx|k|6=Vr#*#G&TKHbtD?Z$rNu+eC15&7F%F1L$Jx8?91@6dM2T zOKct8)zIFcj#)Q%j$mw=*j3ctpo-}W-9vFj3f;vLECLNG z9l*E444#6XmXEky#n;dpe1WbdccKWkgntR`!`0B5%xeN;ct8OH1cNcF>r;g6!_5~O za$u^R5Q1$`G_=OD;QYdaI2fu;gaMw~##{wC;{hTNAHchKUj#ayqEQ&OBE|mviZ=8# zwG&FPEBe)5bv;Pm0*0qEr7yo)S#GphyfXeZv5!aNs$LGxFD}THVZl`(_zfCXs~{K# z{*2}dKDd_x5?V7WT}-el3?QX|tU|j~L(JFVH)M=RgDv6w#Q&O5^0iflG%?#iecXT= zXa{m9#Nc(f4MhkF-~qiYzs~{S3oj1WgzgpFR|hXB0{I+#1lJ`GK?aawJaPmFl0h(_ z!oTuEF$4<&#H#Dl?CV1kxG>xBpp<@u+S4aT*V0nNxvT=3w4M+RM-7e~5Wrv8vL2WT--+(JEyX_x zD!sv$1A^ew75%Dkz;c5PZ3E{8*M!EK-$w;qOI4rRmkWJCb@d@QLj8?BQ0^e&Nq{~u z^eGilcw0iMoa{EW2TO zvP*rBQPO~mu;?V$! z;ghGpf-57)P__8^61l4)?EN)SGqtI1y2UJYRdZP{Ho|hH_cF6W^S~JIB`dc zP5WK81kM`2LhTeeuiha8{TJwk^D=ZMG9~)|hqQMN(yUv)2CKTd*k#*Rmu=g&ZQHhO+eVk`>9TFx z_SC)i_f5=1%zb0NnTY2+=f4wi^6XrhduOhdt6|D~YCGGcyST7sVfx67+W=sCnB%qq zoaoEA!j_o}+iUzUj@T)r5_@3X>;W8T&$Er@9r!?Y*joSKY~h%UlscA}{jivg?T42* zYe{meQ52Hx>qXA>PibfdoWIUNWap5t8-Ea>9j;d*nQ;E_Rdb%hJlmIt8>j<;-`sXA4VkGSitwzi&Gu1=Qbp_7_W{DeEOpSXn@NdKWit=qC`c=GV_| zL|F?jqe8GGb!3{W6tnfruJTPQR!BEAjZc66kRAxrnI(BW?*;4mydc7#q@eZ40(CuvmId&Z+C1#Bs^|k+Fwn zzEQ5)v9c5}hDj+rrLL*r4@3haU97Kon#?luaY-r*k;z@Ge5iezd}+{|khg{>0B$i` zWh8QBNCD=-LAxkW?H*qWEKMVJ8^c8j>HXJ0HtjuwiQ}V8kcH(%Le~7Y*krRg!N9)I z31N6AC9kCDMjK|@(EY?pwh<63U+gkRSO^IobLxdp7$=`hJ6D_R;g(qiT4;=^l|Pi5 zTrMyg^?p+GuIwBP>I^!EGdB@B{2W^H94aRO$AfTkn>1s=SMium^YD;i=l*iXiIH5u z%^}ghX^cFAxMw-alJ!tm)Di=2z7H~@#@$ee!X0M&E71R8lOt;+HcZ~|c8@hmgjJ%U z2DBQ~6dP?LFb8aEcs7kD3!c}4f8qoeIt3IbGbRRIyR5*5V7S^9>K;Y>;;1_z+XS)rGr8NIbK($sWH?R0HY6rzQc|2o2p&eX zZV}9ODNdjWIrq$rI3+y{>pRbij?LU1o0g9`C?up0$s{8>WdDp6m3;yjPjp~9&H;=P z)_=82H7Fo|bC$?c#k8E1LgQ~hLuli2O}$Vo?-pIXK^{Bxki1UrrpX+0QUGEoEva3w z16i$JXQX1K^_$oLdYarDJc`B#PoL*jz75><#LZ>px;;+zc5%1pf!A0Z+pcHXB5<*} z;fmU&SqdaxfX519Pml+qTZt&wD;N4dcY0rnQ70}84(Gp@?QqiW@1*D+>U(9C=v+=n zxZRrTxsGAZ0CLp#UveI;E{^@e)fC2oft9gB*VafE*#036Rp+(GT9rl^&P z7Q{Es`d;SiWWK_usnYJ@I5RNz(9KWJS<0hJ5lHfUxpz_mV!C5)14II_t%WkYTqU;l z5!lj~(#*I3u-7QSRoT<)8GQb*;FV8VF7R*uA15T6a`fIfdxdYyrVJ+}uc?glli_m^ zsV^(i_jCDq`g*Wa!zQ-pnYmV~_nsN$J9@tyPNivbR-4NdY`d2fO_|{AFmgqOA=9_G z%oQDX3~46SITK;$Qn~LJ;%2VkoZ!jMtr>XbF*6VeMnyy&+L~g^>Ix^F{&I*aYQLAt z3~1pPJMLTNiZT~!dYnQ*FE|a}p632;W|}MI532M|PX3X&YE3Kpw_E2euL1!)0l+Ay z1>1z21c}0y?iAImw_a+jhyk5-K7Fx>(5P9Rdyf4!M~1~X_-3`ob6i#o7hXqV=)gr| zEKxmm_7qY|FP3SrF4p+NxlM+o@dwQxj<2w~&yN%Uf*GV`W2}$=K^YuahQZuOdbgAO zO2RFuaw!e9W`ogW0i{+v?UrRyoGbobyYT^vqe&s%fpTPOlJZ1Gpo6O0M@~gb38t)F zc;|5Ft`P~^O1FZCwv3jgFrOovCgtxY*BcKP0XUZS!K*~5&eXGA-+Y;R&bL*-%7+#s zsDmorqZ6hA=O2$lw}#CP%J=5Ps#wyCzm(KAc#rSI(l`+;V!4S`GRRkBUx?>{ zAX_~n?UcSI%1SymG}LZ9H)wuQkVNC0laT_ZrD~|E-tZHRT^bIUZR5B# zi`-IMkM{ESEAi+@2Atk>#V@(>Fds3CL8YOn7U&p3W75*`0nr61Om2^JHR3!ySF?83 z^)M^$GmUi~*C7Nk>=b$WQ_)<1<(#;+lULK9|nB{WVT z3xC?4#L~sdy%tV4}jz?oUmyBqQ1(n~NF9HMK;OIs~Hky@vVr zDs?981y(t+!2;_B!cH(7VF_0g;pwP_-(Ck_cZuKwKkH-mGoV<{Cpo2!hw_OpnB*Uf zEXe6aiD1%(S+PbuM#dfXFdy0~e`lZYk^u=1+03+*?7EuLf2T|Pt2m!C)D8gQF$ zY_Dn~i#BZ8gfDexAQ)h9dT;v}KYj=jXj3ojw1gX}ENmvt-pC73sZ5okJ8~bXv4YK#_f4jJ{4$_IJOy z&K@|N(AFtT`VGQH2(DeHbp(fgPf^Ow9@l04Lm_Dl!H9qeIpN6oTQNawyI)Y<$d%3Q z;_i2|sv}4S&bD^2PAq}t93#_y@n^G76t4z=AtS-W{73PS*%`C1x7<+}g~XC6?qjl< zH_1+2ZP6sM1$uL4XV!{V!2uFUJ%&cNy}VeoZ1>FQ1mG6bk!8^%7xF=j{Of#jUzghwg29%NLy z1bG;T1Vwsv6EXE3b1iBXx%nxJ)K<(<#5GCM8DOwPZXTFpCpj$(T0XF~d*@sg5&3t( z5^JJj{o)Rk#qCrg>PGD|q6B^sH9>c+;`Tj9tqq4~Rhw1+-3^YzJY=hppe53DX6{v* zo@Rfhr4WE)JKl>9EvsBJ^!SLx{l{ch*2gurr;hMyrF>=voH(1&;J`CVmz*KZDT*jG z)}ZcM4KV4nRAX&^>DC*ODgM;sF}Ql0HtG#l*vUKjKAzk6HwQP*mA$C(K&<^X8?WD` zAW$jvaMfdQxIN~hF1zp9kTdZ@h)BEp`a*+*L}xo+-`28_(<%Bj*ZgVaD7KSIL`d45n=( zS+;Xw&ZvDwiYyZ8jN-0H_RZMYK|*9`L5O-?9wmC-vTHv7RIpMr=Jt`4CJgJ^BWbN> ztB5u}dPWUC%}F9)W;zAKx{wRE6peGd*q@w;MS_4ex^;#m-OJYETHacHl!}g z(a>%{qd4WZa<(Le4rQbG8~H37ti_oAWV>GKf!TpWbqMBA?_rzl zwSv_7nXQl5UK^{myrmUy&G+)tJbSgiV{QuA8V!So?yek(JkSTHBgn;{0!A`zz_Tl_ zV57%+$XT$JeghnASPg!2TNOY+GQL^CHTSA4O=qZ|B`(!7wf9eIO=p1N2gkkFbpI#O z&kh&v{;kE;o0gM{pj1^M*k!@M+rZ#5kz%>VagRji{n)-t zvy=T6sZD4$&ZxrZuBfV=)Smj5QCpC4G1I95x=8y(sRj_eY-S-^uXlK0SNUZ+F-uIQ zJ34JNB4ZWJHbB3ugodCGSTUdX;yd!%*@>==2`o+3FRmR9eAJYpVRDh1`?xz3%&bDB z*nYXg?lnSVj@4%?sUNR{u;K*dyR~278?m2Y>JvTnOM8fR%(1MxEhiVv-|>aq&5+Up zacYGjbwQB%QZS5&jqHcON=ZER!+3x*U>I`|Gqxk9YXwWyh>$K3CZ5HO!2BgnoT5vU zqD#9U+R=m)E-#$dEGH46lT=)-7Q1Gt>%$S-hp&}x{;8Rmx^1Hs;0fQu#L?*gHz7?U zZm60b)m&qbU+9CzR$TaX*l4yE|70NS%gW+imvpZu_*rxs6YAyt_+8lPvvvCYDG(1v zW#w0^oaF6$Pw$Iw_}v$)+DR=ehR`sf{prOKHoQMt1r=OCGx?7m8_>vQ^?6uN7t)A6 zFhNYmyk+08*Tb7(h9b^lm*)kYHX?Bc7CdAZM2VVf$!LO1d0o^lS~}?^^|0YL)~2n~ z3W)VJD5SWDvuZj-VRvgRfSwTHw> z2UP;2v!RHVG1EpY^mr>n-|u4_fKyJ1_9KKm}UIsdmNE41Cz>{d}|qQC|g4SOyD(`LloUH`ycH znE>i}^qV=P)+1@)feNi}I-ul2TQfQL4iuhQuundG>PBuP6;h;T+YY#tCqaI@Z=4qR zgK6nJ@|x`hWO^Kg(&h>@za7&^&k=jmh6Ut_YxMZTsBx?B=3LmWAypsS6SSmpfG)Nh z>esSmT~IeL)iS5tKnx$FPW63if0X+ohWvNsm&b)65MnIHK*kKl61x#E5r-T~X>wwY_o04oZ8uGg?j)+PD~@gLw{Z>ZwXCt%^g z6ykP})awB*i9@xlV$h6YMr_Wq{mPP1UtHQ)c8&CUw68cYbwQ6H-hJ;a~lE?#v$G)-hC(TBxS>SnsY_(DWx#_`EH0Wtavb6)Vuz zRS-9yiHK7Ov8JuaiTvKkNP6Tej8hA;J4IH6x*T zj@YQ7waN{{)Z*AYgIjo+4cieuCBu-KY4})>8@d2+?|tsH5eOv}JuZY0<)RuxE7?r7 z)B;~zl9w&XpV>y9A-5=qzc#0>!s+1Ax1VHk%0Uz_+7pzmZ$e+fIy7?$Xz7disB6y< z(u%(94N>W2zzyLfI(PF739I|z2^M2a)bxuBUSGyDWR5SU*A;=qiZ@H$TDDbF{xgb2 z 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
)K(>O$x8cW)5r_&d#wbXUh|uNB!Jux0 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

9|C0)|`diZ&RT`$I!m&oqL?4t5WZPY!xetdXY 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 z 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ǢiL^2Zi2eit+CNzastm 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

+ * + * to your template and Lift will replace it with the path to the blueprint fancy-type plugin + * css styles + * (screen media) + */ def fancyType: NodeSeq = { render _ } + /** + * Returns the child nodes: + * + *
+   *   <lift:Children>
+   *     <div class="lift:MySnippet">
+   *     </div>
+   *     <div class="lift:MyOtherSnippet">
+   *     </div>
+   *  </lift>
+   * 
+ */ def render(kids: NodeSeq): NodeSeq = kids } diff --git a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Comet.scala b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Comet.scala index ce7654a346..2efb1bc625 100644 --- a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Comet.scala +++ b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Comet.scala @@ -1,5 +1,5 @@ /* - * Copyright 2007-2011 WorldWide Conferencing, LLC + * 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. @@ -30,6 +30,26 @@ object Comet extends DispatchSnippet with LazyLoggable { case _ => render _ } + /** + * + * A typical comet tag could look like: + * + *
+   *   <lift:comet type=<Your comet class> name=<Optional, the name of this comet instance>>{xhtml}
+   * 
+ * + * For the name, you have three options + *
    + *
  • You can set a fix name using
    name="MyComet"
    + *
  • You can use a query parameter, for a url like foo?=id=122, your comet could take the + * name "122" if you use:
    metaname=id
    + *
  • You could assign a random name by using
    randomname=true
    + *
+ * + * + * @param kids The NodeSeq that is enclosed by the comet tags + * @return + */ def render(kids: NodeSeq) : NodeSeq = { Props.inGAE match { @@ -43,7 +63,7 @@ object Comet extends DispatchSnippet with LazyLoggable { cometActor.parentTag.scope, Group(xml)) % (new UnprefixedAttribute("id", Text(spanId), Null)) % (timeb.filter(_ > 0L).map(time => (new PrefixedAttribute("lift", "when", Text(time.toString), Null))) openOr Null) - + private def buildComet(kids: NodeSeq) : NodeSeq = { val theType: Box[String] = S.attr.~("type").map(_.text) val name: Box[String] = S.currentAttr("name") or diff --git a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/HTML5.scala b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/HTML5.scala index dd0ffd1a8f..817374d2ae 100644 --- a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/HTML5.scala +++ b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/HTML5.scala @@ -1,5 +1,5 @@ /* - * Copyright 2009-2011 WorldWide Conferencing, LLC + * Copyright 2009-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. @@ -22,6 +22,9 @@ import scala.xml._ import net.liftweb.http._ import net.liftweb.common._ +/** + * Sets the DocType to html5 + */ object HTML5 extends DispatchSnippet { def dispatch : DispatchIt = { diff --git a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/LazyLoad.scala b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/LazyLoad.scala index 1ab3c2dfcd..4d408193e6 100644 --- a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/LazyLoad.scala +++ b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/LazyLoad.scala @@ -1,5 +1,5 @@ /* - * Copyright 2007-2011 WorldWide Conferencing, LLC + * 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. @@ -31,7 +31,8 @@ import Helpers._ /** - * + * Enclose your snippet tags on your template with LazyLoad and the snippet will execute + * on a different thread, which avoids blocking the page render. */ object LazyLoad extends DispatchSnippet { private object myFuncName extends TransientRequestVar(Helpers.nextFuncName) @@ -41,6 +42,27 @@ object LazyLoad extends DispatchSnippet { case _ => render _ } + /** + * Enclose your snippet like this: + * + *
+   *   <div class="lift:LazyLoad">
+   *     <div class="lift:MyLongRunningSnippet"></div>
+   *   </div>
+   * 
+ * + * You can add the template attribute to the LazyLoad tag and instead of + * showing the spinning circle, it will render your template. + * + * + *
+   *   <div class="lift:LazyLoad?template='my-nice-wait-message-template'">
+   *     <div class="lift:MyLongRunningSnippet"></div>
+   *   </div>
+   * 
+ * + * + */ def render(xhtml: NodeSeq): NodeSeq = { (for { session <- S.session ?~ ("FIXME: Invalid session") diff --git a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/SkipDocType.scala b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/SkipDocType.scala index 1aae01088c..788d4b49ac 100644 --- a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/SkipDocType.scala +++ b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/SkipDocType.scala @@ -1,5 +1,5 @@ /* - * Copyright 2009-2011 WorldWide Conferencing, LLC + * Copyright 2009-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. @@ -27,6 +27,10 @@ object SkipDocType extends DispatchSnippet { case _ => render _ } + /** + * Useful if you need to omit the DocType from the returned html + * (calling the page from JavaScript, etc + */ def render(kids: NodeSeq): NodeSeq = { S.skipDocType = true kids diff --git a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/TestCond.scala b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/TestCond.scala index ad780504be..44d009cfc3 100644 --- a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/TestCond.scala +++ b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/TestCond.scala @@ -1,5 +1,5 @@ /* - * Copyright 2009-2011 WorldWide Conferencing, LLC + * Copyright 2009-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. @@ -21,17 +21,14 @@ package snippet import scala.xml._ import net.liftweb.http._ +/** + * Use this builtin snippet to either show or hide some html + * based on the user being logged in or not. + */ object TestCond extends DispatchSnippet { def dispatch : DispatchIt = { - case "loggedin" | - "logged_in" | - "LoggedIn" | "loggedIn" => loggedIn _ - - case "loggedout" | - "logged_out" | - "LoggedOut" | - "loggedOut" - => loggedOut _ + case "loggedin" | "logged_in" | "LoggedIn" | "loggedIn" => loggedIn _ + case "loggedout" | "logged_out" | "LoggedOut" | "loggedOut" => loggedOut _ } def loggedIn(xhtml: NodeSeq): NodeSeq = diff --git a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/WithParam.scala b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/WithParam.scala index d494de2be7..3fd7dbbf92 100644 --- a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/WithParam.scala +++ b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/WithParam.scala @@ -25,12 +25,22 @@ import net.liftweb.common._ object WithParamVar extends RequestVar[Map[String, NodeSeq]](Map.empty) +/** + * Evaluates the body and stores it in the WithParam RequestVar map. + * This map is used in builtin.snippet.Surround to bind content to named sections. + * Note that the WithParam snippet is also mapped to "bind-at" + */ object WithParam extends DispatchSnippet { def dispatch : DispatchIt = { case _ => render _ } + /** + * Evaluates the body and stores it in the WithParam RequestVar map. + * This map is used in builtin.snippet.Surround to bind content to named sections. + * Note that the WithParam snippet is also mapped to "bind-at" + */ def render(kids: NodeSeq) : NodeSeq = { (for { ctx <- S.session ?~ ("FIX"+"ME: Invalid session") diff --git a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/XmlGroup.scala b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/XmlGroup.scala index 7455bbc2ba..eb44abf90f 100644 --- a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/XmlGroup.scala +++ b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/XmlGroup.scala @@ -27,6 +27,18 @@ object XmlGroup extends DispatchSnippet { case _ => render _ } + /** + * Returns the child nodes: + * + *
+   *   <lift:XmlGroup>
+   *     <div class="lift:MySnippet">
+   *     </div>
+   *     <div class="lift:MyOtherSnippet">
+   *     </div>
+   *  </lift>
+   * 
+ */ def render(kids: NodeSeq): NodeSeq = kids } diff --git a/web/webkit/src/main/scala/net/liftweb/http/auth/Role.scala b/web/webkit/src/main/scala/net/liftweb/http/auth/Role.scala index 02f1bacaba..4c9bf0c25f 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/auth/Role.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/auth/Role.scala @@ -35,7 +35,7 @@ object AuthRole { } /** - * A Role may be assingned to a resource denominated by a path. A subject + * A Role may be assigned to a resource denominated by a path. A subject * that is assigned to the same role or to a role higher into the roles hierarchy * will have access to requested resource. */ @@ -44,7 +44,7 @@ trait Role { private var childs: List[Role] = Nil /** - * The name ofthe role + * The name of the role */ def name: String @@ -70,7 +70,7 @@ trait Role { def getChildRoles = childs /** - * Retuns the parent node + * Returns the parent node */ def getParent = parent From 379670c40cdbf3eec8f4a4d8dab8065ee8e53695 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Kuczera?= Date: Tue, 20 Mar 2012 00:22:28 +0100 Subject: [PATCH 0030/1949] fixed subject enoding issue with non ASCI characters --- core/util/src/main/scala/net/liftweb/util/Mailer.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/Mailer.scala b/core/util/src/main/scala/net/liftweb/util/Mailer.scala index 80614039b3..88b69819ee 100644 --- a/core/util/src/main/scala/net/liftweb/util/Mailer.scala +++ b/core/util/src/main/scala/net/liftweb/util/Mailer.scala @@ -224,7 +224,7 @@ trait Mailer extends SimpleInjector { case Full(a) => jndiSession openOr Session.getInstance(buildProps, a) case _ => jndiSession openOr Session.getInstance(buildProps) } - + val subj = MimeUtility.encodeText(subject.subject, "utf-8", "Q") val message = new MimeMessage(session) message.setFrom(from) message.setRecipients(Message.RecipientType.TO, info.flatMap {case x: To => Some[To](x) case _ => None}) @@ -232,7 +232,7 @@ trait Mailer extends SimpleInjector { message.setRecipients(Message.RecipientType.BCC, info.flatMap {case x: BCC => Some[BCC](x) case _ => None}) // message.setReplyTo(filter[MailTypes, ReplyTo](info, {case x @ ReplyTo(_) => Some(x); case _ => None})) message.setReplyTo(info.flatMap {case x: ReplyTo => Some[ReplyTo](x) case _ => None}) - message.setSubject(subject.subject) + message.setSubject(subj) info.foreach { case MessageHeader(name, value) => message.addHeader(name, value) case _ => From 32e68481c82cfb7a393c7c1035e7f1219451a548 Mon Sep 17 00:00:00 2001 From: Indrajit Raychaudhuri Date: Fri, 16 Mar 2012 10:31:14 +0530 Subject: [PATCH 0031/1949] Move to SBT 0.12.0-M2 --- build.sbt | 12 +++--------- liftsh | 2 +- liftsh.cmd | 2 +- project/build.properties | 3 ++- ...0.12.0-M1.jar => sbt-launch-0.12.0-M2.jar} | Bin 1055106 -> 1055107 bytes 5 files changed, 7 insertions(+), 12 deletions(-) rename project/{sbt-launch-0.12.0-M1.jar => sbt-launch-0.12.0-M2.jar} (84%) diff --git a/build.sbt b/build.sbt index 5d74fa2f33..55304bfddc 100644 --- a/build.sbt +++ b/build.sbt @@ -19,17 +19,11 @@ libraryDependencies in ThisBuild <++= scalaVersion { sv => Seq(specs, scalacheck // Settings for Sonatype compliance pomIncludeRepository in ThisBuild := { _ => false } -publishTo in ThisBuild <<= isSnapshot(if (_) Some(PublishRepo.Snapshot) else Some(PublishRepo.Staging)) +publishTo in ThisBuild <<= isSnapshot(if (_) Some(Opts.resolver.sonatypeSnapshots) else Some(Opts.resolver.sonatypeStaging)) -// TODO: Enable after SBT 0.12.0-M2 -//scmInfo in ThisBuild := Some(ScmInfo(url("https://github.com/lift/framework"), "scm:git:https://github.com/lift/framework.git")) -// -//pomExtra in ThisBuild ~= (_ ++ {Developers.toXml}) +scmInfo in ThisBuild := Some(ScmInfo(url("https://github.com/lift/framework"), "scm:git:https://github.com/lift/framework.git")) -pomExtra in ThisBuild ~= (_ ++ - http://github.com/lift/framework - scm:git:https://github.com/lift/framework.git - ++ {Developers.toXml}) +pomExtra in ThisBuild ~= (_ ++ {Developers.toXml}) credentials in ThisBuild <+= state map { s => Credentials(BuildPaths.getGlobalSettingsDirectory(s, BuildPaths.getGlobalBase(s)) / ".credentials") } diff --git a/liftsh b/liftsh index 1ec4c4255c..36b6273724 100755 --- a/liftsh +++ b/liftsh @@ -14,4 +14,4 @@ DEFAULT_OPTS="" cd `dirname $0` # Call with INTERNAL_OPTS followed by LIFTSH_OPTS (or DEFAULT_OPTS). java always takes the last option when duplicate. -exec java ${INTERNAL_OPTS} ${LIFTSH_OPTS:-${DEFAULT_OPTS}} -jar project/sbt-launch-0.12.0-M1.jar "$@" +exec java ${INTERNAL_OPTS} ${LIFTSH_OPTS:-${DEFAULT_OPTS}} -jar project/sbt-launch-0.12.0-M2.jar "$@" diff --git a/liftsh.cmd b/liftsh.cmd index 17ec4bf1c7..3e3c6757e8 100644 --- a/liftsh.cmd +++ b/liftsh.cmd @@ -11,4 +11,4 @@ if "%LIFTSH_OPTS%"=="" ( ) @REM Call with INTERNAL_OPTS followed by LIFTSH_OPTS (or DEFAULT_OPTS). java always takes the last option when duplicate. -java %INTERNAL_OPTS% %LIFTSH_OPTS% -jar "%~dp0\project\sbt-launch-0.12.0-M1.jar" %* +java %INTERNAL_OPTS% %LIFTSH_OPTS% -jar "%~dp0\project\sbt-launch-0.12.0-M2.jar" %* diff --git a/project/build.properties b/project/build.properties index 84c152ab45..c279119e64 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1,2 @@ -sbt.version=0.12.0-M1 +# Deprecate using build.properties, use -Dsbt.version=... in launcher arg instead +sbt.version=0.12.0-M2 diff --git a/project/sbt-launch-0.12.0-M1.jar b/project/sbt-launch-0.12.0-M2.jar similarity index 84% rename from project/sbt-launch-0.12.0-M1.jar rename to project/sbt-launch-0.12.0-M2.jar index 3dfcc9cf56e16d8ed43c8d4024a020249cc9df57..2c4673b44b58697e13520d4c7a7078c6440afd51 100644 GIT binary patch delta 29713 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 Twqe!MFVGi-ez|;Q$Dl6e_ delta 30259 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 Date: Sun, 18 Mar 2012 18:57:51 +0530 Subject: [PATCH 0032/1949] update plugin ref --- project/project/Plugins.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/project/Plugins.scala b/project/project/Plugins.scala index 6ed864ea29..fcd37b4161 100644 --- a/project/project/Plugins.scala +++ b/project/project/Plugins.scala @@ -2,5 +2,5 @@ import sbt._ object PluginDef extends Build { lazy val root = Project("plugins", file(".")) dependsOn(buildPlugin) - lazy val buildPlugin = uri("git://github.com/indrajitr/sbt-lift-build-plugin.git#5f6930f787") + lazy val buildPlugin = uri("git://github.com/indrajitr/sbt-lift-build-plugin.git#59101ef355") } From 5275c48cd9de436480aaa32f628db9f961c0f2b7 Mon Sep 17 00:00:00 2001 From: Indrajit Raychaudhuri Date: Sun, 18 Mar 2012 18:58:22 +0530 Subject: [PATCH 0033/1949] use `-XX:+UseCompressedOops` for 64 bit jvm --- liftsh | 3 +++ liftsh.cmd | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/liftsh b/liftsh index 36b6273724..086c4920be 100755 --- a/liftsh +++ b/liftsh @@ -8,6 +8,9 @@ fi # Internal options, always specified INTERNAL_OPTS="-Dfile.encoding=UTF-8 -Xmx768m -noverify -XX:ReservedCodeCacheSize=96m -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC -XX:MaxPermSize=512m" +# Add 64bit specific option +exec java -version 2>&1 | grep -q "64-Bit" && INTERNAL_OPTS="${INTERNAL_OPTS} -XX:+UseCompressedOops" + # Default options, if nothing is specified DEFAULT_OPTS="" diff --git a/liftsh.cmd b/liftsh.cmd index 3e3c6757e8..b986d8b8db 100644 --- a/liftsh.cmd +++ b/liftsh.cmd @@ -3,6 +3,12 @@ @REM Internal options, always specified set INTERNAL_OPTS=-Dfile.encoding=UTF-8 -Xmx768m -noverify -XX:ReservedCodeCacheSize=96m -XX:+UseCompressedOops -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC -XX:MaxPermSize=512m +@REM Add 64bit specific option +java -version 2>&1 | find "64-Bit" >nul: +if not errorlevel 1 ( + set INTERNAL_OPTS=%INTERNAL_OPTS% -XX:+UseCompressedOops +) + @REM Default options, if nothing is specified set DEFAULT_OPTS= From d07d091f3c8f5b5400c6641c1595f54c82b9b225 Mon Sep 17 00:00:00 2001 From: Indrajit Raychaudhuri Date: Fri, 23 Mar 2012 16:01:32 +0530 Subject: [PATCH 0034/1949] bump up `ReservedCodeCacheSize` for 64bit jvm --- liftsh | 2 +- liftsh.cmd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/liftsh b/liftsh index 086c4920be..3d096a3fae 100755 --- a/liftsh +++ b/liftsh @@ -9,7 +9,7 @@ fi INTERNAL_OPTS="-Dfile.encoding=UTF-8 -Xmx768m -noverify -XX:ReservedCodeCacheSize=96m -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC -XX:MaxPermSize=512m" # Add 64bit specific option -exec java -version 2>&1 | grep -q "64-Bit" && INTERNAL_OPTS="${INTERNAL_OPTS} -XX:+UseCompressedOops" +exec java -version 2>&1 | grep -q "64-Bit" && INTERNAL_OPTS="${INTERNAL_OPTS} -XX:+UseCompressedOops -XX:ReservedCodeCacheSize=128m" # Default options, if nothing is specified DEFAULT_OPTS="" diff --git a/liftsh.cmd b/liftsh.cmd index b986d8b8db..379b95822a 100644 --- a/liftsh.cmd +++ b/liftsh.cmd @@ -6,7 +6,7 @@ set INTERNAL_OPTS=-Dfile.encoding=UTF-8 -Xmx768m -noverify -XX:ReservedCodeCache @REM Add 64bit specific option java -version 2>&1 | find "64-Bit" >nul: if not errorlevel 1 ( - set INTERNAL_OPTS=%INTERNAL_OPTS% -XX:+UseCompressedOops + set INTERNAL_OPTS=%INTERNAL_OPTS% -XX:+UseCompressedOops -XX:ReservedCodeCacheSize=128m ) @REM Default options, if nothing is specified From ac3fc0acfc67c4c5d5a648948ea5dd1d87c58774 Mon Sep 17 00:00:00 2001 From: Indrajit Raychaudhuri Date: Fri, 23 Mar 2012 16:03:43 +0530 Subject: [PATCH 0035/1949] Sync with lift-build-plugin updates --- build.sbt | 2 +- project/project/Plugins.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index 55304bfddc..6f65cbbd6b 100644 --- a/build.sbt +++ b/build.sbt @@ -27,4 +27,4 @@ pomExtra in ThisBuild ~= (_ ++ {Developers.toXml}) credentials in ThisBuild <+= state map { s => Credentials(BuildPaths.getGlobalSettingsDirectory(s, BuildPaths.getGlobalBase(s)) / ".credentials") } -initialize <<= (name, version) apply printLogo +initialize <<= (name, version, scalaVersion) apply printLogo diff --git a/project/project/Plugins.scala b/project/project/Plugins.scala index fcd37b4161..e06d71bc4f 100644 --- a/project/project/Plugins.scala +++ b/project/project/Plugins.scala @@ -2,5 +2,5 @@ import sbt._ object PluginDef extends Build { lazy val root = Project("plugins", file(".")) dependsOn(buildPlugin) - lazy val buildPlugin = uri("git://github.com/indrajitr/sbt-lift-build-plugin.git#59101ef355") + lazy val buildPlugin = uri("git://github.com/indrajitr/sbt-lift-build-plugin.git#c78f617f62") } From aa8a9e054cc0123598e4c7260400a3d388cb8e4b Mon Sep 17 00:00:00 2001 From: Indrajit Raychaudhuri Date: Fri, 23 Mar 2012 16:05:54 +0530 Subject: [PATCH 0036/1949] lift-common doesn't need commons-codec --- project/Build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index 6bf828d5ed..f5a1039697 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -39,7 +39,7 @@ object BuildDef extends Build { lazy val common = coreProject("common") .settings(description := "Common Libraties and Utilities", - libraryDependencies ++= Seq(slf4j_api, logback, slf4j_log4j12, commons_codec)) + libraryDependencies ++= Seq(slf4j_api, logback, slf4j_log4j12)) lazy val actor = coreProject("actor") From a683fbf5df04cce7d7e3ada6821c2a614bac9bda Mon Sep 17 00:00:00 2001 From: Indrajit Raychaudhuri Date: Fri, 23 Mar 2012 16:06:28 +0530 Subject: [PATCH 0037/1949] Library version updates --- project/Dependencies.scala | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 0c2ab8586b..cc11bacebf 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -30,26 +30,26 @@ object Dependencies { lazy val slf4jVersion = "1.6.4" lazy val scalazGroup = defaultOrMapped("org.scalaz", "2.8.0" -> "com.googlecode.scalaz") - lazy val scalazVersion = defaultOrMapped("6.0.2", "2.8.0" -> "5.0", "2.9.0" -> "6.0.RC2") + lazy val scalazVersion = defaultOrMapped("6.0.4", "2.8.0" -> "5.0", "2.9.0" -> "6.0.RC2") lazy val scalacheckVersion = defaultOrMapped("1.9", "2.8.0" -> "1.7", "2.8.1" -> "1.8", "2.8.2" -> "1.8") lazy val specsVersion = defaultOrMapped("1.6.8", "2.8.0" -> "1.6.5", "2.9.1" -> "1.6.9", "2.9.1-1" -> "1.6.9") // Compile scope: // Scope available in all classpath, transitive by default. - lazy val commons_codec = "commons-codec" % "commons-codec" % "1.4" + lazy val commons_codec = "commons-codec" % "commons-codec" % "1.6" lazy val commons_fileupload = "commons-fileupload" % "commons-fileupload" % "1.2.2" lazy val commons_httpclient = "commons-httpclient" % "commons-httpclient" % "3.1" lazy val dispatch_http = "net.databinder" % "dispatch-http" % "0.7.8" cross CVMapping2911 lazy val javamail = "javax.mail" % "mail" % "1.4.4" - lazy val joda_time = "joda-time" % "joda-time" % "1.6.2" + lazy val joda_time = "joda-time" % "joda-time" % "1.6.2" // TODO: 2.1 lazy val htmlparser = "nu.validator.htmlparser" % "htmlparser" % "1.2.1" - lazy val mongo_java_driver = "org.mongodb" % "mongo-java-driver" % "2.6.5" + lazy val mongo_java_driver = "org.mongodb" % "mongo-java-driver" % "2.7.3" lazy val paranamer = "com.thoughtworks.paranamer" % "paranamer" % "2.4.1" lazy val scalajpa = "org.scala-libs" % "scalajpa" % "1.4" cross CVMappingAll lazy val scalap: ModuleMap = "org.scala-lang" % "scalap" % _ lazy val scalaz_core: ModuleMap = sv => scalazGroup(sv) % "scalaz-core" % scalazVersion(sv) cross CVMappingAll lazy val slf4j_api = "org.slf4j" % "slf4j-api" % slf4jVersion - lazy val squeryl = "org.squeryl" % "squeryl" % "0.9.4" cross CVMappingAll + lazy val squeryl = "org.squeryl" % "squeryl" % "0.9.4" cross CVMappingAll // TODO: 0.9.5 // Aliases lazy val mongo_driver = mongo_java_driver @@ -58,7 +58,7 @@ object Dependencies { // Provided scope: // Scope provided by container, available only in compile and test classpath, non-transitive by default. - lazy val logback = "ch.qos.logback" % "logback-classic" % "1.0.0" % "provided" + lazy val logback = "ch.qos.logback" % "logback-classic" % "1.0.1" % "provided" lazy val log4j = "log4j" % "log4j" % "1.2.16" % "provided" lazy val slf4j_log4j12 = "org.slf4j" % "slf4j-log4j12" % slf4jVersion % "provided" lazy val persistence_api = "javax.persistence" % "persistence-api" % "1.0" % "provided" @@ -77,7 +77,7 @@ object Dependencies { // Test scope: // Scope available only in test classpath, non-transitive by default. // TODO: See if something alternative with lesser footprint can be used instead of mega heavy apacheds - lazy val apacheds = "org.apache.directory.server" % "apacheds-server-integ" % "1.5.5" % "test" + lazy val apacheds = "org.apache.directory.server" % "apacheds-server-integ" % "1.5.5" % "test" // TODO: 1.5.7 lazy val jetty6 = "org.mortbay.jetty" % "jetty" % "6.1.26" % "test" lazy val jwebunit = "net.sourceforge.jwebunit" % "jwebunit-htmlunit-plugin" % "2.5" % "test" lazy val mockito_all = "org.mockito" % "mockito-all" % "1.8.5" % "test" From 6289a10a06397d51e1bb28cbef022d949cec43c6 Mon Sep 17 00:00:00 2001 From: Indrajit Raychaudhuri Date: Fri, 23 Mar 2012 23:36:32 +0530 Subject: [PATCH 0038/1949] Deprecation fixes --- .../main/scala/net/liftweb/json/Formats.scala | 2 +- .../liftweb/json/FieldSerializerBugs.scala | 10 ++-- .../net/liftweb/util/PCDataMarkupParser.scala | 8 +-- .../net/liftweb/util/SecurityHelpers.scala | 18 +++---- .../scala/net/liftweb/mongodb/Mongo.scala | 1 + .../mocks/MockHttpServletRequest.scala | 54 +++++++++---------- .../net/liftweb/http/HtmlProperties.scala | 22 ++++---- .../scala/net/liftweb/http/LiftRules.scala | 2 - .../src/main/scala/net/liftweb/http/S.scala | 44 +++++++-------- .../net/liftweb/http/js/JsCommands.scala | 36 ++++++------- .../scala/net/liftweb/http/SnippetSpec.scala | 34 ++++++------ 11 files changed, 114 insertions(+), 117 deletions(-) diff --git a/core/json/src/main/scala/net/liftweb/json/Formats.scala b/core/json/src/main/scala/net/liftweb/json/Formats.scala index c484da34cf..5349531b61 100644 --- a/core/json/src/main/scala/net/liftweb/json/Formats.scala +++ b/core/json/src/main/scala/net/liftweb/json/Formats.scala @@ -275,7 +275,7 @@ class CustomSerializer[A: Manifest]( val Class = implicitly[Manifest[A]].erasure def deserialize(implicit format: Formats) = { - case (TypeInfo(Class, _), json) => + case (TypeInfo(Class, _), json) => if (ser(format)._1.isDefinedAt(json)) ser(format)._1(json) else throw new MappingException("Can't convert " + json + " to " + Class) } diff --git a/core/json/src/test/scala/net/liftweb/json/FieldSerializerBugs.scala b/core/json/src/test/scala/net/liftweb/json/FieldSerializerBugs.scala index df979773b2..d870196e79 100644 --- a/core/json/src/test/scala/net/liftweb/json/FieldSerializerBugs.scala +++ b/core/json/src/test/scala/net/liftweb/json/FieldSerializerBugs.scala @@ -19,7 +19,7 @@ package json import org.specs.Specification -object FieldSerializerBugs extends Specification { +object FieldSerializerBugs extends Specification { import Serialization.{read, write => swrite} implicit val formats = DefaultFormats + FieldSerializer[AnyRef]() @@ -29,7 +29,7 @@ object FieldSerializerBugs extends Specification { import java.util.concurrent.atomic.AtomicInteger val ser = swrite(new AtomicInteger(1)) - val atomic = read[AtomicInteger](ser) + val atomic = read[AtomicInteger](ser) atomic.get mustEqual 1 } */ @@ -44,7 +44,7 @@ object FieldSerializerBugs extends Specification { } "FieldSerialization should work with Options" in { - implicit val formats = DefaultFormats + FieldSerializer[ClassWithOption]() + implicit val formats = DefaultFormats + FieldSerializer[ClassWithOption]() val t = new ClassWithOption t.field = Some(5) @@ -53,8 +53,8 @@ object FieldSerializerBugs extends Specification { case class WithSymbol(`a-b*c`: Int) - class ClassWithOption { - var field: Option[Int] = None + class ClassWithOption { + var field: Option[Int] = None } } diff --git a/core/util/src/main/scala/net/liftweb/util/PCDataMarkupParser.scala b/core/util/src/main/scala/net/liftweb/util/PCDataMarkupParser.scala index e07ea7dacf..56270ab6c9 100644 --- a/core/util/src/main/scala/net/liftweb/util/PCDataMarkupParser.scala +++ b/core/util/src/main/scala/net/liftweb/util/PCDataMarkupParser.scala @@ -162,7 +162,7 @@ class PCDataXmlParser(val input: Source) extends ConstructingHandler with PCData val report = curInput.descr + ":" + line + ":" + col + ": " + msg System.err.println(report) try { - System.err.println(curInput.getLine(line)) + System.err.println(curInput.getLines().toIndexedSeq(line)) } catch { case e: Exception => // ignore } @@ -205,21 +205,21 @@ object PCDataXmlParser { pos += 1 } } - + moveToLT() // scan past if (pos + 1 < len && in.charAt(pos + 1) == '?') { pos += 1 moveToLT() - } + } // scan past if (pos + 1 < len && in.charAt(pos + 1) == '!') { pos += 1 moveToLT() } - + apply(Source.fromString(in.substring(pos))) } } diff --git a/core/util/src/main/scala/net/liftweb/util/SecurityHelpers.scala b/core/util/src/main/scala/net/liftweb/util/SecurityHelpers.scala index 90931317ce..adb1ede659 100644 --- a/core/util/src/main/scala/net/liftweb/util/SecurityHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/SecurityHelpers.scala @@ -14,8 +14,8 @@ * limitations under the License. */ -package net.liftweb -package util +package net.liftweb +package util import org.apache.commons.codec.binary.Base64 import java.io.{InputStream, ByteArrayOutputStream, ByteArrayInputStream, Reader, File, FileInputStream, BufferedReader} @@ -35,20 +35,20 @@ object SecurityHelpers extends StringHelpers with IoHelpers with SecurityHelpers *

  • create SHA, SHA-256, MD5 hashs (can be hex encoded) * */ -trait SecurityHelpers { - self: StringHelpers with IoHelpers => +trait SecurityHelpers { + self: StringHelpers with IoHelpers => /** short alias for java.security.SecureRandom */ private val _random = new SecureRandom - private def withRandom[T](f: SecureRandom => T): T = + private def withRandom[T](f: SecureRandom => T): T = _random.synchronized(f(_random)) /** return a random Long modulo a number */ - def randomLong(mod: Long): Long = withRandom(random => Math.abs(random.nextLong) % mod) + def randomLong(mod: Long): Long = withRandom(random => math.abs(random.nextLong) % mod) /** return a random int modulo a number */ - def randomInt(mod: Int): Int = withRandom(random => Math.abs(random.nextInt) % mod) + def randomInt(mod: Int): Int = withRandom(random => math.abs(random.nextInt) % mod) /** * return true only 'percent' times when asked repeatedly. @@ -98,7 +98,7 @@ trait SecurityHelpers { case (_, null) => false case (a, b) => secureEquals(a.getBytes("UTF-8"), b.getBytes("UTF-8")) } - + /** Compare two byte arrays in a way that does not vary if the arrays * are determined to be not equal early (test every byte... avoids * timing attackes */ @@ -118,7 +118,7 @@ trait SecurityHelpers { ret && la == lb } } - + /** create a SHA-256 hash from a Byte array */ def hash256(in : Array[Byte]) : Array[Byte] = { diff --git a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/Mongo.scala b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/Mongo.scala index 9b9b25278a..59f3af10e4 100644 --- a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/Mongo.scala +++ b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/Mongo.scala @@ -68,6 +68,7 @@ object MongoHost { /* * Wrapper for creating a Replica Pair */ +@deprecated("Use `MongoSet` with `List[ServerAddress]` as argument instead") case class MongoPair(left: ServerAddress, right: ServerAddress, options: MongoOptions = new MongoOptions) extends MongoHostBase { lazy val mongo = new Mongo(left, right, options) } diff --git a/web/testkit/src/main/scala/net/liftweb/mocks/MockHttpServletRequest.scala b/web/testkit/src/main/scala/net/liftweb/mocks/MockHttpServletRequest.scala index e27d0bd9c2..f5088f30f5 100644 --- a/web/testkit/src/main/scala/net/liftweb/mocks/MockHttpServletRequest.scala +++ b/web/testkit/src/main/scala/net/liftweb/mocks/MockHttpServletRequest.scala @@ -56,7 +56,7 @@ import json.JsonAST._ * * @param url The URL to extract from * @param contextPath The context path for this request. Defaults to "" per the Servlet API. - * + * */ class MockHttpServletRequest(val url : String = null, var contextPath : String = "") extends HttpServletRequest { @deprecated("Use the \"attributes\" var instead") @@ -64,7 +64,7 @@ class MockHttpServletRequest(val url : String = null, var contextPath : String = @deprecated("Use the \"attributes\" var instead") def attr_= (attrs : Map[String,Object]) = attributes = attrs - + var attributes: Map[String, Object] = Map() var authType: String = null @@ -85,7 +85,7 @@ class MockHttpServletRequest(val url : String = null, var contextPath : String = /** * Sets the body to the given string. The content * type is set to "text/plain". - * + * * Note that the String will be converted to bytes * based on the current setting of charEncoding. */ @@ -105,7 +105,7 @@ class MockHttpServletRequest(val url : String = null, var contextPath : String = /** * Sets the body to the given elements. Also sets * the contentType to "text/xml" - * + * * Note that the elements will be converted to bytes * based on the current setting of charEncoding. */ @@ -113,7 +113,7 @@ class MockHttpServletRequest(val url : String = null, var contextPath : String = /** * Sets the body to the given elements and content type. - * + * * Note that the elements will be converted to bytes * based on the current setting of charEncoding. */ @@ -210,7 +210,7 @@ class MockHttpServletRequest(val url : String = null, var contextPath : String = if (q != null && q.length > 0) { val newParams = ListBuffer[(String,String)]() - q.split('&').foreach { + q.split('&').foreach { pair => pair.split('=') match { case Array(key,value) => { // Append to the current key's value @@ -219,7 +219,7 @@ class MockHttpServletRequest(val url : String = null, var contextPath : String = case invalid => throw new IllegalArgumentException("Invalid query string: \"" + q + "\"") } } - + parameters = newParams.toList method = "GET" } @@ -249,7 +249,7 @@ class MockHttpServletRequest(val url : String = null, var contextPath : String = var user : String = null var userRoles : Set[String] = Set() - + var userPrincipal : Principal = null var scheme = "http" @@ -339,7 +339,7 @@ class MockHttpServletRequest(val url : String = null, var contextPath : String = *
  • The userinfo field isn't processed. If you want to mock BASIC authentication, * use the addBasicAuth method
  • * - * + * * @param url The URL to extract from * @param contextPath The servlet context of the request. Defaults to "" */ @@ -367,11 +367,11 @@ class MockHttpServletRequest(val url : String = null, var contextPath : String = queryString = url.getQuery - + } /** Compute the path portion after the contextPath */ - def computeRealPath (path : String) = { + def computeRealPath (path : String) = { if (! path.startsWith(contextPath)) { throw new IllegalArgumentException("Path \"%s\" doesn't begin with context path \"%s\"!".format(path, contextPath)) } @@ -390,9 +390,9 @@ class MockHttpServletRequest(val url : String = null, var contextPath : String = // ServletRequest methods - def getAttribute(key: String): Object = attr.get(key).getOrElse(null) + def getAttribute(key: String): Object = attributes.get(key).getOrElse(null) - def getAttributeNames(): JEnum[Object] = attr.keys.iterator + def getAttributeNames(): JEnum[Object] = attributes.keys.iterator def getCharacterEncoding(): String = charEncoding @@ -409,12 +409,12 @@ class MockHttpServletRequest(val url : String = null, var contextPath : String = def getLocale(): Locale = locales.headOption.getOrElse(Locale.getDefault) def getLocales(): JEnum[Object] = locales.iterator - + def getLocalName(): String = localName def getLocalPort(): Int = localPort - - def getParameter(key: String): String = + + def getParameter(key: String): String = parameters.find(_._1 == key).map(_._2) getOrElse null def getParameterMap(): java.util.Map[Object, Object] = { @@ -428,17 +428,17 @@ class MockHttpServletRequest(val url : String = null, var contextPath : String = asMap(newMap.map{case (k,v) => (k,v.toArray)}.asInstanceOf[Map[Object,Object]]) } - def getParameterNames(): JEnum[Object] = + def getParameterNames(): JEnum[Object] = parameters.map(_._1).distinct.iterator - def getParameterValues(key: String): Array[String] = + def getParameterValues(key: String): Array[String] = parameters.filter(_._1 == key).map(_._2).toArray def getProtocol(): String = protocol - - def getReader(): BufferedReader = + + def getReader(): BufferedReader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(body), charEncoding)) - + def getRealPath(s: String): String = s def getRemoteAddr(): String = remoteAddr @@ -457,9 +457,9 @@ class MockHttpServletRequest(val url : String = null, var contextPath : String = def isSecure = secure - def removeAttribute(key: String): Unit = attr -= key + def removeAttribute(key: String): Unit = attributes -= key - def setAttribute(key: String, value: Object): Unit = attr += (key -> value) + def setAttribute(key: String, value: Object): Unit = attributes += (key -> value) def setCharacterEncoding(enc: String): Unit = charEncoding = enc @@ -477,7 +477,7 @@ class MockHttpServletRequest(val url : String = null, var contextPath : String = Empty } } - + Helpers.tryo(handler,{ // Have to use internetDateFormatter directly since parseInternetDate returns the epoch date on failure Box.!!(getHeader(h)).map(Helpers.internetDateFormatter.parse(_).getTime) @@ -509,14 +509,14 @@ class MockHttpServletRequest(val url : String = null, var contextPath : String = def getRemoteUser(): String = user def getRequestedSessionId(): String = null - + def getRequestURI(): String = contextPath + path def getRequestURL(): StringBuffer = { val buffer = new StringBuffer(scheme + "://" + localName) - + if (localPort != 80) buffer.append(":" + localPort) - + if (contextPath != "") buffer.append(contextPath) buffer.append(path) diff --git a/web/webkit/src/main/scala/net/liftweb/http/HtmlProperties.scala b/web/webkit/src/main/scala/net/liftweb/http/HtmlProperties.scala index 5a01a16703..00399b34a3 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/HtmlProperties.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/HtmlProperties.scala @@ -154,7 +154,7 @@ trait HtmlProperties { def userAgent = old.userAgent } } - + /** * Given a NodeSeq and a Writer, convert the output @@ -257,7 +257,7 @@ trait HtmlProperties { def userAgent = newUA } } - + } /** @@ -269,7 +269,7 @@ final case class OldHtmlProperties(userAgent: Box[String]) extends HtmlPropertie * setDocType. */ def docType: Box[String] = LiftRules.docType.vend.apply(S.request openOr Req.nil) - def encoding: Box[String] = + def encoding: Box[String] = Full(LiftRules.calculateXmlHeader(null, , contentType)) def contentType: Box[String] = { @@ -294,10 +294,10 @@ final case class OldHtmlProperties(userAgent: Box[String]) extends HtmlPropertie w.append(sb) w.flush() } - - def htmlOutputHeader: Box[String] = + + def htmlOutputHeader: Box[String] = (docType -> encoding) match { - case (Full(dt), Full(enc)) if dt.length > 0 && enc.length > 0 => + case (Full(dt), Full(enc)) if dt.length > 0 && enc.length > 0 => if (LiftRules.calcIE6ForResponse() && LiftRules.flipDocTypeForIE6) { Full(dt.trim + "\n" + enc.trim + "\n") } else { @@ -317,7 +317,7 @@ final case class OldHtmlProperties(userAgent: Box[String]) extends HtmlPropertie r.isChrome5 || r.isChrome6 } - val maxOpenRequests: Int = + val maxOpenRequests: Int = LiftRules.maxConcurrentRequests.vend(S.request openOr Req.nil) } @@ -337,7 +337,7 @@ final case class Html5Properties(userAgent: Box[String]) extends HtmlProperties def htmlWriter: (Node, Writer) => Unit = Html5.write(_, _, false, !LiftRules.convertToEntity.vend) - + def htmlOutputHeader: Box[String] = docType.map(_.trim + "\n") val html5FormsSupport: Boolean = { @@ -346,7 +346,7 @@ final case class Html5Properties(userAgent: Box[String]) extends HtmlProperties r.isChrome5 || r.isChrome6 } - val maxOpenRequests: Int = + val maxOpenRequests: Int = LiftRules.maxConcurrentRequests.vend(S.request openOr Req.nil) } @@ -369,7 +369,7 @@ final case class XHtmlInHtml5OutProperties(userAgent: Box[String]) extends HtmlP def htmlWriter: (Node, Writer) => Unit = Html5.write(_, _, false, !LiftRules.convertToEntity.vend) - + def htmlOutputHeader: Box[String] = docType.map(_ + "\n") val html5FormsSupport: Boolean = { @@ -378,7 +378,7 @@ final case class XHtmlInHtml5OutProperties(userAgent: Box[String]) extends HtmlP r.isChrome5 || r.isChrome6 } - val maxOpenRequests: Int = + val maxOpenRequests: Int = LiftRules.maxConcurrentRequests.vend(S.request openOr Req.nil) } diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 0ac9b85949..a09d84b4d3 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -457,8 +457,6 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { /** * Set the doc type used. Use the HtmlProperties - * - * @deprecated */ @deprecated("Use the HtmlProperties") val docType: FactoryMaker[Req => Box[String]] = new FactoryMaker( (r: Req) => r match { diff --git a/web/webkit/src/main/scala/net/liftweb/http/S.scala b/web/webkit/src/main/scala/net/liftweb/http/S.scala index 9dd55de15e..d92f0e1107 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/S.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/S.scala @@ -218,13 +218,13 @@ object S extends S { } /** - * We create one of these dudes and put it + * We create one of these dudes and put it */ private[http] final case class PageStateHolder(owner: Box[String], session: LiftSession) extends AFuncHolder { private val loc = S.location private val snapshot: Function1[Function0[Any], Any] = RequestVarHandler.generateSnapshotRestorer() override def sessionLife: Boolean = false - + def apply(in: List[String]): Any = { error("You shouldn't really be calling apply on this dude...") } @@ -365,7 +365,7 @@ trait S extends HasParams with Loggable { * We can now collect Elems to put at the end of the body */ private object _tailTags extends TransientRequestVar(new ListBuffer[Elem]) - + private object p_queryLog extends TransientRequestVar(new ListBuffer[(String, Long)]) private object p_notice extends TransientRequestVar(new ListBuffer[(NoticeType.Value, NodeSeq, Box[String])]) @@ -574,7 +574,7 @@ trait S extends HasParams with Loggable { * Get the current instance of HtmlProperties */ def htmlProperties: HtmlProperties = { - session.map(_.requestHtmlProperties.is) openOr + session.map(_.requestHtmlProperties.is) openOr LiftRules.htmlProperties.vend( S.request openOr Req.nil ) @@ -896,7 +896,7 @@ trait S extends HasParams with Loggable { */ def loc(str: String, dflt: NodeSeq): NodeSeq = loc(str).openOr(dflt) - + /** * Localize the incoming string based on a resource bundle for the current locale. The * localized string is converted to an XML element if necessary via the LiftRules.localizeStringToXml @@ -1236,12 +1236,12 @@ for { def init[B](request: Req, session: LiftSession)(f: => B): B = { if (inS.value) f else { - if (request.stateless_?) + if (request.stateless_?) session.doAsStateless(_init(request, session)(() => f)) else _init(request, session)(() => f) } } - + def statelessInit[B](request: Req)(f: => B): B = { session match { case Full(s) if s.stateful_? => { @@ -1249,13 +1249,13 @@ for { "Attempt to initialize a stateless session within the context "+ "of a stateful session") } - + case Full(_) => f case _ => { val fakeSess = LiftRules.statelessSession.vend.apply(request) try { - _init(request, + _init(request, fakeSess)(() => f) } finally { // ActorPing.schedule(() => fakeSess.doShutDown(), 0 seconds) @@ -1577,11 +1577,11 @@ for { session match { case Full(s) if s.stateful_? => LiftRules.earlyInStateful.toList.foreach(_(req)) - - case Full(s) => + + case Full(s) => LiftRules.earlyInStateless.toList.foreach(_(req)) - case _ => + case _ => } f } @@ -1595,9 +1595,9 @@ for { private def doStatefulRewrite(old: Req): Req = { // Don't even try to rewrite Req.nil if (statefulRequest_? && - !old.path.partPath.isEmpty && + !old.path.partPath.isEmpty && (old.request ne null)) - Req(old, S.sessionRewriter.map(_.rewrite) ::: + Req(old, S.sessionRewriter.map(_.rewrite) ::: LiftRules.statefulRewrite.toList, LiftRules.statelessTest.toList, LiftRules.statelessReqTest.toList) else old @@ -2035,7 +2035,7 @@ for { /** * Retrieves the attributes from the most recently executed * snippet element. - * + * * For example, given the snippets: * *
    ) openOr NodeSeq.Empty + } } private final def findNSAttr(attrs: MetaData, prefix: String, key: String): Option[Seq[Node]] = @@ -1927,39 +1937,50 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, else f } + private object _lastFoundSnippet extends ThreadGlobal[String] + /** * Processes the surround tag and other lift tags + * + * @param page the name of the page currently being processed + * @param in the DOM to process */ def processSurroundAndInclude(page: String, in: NodeSeq): NodeSeq = { - in.flatMap { - case Group(nodes) => - Group(processSurroundAndInclude(page, nodes)) - - case SnippetNode(element, kids, isLazy, attrs, snippetName) => - processOrDefer(isLazy) { - S.doSnippet(snippetName) { - S.withAttrs(attrs) { - processSurroundAndInclude(page, - NamedPF((snippetName, - element, attrs, - kids, - page), - liftTagProcessing)) + try { + in.flatMap { + case Group(nodes) => + Group(processSurroundAndInclude(page, nodes)) + + case elem @ SnippetNode(element, kids, isLazy, attrs, snippetName) if snippetName != _lastFoundSnippet.value => + processOrDefer(isLazy) { + S.withCurrentSnippetNodeSeq(elem) { + S.doSnippet(snippetName) { + S.withAttrs(attrs) { + processSurroundAndInclude(page, + NamedPF((snippetName, + element, attrs, + kids, + page), + liftTagProcessing)) + } + } } } - } - case v: Elem => - Elem(v.prefix, v.label, processAttributes(v.attributes, this.allowAttributeProcessing.is), - v.scope, processSurroundAndInclude(page, v.child): _*) + case v: Elem => + Elem(v.prefix, v.label, processAttributes(v.attributes, this.allowAttributeProcessing.is), + v.scope, processSurroundAndInclude(page, v.child): _*) - case pcd: scala.xml.PCData => pcd - case text: Text => text - case unparsed: Unparsed => unparsed + case pcd: scala.xml.PCData => pcd + case text: Text => text + case unparsed: Unparsed => unparsed - case a: Atom[Any] if (a.getClass == classOf[Atom[Any]]) => new Text(a.data.toString) + case a: Atom[Any] if (a.getClass == classOf[Atom[Any]]) => new Text(a.data.toString) - case v => v + case v => v + } + } finally { + _lastFoundSnippet.set(null) } } diff --git a/web/webkit/src/main/scala/net/liftweb/http/S.scala b/web/webkit/src/main/scala/net/liftweb/http/S.scala index fc7502849e..31d9b250b1 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/S.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/S.scala @@ -2278,6 +2278,52 @@ for { NamedPF.applyBox(snippet, LiftRules.snippets.toList) } + + private object _currentSnippetNodeSeq extends ThreadGlobal[NodeSeq] + + /** + * The code block is executed while setting the current raw NodeSeq + * for access elsewhere + * + * @param ns the current NodeSeq + * @param f the call-by-name value to return (the code block to execute) + * @tparam T the type that f returns + * @return the value the the expression returns + */ + def withCurrentSnippetNodeSeq[T](ns: NodeSeq)(f: => T): T = + _currentSnippetNodeSeq.doWith(ns)(f) + + /** + * The current raw NodeSeq that resulted in a snippet invocation + * @return The current raw NodeSeq that resulted in a snippet invocation + */ + def currentSnippetNodeSeq: Box[NodeSeq] = _currentSnippetNodeSeq.box + + private object _ignoreFailedSnippets extends ThreadGlobal[Boolean] + + /** + * Set the ignore snippet error mode. In this mode, any + * snippet failures (usually snippets not being invocable) will be + * ignored and the original NodeSeq will be returned. + * + * This is useful if you want to do an initial pass of a page with a white-list + * of snippets, but not run every snippet on the page. + * + * @param ignore sets the ignore flag + * @param f the code block to execute + * @tparam T the return type of the code block + * @return the return of the code block + */ + def runSnippetsWithIgnoreFailed[T](ignore: Boolean)(f: => T): T = + _ignoreFailedSnippets.doWith(ignore)(f) + + /** + * + * @return should failed snippets be ignored and have the original NodeSeq + * returned? + */ + def ignoreFailedSnippets: Boolean = _ignoreFailedSnippets.box openOr false + private object _currentSnippet extends RequestVar[Box[String]](Empty) private[http] def doSnippet[T](name: String)(f: => T): T = { From 45d285d9b1f5556605004078198052b250ab2779 Mon Sep 17 00:00:00 2001 From: David Pollak Date: Thu, 2 Aug 2012 08:48:50 -0700 Subject: [PATCH 0143/1949] Minor tuning of the ignore failure snippet mode --- web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index 78bff90dc6..dc206fd6c2 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -1461,12 +1461,11 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, why: LiftRules.SnippetFailures.Value, addlMsg: NodeSeq, whole: NodeSeq): NodeSeq = { - (for { nodeSeq <- S.currentSnippetNodeSeq if S.ignoreFailedSnippets } yield { // don't keep nailing the same snippet name if we just failed it - S.currentSnippet.foreach(s => _lastFoundSnippet.set(s)) + (snippetName or S.currentSnippet).foreach(s => _lastFoundSnippet.set(s)) nodeSeq }) openOr { From 006a1efbe07659d90628082ed7d4d3598671e202 Mon Sep 17 00:00:00 2001 From: David Pollak Date: Tue, 14 Aug 2012 16:27:00 -0700 Subject: [PATCH 0144/1949] Added the ability to do mail attachments as well as inline items --- core/util/src/main/scala/net/liftweb/util/Mailer.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/Mailer.scala b/core/util/src/main/scala/net/liftweb/util/Mailer.scala index 88b69819ee..cc7b6f637b 100644 --- a/core/util/src/main/scala/net/liftweb/util/Mailer.scala +++ b/core/util/src/main/scala/net/liftweb/util/Mailer.scala @@ -43,7 +43,7 @@ trait Mailer extends SimpleInjector { */ final case class MessageHeader(name: String, value: String) extends MailTypes abstract class MailBodyType extends MailTypes - final case class PlusImageHolder(name: String, mimeType: String, bytes: Array[Byte]) + final case class PlusImageHolder(name: String, mimeType: String, bytes: Array[Byte], attachment: Boolean = false) /** * Represents a text/plain mail body. The given text will @@ -60,6 +60,7 @@ trait Mailer extends SimpleInjector { final case class XHTMLMailBodyType(text: NodeSeq) extends MailBodyType final case class XHTMLPlusImages(text: NodeSeq, items: PlusImageHolder*) extends MailBodyType + sealed abstract class RoutingType extends MailTypes sealed abstract class AddressType extends RoutingType { def address: String @@ -283,6 +284,7 @@ trait Mailer extends SimpleInjector { case PlainMailBodyType(txt) => bp.setText(txt, "UTF-8") case PlainPlusBodyType(txt, charset) => bp.setText(txt, charset) case XHTMLMailBodyType(html) => bp.setContent(encodeHtmlBodyPart(html), "text/html; charset=" + charSet) + case XHTMLPlusImages(html, img@_*) => val html_mp = new MimeMultipart("related") val bp2 = new MimeBodyPart @@ -293,7 +295,7 @@ trait Mailer extends SimpleInjector { val rel_bpi = new MimeBodyPart rel_bpi.setFileName(i.name) rel_bpi.setContentID(i.name) - rel_bpi.setDisposition("inline") + rel_bpi.setDisposition(if (!i.attachment) "inline" else "attachment") rel_bpi.setDataHandler(new javax.activation.DataHandler(new javax.activation.DataSource { def getContentType = i.mimeType From 4b61567f2ee9225e770e8e299273f75b73a22ec5 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 18 Aug 2012 14:37:20 -0400 Subject: [PATCH 0145/1949] Add !< to LiftCometActor. CometActor had the function by virtue of extending Lift's actor traits; however, the LiftCometActor trait that is often used to reference it didn't define that the method would be present. We add it there so it can be referred to anywhere that LiftCometActor is passed around instead of CometActor. !< returns an LAFuture for a message reply. --- web/webkit/src/main/scala/net/liftweb/http/CometActor.scala | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala b/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala index 6ad032fd93..a382f42ce9 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala @@ -399,6 +399,12 @@ trait LiftCometActor extends TypedActor[Any, Any] with ForwardableActor[Any, Any initCometActor(theSession, theType, name, defaultHtml, attributes) } + /** + * Asynchronous message send. Send-and-receive eventually. Returns a Future for the reply message. + */ + def !<(msg: Any): LAFuture[Any] + + /** * Override in sub-class to customise timeout for the render()-method for the specific comet */ From bbd703dcad3296e211354c9a521707c8f2fc0b31 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 18 Aug 2012 14:40:21 -0400 Subject: [PATCH 0146/1949] Include a version on the end of the Ajax GUID. AJAX requests currently carry a GUID. We now add a single-character version indicator. This version increments for distinct AJAX requests. In particular, it does NOT increment during AJAX retries, so that a retry of an existing request can be identified as being part of the same attempt on the server. Not all AJAX requests carry this version identifier. In particular, Lift GC requests do not carry a version identifier. This is because these are going to be handled in a streamlined handler. If a Lift GC request doesn't make it through, we can retry it as many times as we want and it'll just remark stuff in the session. liftAjax.addPageNameAndVersion appends both the page GUID and the version number. The version number is encoded in base-36. --- .../net/liftweb/http/js/JSArtifacts.scala | 31 ++++++++++++++----- .../net/liftweb/http/js/ScriptRenderer.scala | 23 +++++++++++--- .../http/js/extcore/ExtCoreArtifacts.scala | 2 +- .../http/js/jquery/JQueryArtifacts.scala | 8 ++++- .../liftweb/http/js/yui/YUIArtifacts.scala | 8 ++++- 5 files changed, 57 insertions(+), 15 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/js/JSArtifacts.scala b/web/webkit/src/main/scala/net/liftweb/http/js/JSArtifacts.scala index d719954026..b75de40ac6 100755 --- a/web/webkit/src/main/scala/net/liftweb/http/js/JSArtifacts.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/js/JSArtifacts.scala @@ -112,24 +112,24 @@ trait JSArtifacts { } /** - * The companion module for AjaxInfo that provides + * The companion module for AjaxInfodd that provides * different construction schemes */ object AjaxInfo { def apply(data: JsExp, post: Boolean) = - new AjaxInfo(data, if (post) "POST" else "GET", 1000, false, "script", Empty, Empty) + new AjaxInfo(data, if (post) "POST" else "GET", 1000, false, "script", Empty, Empty, true) def apply(data: JsExp, dataType: String, post: Boolean) = - new AjaxInfo(data, if (post) "POST" else "GET", 1000, false, dataType, Empty, Empty) + new AjaxInfo(data, if (post) "POST" else "GET", 1000, false, dataType, Empty, Empty, true) def apply(data: JsExp) = - new AjaxInfo(data, "POST", 1000, false, "script", Empty, Empty) + new AjaxInfo(data, "POST", 1000, false, "script", Empty, Empty, true) def apply(data: JsExp, dataType: String) = - new AjaxInfo(data, "POST", 1000, false, dataType, Empty, Empty) + new AjaxInfo(data, "POST", 1000, false, dataType, Empty, Empty, true) def apply(data: JsExp, post: Boolean, @@ -142,7 +142,23 @@ object AjaxInfo { false, "script", Full(successFunc), - Full(failFunc)) + Full(failFunc), + true) + + def apply(data: JsExp, + post: Boolean, + timeout: Long, + successFunc: String, + failFunc: String, + includeVersion: Boolean) = + new AjaxInfo(data, + if (post) "POST" else "GET", + timeout, + false, + "script", + Full(successFunc), + Full(failFunc), + includeVersion) } /** @@ -150,5 +166,6 @@ object AjaxInfo { */ case class AjaxInfo(data: JsExp, action: String, timeout: Long, cache: Boolean, dataType: String, - successFunc: Box[String], failFunc: Box[String]) + successFunc: Box[String], failFunc: Box[String], + includeVersion: Boolean) diff --git a/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala b/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala index 4b9d536fdc..00bbef286a 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala @@ -107,8 +107,7 @@ object ScriptRenderer { "POST", LiftRules.ajaxPostTimeout, false, "script", - Full("liftAjax.lift_successRegisterGC"), Full("liftAjax.lift_failRegisterGC"))) + - """ + Full("liftAjax.lift_successRegisterGC"), Full("liftAjax.lift_failRegisterGC"), false)) + """ }, @@ -132,6 +131,7 @@ object ScriptRenderer { aboutToSend.onSuccess(data); } liftAjax.lift_doCycleQueueCnt++; + liftAjax.lift_ajaxVersion++; liftAjax.lift_doAjaxCycle(); }; @@ -145,6 +145,7 @@ object ScriptRenderer { queue.push(aboutToSend); liftAjax.lift_ajaxQueueSort(); } else { + liftAjax.lift_ajaxVersion++; if (aboutToSend.onFailure) { aboutToSend.onFailure(); } else { @@ -180,6 +181,18 @@ object ScriptRenderer { setTimeout("liftAjax.lift_doAjaxCycle();", 200); }, + lift_ajaxVersion: 0, + + addPageNameAndVersion: function(url) { + return """ + { + if (LiftRules.enableLiftGC) { + "url.replace('" + LiftRules.ajaxPath + "', '" + LiftRules.ajaxPath + "/'+lift_page+(liftAjax.lift_ajaxVersion % 36).toString(36));" + } else { + "url;" + } + } + """ + }, + addPageName: function(url) { return """ + { if (LiftRules.enableLiftGC) { @@ -196,7 +209,7 @@ object ScriptRenderer { "POST", LiftRules.ajaxPostTimeout, false, "script", - Full("onSuccess"), Full("onFailure"))) + + Full("onSuccess"), Full("onFailure"), true)) + """ }, @@ -206,7 +219,7 @@ object ScriptRenderer { "POST", LiftRules.ajaxPostTimeout, false, "json", - Full("onSuccess"), Full("onFailure"))) + + Full("onSuccess"), Full("onFailure"), true)) + """ } }; @@ -279,7 +292,7 @@ object ScriptRenderer { false, "script", Full("liftComet.lift_handlerSuccessFunc"), - Full("liftComet.lift_handlerFailureFunc"))) + + Full("liftComet.lift_handlerFailureFunc"), false)) + """ } } diff --git a/web/webkit/src/main/scala/net/liftweb/http/js/extcore/ExtCoreArtifacts.scala b/web/webkit/src/main/scala/net/liftweb/http/js/extcore/ExtCoreArtifacts.scala index ee36f9d709..b1de61371d 100755 --- a/web/webkit/src/main/scala/net/liftweb/http/js/extcore/ExtCoreArtifacts.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/js/extcore/ExtCoreArtifacts.scala @@ -148,7 +148,7 @@ object ExtCoreArtifacts extends JSArtifacts { } private def toJson(info: AjaxInfo, server: String, path: String => JsExp): String = - (("url : liftAjax.addPageName(" + path(server).toJsCmd + ")" ) :: + (("url : liftAjax.addPageNameAndVersion(" + path(server).toJsCmd + ")" ) :: "params : " + info.data.toJsCmd :: ("method : " + info.action.encJs) :: ("dataType : " + info.dataType.encJs) :: diff --git a/web/webkit/src/main/scala/net/liftweb/http/js/jquery/JQueryArtifacts.scala b/web/webkit/src/main/scala/net/liftweb/http/js/jquery/JQueryArtifacts.scala index 346c554542..1b8cc1de81 100755 --- a/web/webkit/src/main/scala/net/liftweb/http/js/jquery/JQueryArtifacts.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/js/jquery/JQueryArtifacts.scala @@ -95,9 +95,15 @@ trait JQueryArtifacts extends JSArtifacts { * attributes described by data parameter */ def ajax(data: AjaxInfo): String = { + val versionIncluder = + if (data.includeVersion) + "liftAjax.addPageNameAndVersion" + else + "liftAjax.addPageName" + "jQuery.ajax(" + toJson(data, S.contextPath, prefix => - JsRaw("liftAjax.addPageName(" + S.encodeURL(prefix + "/" + LiftRules.ajaxPath + "/").encJs + ")")) + ");" + JsRaw(versionIncluder + "(" + S.encodeURL(prefix + "/" + LiftRules.ajaxPath + "/").encJs + ")")) + ");" } /** diff --git a/web/webkit/src/main/scala/net/liftweb/http/js/yui/YUIArtifacts.scala b/web/webkit/src/main/scala/net/liftweb/http/js/yui/YUIArtifacts.scala index a3ce7214a1..2ca7011894 100755 --- a/web/webkit/src/main/scala/net/liftweb/http/js/yui/YUIArtifacts.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/js/yui/YUIArtifacts.scala @@ -126,9 +126,15 @@ object YUIArtifacts extends JSArtifacts { * attributes described by data parameter */ def ajax(data: AjaxInfo): String = { + val versionIncluder = + if (data.includeVersion) + "liftAjax.addPageNameAndVersion" + else + "liftAjax.addPageName" + val url = S.encodeURL(S.contextPath + "/" + LiftRules.ajaxPath + "/") - "url = YAHOO.lift.buildURI(liftAjax.addPageName(" + url.encJs + ") , " + data.data.toJsCmd + ");" + + "url = YAHOO.lift.buildURI(" + versionIncluder + "(" + url.encJs + ") , " + data.data.toJsCmd + ");" + "YAHOO.util.Connect.asyncRequest(" + data.action.encJs + ", url, " + toJson(data) + ");" } From 0f7b081642948f8b3ca0c75af77936101d3323d3 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 18 Aug 2012 15:29:39 -0400 Subject: [PATCH 0147/1949] Rename continuation-related code to clarify Comet association. We're about to add some AJAX-related continuation code, so we want it to be clear the existing stuff is for comets. --- .../scala/net/liftweb/http/LiftServlet.scala | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala index 8bafb9ce59..9622f6f5eb 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala @@ -496,10 +496,10 @@ class LiftServlet extends Loggable { } } -/** - * An actor that manages continuations from container (Jetty style) + /** + * An actor that manages comet continuations from container (Jetty style) */ - class ContinuationActor(request: Req, session: LiftSession, + class CometContinuationActor(request: Req, session: LiftSession, actors: List[(LiftCometActor, Long)], onBreakout: List[AnswerRender] => Unit) extends LiftActor { private var answers: List[AnswerRender] = Nil @@ -529,15 +529,15 @@ class LiftServlet extends Loggable { case _ => } - override def toString = "Actor dude " + seqId + override def toString = "Continuation Actor dude " + seqId } private object BeginContinuation private lazy val cometTimeout: Long = (LiftRules.cometRequestTimeout openOr 120) * 1000L - private def setupContinuation(request: Req, session: LiftSession, actors: List[(LiftCometActor, Long)]): Any = { - val cont = new ContinuationActor(request, session, actors, + private def setupCometContinuation(request: Req, session: LiftSession, actors: List[(LiftCometActor, Long)]): Any = { + val cont = new CometContinuationActor(request, session, actors, answers => request.request.resume( (request, S.init(request, session) (LiftRules.performTransform( @@ -566,7 +566,7 @@ class LiftServlet extends Loggable { if (actors.isEmpty) Left(Full(new JsCommands(LiftRules.noCometSessionCmd.vend :: js.JE.JsRaw("lift_toWatch = {};").cmd :: Nil).toResponse)) else requestState.request.suspendResumeSupport_? match { case true => { - setupContinuation(requestState, sessionActor, actors) + setupCometContinuation(requestState, sessionActor, actors) Left(Full(EmptyResponse)) } @@ -614,7 +614,7 @@ class LiftServlet extends Loggable { private def handleNonContinuationComet(request: Req, session: LiftSession, actors: List[(LiftCometActor, Long)], originalRequest: Req): () => Box[LiftResponse] = () => { val f = new LAFuture[List[AnswerRender]] - val cont = new ContinuationActor(request, session, actors, + val cont = new CometContinuationActor(request, session, actors, answers => f.satisfy(answers)) try { From ba0b3130949459fbcb3dc07d89e23106c046aa03 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 18 Aug 2012 15:30:41 -0400 Subject: [PATCH 0148/1949] AJAX version-based deduplication. The meat of the deal. Based on the AJAX version appended to the request GUID, we determine whether we've already seen this request. If so, we wait for the original request to complete before returning the resulting value. If we already completed the request before, we return the same answer without re-running the associated parameters. AJAX requests that need to wait are put into continuations if available. --- .../scala/net/liftweb/http/LiftServlet.scala | 321 ++++++++++++++++-- .../scala/net/liftweb/http/LiftSession.scala | 24 +- 2 files changed, 305 insertions(+), 40 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala index 9622f6f5eb..e8e42c2290 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala @@ -416,48 +416,146 @@ class LiftServlet extends Loggable { toReturn } - private def extractVersion[T](path: List[String])(f: => T): T = { + private object AjaxVersions { + def unapply(ajaxPathPart: String) : Option[(String,Int)] = { + val funcLength = Helpers.nextFuncName.length + if (ajaxPathPart.length > funcLength) + Some( + (ajaxPathPart.substring(0, funcLength), + ajaxPathPart.charAt(funcLength)) + ) + else + None + } + } + /** + * Extracts two versions from a given AJAX path: + * - The RenderVersion, which is used for GC purposes. + * - The requestVersion, which lets us determine if this is + * a request we've already dealt with or are currently dealing + * with (so we don't rerun the associated handler). See + * handleVersionedAjax for more. + * + * The requestVersion is passed to the function that is passed in. + */ + private def extractVersions[T](path: List[String])(f: (Box[Int]) => T): T = { path match { - case first :: second :: _ => RenderVersion.doWith(second)(f) - case _ => f + case first :: AjaxVersions(renderVersion, requestVersion) :: _ => + RenderVersion.doWith(renderVersion)(f(Full(requestVersion))) + case _ => f(Empty) } } - private def handleAjax(liftSession: LiftSession, - requestState: Req): Box[LiftResponse] = { - extractVersion(requestState.path.partPath) { + /** + * An actor that manages AJAX continuations from container (Jetty style). + */ + class AjaxContinuationActor(request: Req, session: LiftSession, + onBreakout: Box[LiftResponse] => Unit) extends LiftActor { + private var response: Box[LiftResponse] = Empty + private var done = false - LiftRules.cometLogger.debug("AJAX Request: " + liftSession.uniqueId + " " + requestState.params) - tryo { - LiftSession.onBeginServicing.foreach(_(liftSession, requestState)) - } + def messageHandler = { + case AjaxResponseComplete(completeResponse) => + response = completeResponse + LAPinger.schedule(this, BreakOut(), 5 millis) - val ret = try { - requestState.param("__lift__GC") match { - case Full(_) => - liftSession.updateFuncByOwner(RenderVersion.get, millis) - Full(JavaScriptResponse(js.JsCmds.Noop)) + case BreakOut() if ! done => + done = true + session.exitComet(this) + onBreakout(response) - case _ => - try { - val what = flatten(try { - liftSession.runParams(requestState) - } catch { - case ResponseShortcutException(_, Full(to), _) => - import js.JsCmds._ - List(RedirectTo(to)) - }) + case _ => + } + + override def toString = "AJAX Continuation Actor" + } + + private case class AjaxResponseComplete(response: Box[LiftResponse]) + + /** + * existingResponse is Empty if we have no response for this request + * yet. pendingActors is a list of actors who want to be notified when + * this response is received. + */ + private case class AjaxRequestInfo(requestVersion:Int, existingResponse:Box[Box[LiftResponse]], pendingActors:List[LiftActor]) + + private lazy val ajaxPostTimeout: Long = LiftRules.ajaxPostTimeout * 1000L + private val currentAjaxRequests = scala.collection.mutable.Map[String,AjaxRequestInfo]() - val what2 = what.flatMap { + /** + * Runs the actual AJAX processing. This includes handling __lift__GC, + * or running the parameters in the session. onComplete is run when the + * AJAX request has completed with a response that is meant for the + * user. In cases where the request is taking too long to respond, + * an LAFuture may be used to delay the real response (and thus the + * invocation of onComplete) while this function returns an empty + * response. + */ + private def runAjax(requestState: Req, + liftSession: LiftSession, + onCompleteFn: Box[(LiftResponse)=>Unit]): Box[LiftResponse] = { + // We don't want to call the onComplete function if we're returning + // an error response, as if there is a comet timeout while + // processing. This is because the onComplete function is meant to + // indicate a successful completion, and will short-circuit any + // other AJAX requests with the same version with the same + // response. Error responses are specific to the AJAX request, + // rather than to the version of the AJAX request. Successful + // responses are for all AJAX requests with the same version. + var callCompleteFn = true + + val ret = try { + requestState.param("__lift__GC") match { + case Full(_) => + val renderVersion = RenderVersion.get + liftSession.updateFuncByOwner(renderVersion, millis) + + // FIXME We need to clean up currentAjaxRequests entries when + // FIXME the page expires. + + Full(JavaScriptResponse(js.JsCmds.Noop)) + + case _ => + try { + val what = flatten(try { + liftSession.runParams(requestState) + } catch { + case ResponseShortcutException(_, Full(to), _) => + import js.JsCmds._ + List(RedirectTo(to)) + }) + + def processResponse(response: List[Any]): LiftResponse = { + val what2 = response.flatMap { case js: JsCmd => List(js) case jv: JValue => List(jv) case n: NodeSeq => List(n) case js: JsCommands => List(js) case r: LiftResponse => List(r) + + // This strange case comes from ajax requests run in + // a comet context that don't complete in the + // cometTimeout time frame. The js is the result of the + // comet processing timeout handler, while the future + // gives us access to the eventual result of the ajax + // request. + case (js, future: LAFuture[List[Any]]) if onCompleteFn.isDefined => + // Wait for the future on a separate thread. + Schedule.schedule(() => { + val result = flatten(future.get) + onCompleteFn.foreach(_(processResponse(result))) + }, 0 seconds) + + // But this request is done for. Return the comet + // processing timeout result, but don't mark the + // request complete; that happens whenever we satisfy + // the future above. + callCompleteFn = false + List(js) case s => Nil } - val ret: LiftResponse = what2 match { + what2 match { case (json: JsObj) :: Nil => JsonResponse(json) case (jv: JValue) :: Nil => JsonResponse(jv) case (js: JsCmd) :: xs => { @@ -474,21 +572,174 @@ class LiftServlet extends Loggable { case (r: LiftResponse) :: _ => r case _ => JsCommands(S.noticesToJsCmd :: JsCmds.Noop :: S.jsToAppend).toResponse } + } - LiftRules.cometLogger.debug("AJAX Response: " + liftSession.uniqueId + " " + ret) + val ret: LiftResponse = processResponse(what) - Full(ret) - } finally { - if (S.functionMap.size > 0) { - liftSession.updateFunctionMap(S.functionMap, RenderVersion.get, millis) - S.clearFunctionMap + LiftRules.cometLogger.debug("AJAX Response: " + liftSession.uniqueId + " " + ret) + + Full(ret) + } finally { + if (S.functionMap.size > 0) { + liftSession.updateFunctionMap(S.functionMap, RenderVersion.get, millis) + S.clearFunctionMap + } + } + } + } catch { + case foc: LiftFlowOfControlException => throw foc + case e => S.assertExceptionThrown() ; NamedPF.applyBox((Props.mode, requestState, e), LiftRules.exceptionHandler.toList); + } + + for { + response <- ret if callCompleteFn + onComplete <- onCompleteFn + } { + onComplete(response) + } + + ret + } + + /** + * Handles the details of versioned AJAX requests. If the version is + * Empty, runs the request through a normal AJAX flow with no + * continuations or special handling. Repeated calls will cause + * repeated evaluation. + * + * If the version is Full, the request is tracked. Subsequent requests + * for the same version will suspend until a response is available, + * then they will return the response. + * + * cont is the AjaxContinuationActor for this request, and + * suspendRequest is the function that will suspend the request. + * These are present to support non-continuation containers; these + * will present a no-op to suspendRequest. + */ + private def handleVersionedAjax(handlerVersion: Box[Int], + cont: AjaxContinuationActor, + suspendRequest: () => Any, + requestState: Req, + liftSession: LiftSession): Box[LiftResponse] = { + handlerVersion match { + case Full(handlerVersion) => + val renderVersion = RenderVersion.get + + val toReturn: Box[Box[LiftResponse]] = + currentAjaxRequests.synchronized { + currentAjaxRequests.get(renderVersion).collect { + case AjaxRequestInfo(storedVersion, _, pendingActors) if handlerVersion != storedVersion => + // Break out of any actors for the stale version. + pendingActors.foreach(_ ! BreakOut()) + + // Evict the older version's info. + currentAjaxRequests += + (renderVersion -> + AjaxRequestInfo(handlerVersion, Empty, cont :: Nil)) + + suspendRequest() + + Empty // no response available, triggers the actual AJAX computation below + + case AjaxRequestInfo(storedVersion, existingResponseBox @ Full(_), _) => + existingResponseBox // return the Full response Box + + case AjaxRequestInfo(storedVersion, _, pendingActors) => + currentAjaxRequests += + (renderVersion -> + AjaxRequestInfo(handlerVersion, Empty, cont :: pendingActors)) + + suspendRequest() + + Full(Full(EmptyResponse)) + } openOr { + currentAjaxRequests += + (renderVersion -> + AjaxRequestInfo(handlerVersion, Empty, cont :: Nil)) + + suspendRequest() + + Empty // no response available, triggers the actual AJAX computation below + } + } + + toReturn or { + Full(runAjax(requestState, liftSession, Full((result: LiftResponse) => { + // When we get the response, synchronizedly check that the + // versions are still the same in the map, and, if so, update + // any waiting actors then clear the actor list and update the + // request info to include the response in case any other + // requests come in with this version. + currentAjaxRequests.synchronized { + currentAjaxRequests.get(renderVersion).collect { + case AjaxRequestInfo(storedVersion, _, pendingActors) if storedVersion == handlerVersion => + pendingActors.foreach(_ ! AjaxResponseComplete(Full(result))) + currentAjaxRequests += + (renderVersion -> + AjaxRequestInfo(handlerVersion, Full(Full(result)), Nil)) } } + }))) + } openOr Empty + + case _ => + runAjax(requestState, liftSession, Empty) + } + } + + /** + * Kick off AJAX handling. Extracts relevant versions and handles the + * begin/end servicing requests, as well as generation of + * ContinuationActors and choosing between continuation and + * continuationless request handling. + */ + private def handleAjax(liftSession: LiftSession, + requestState: Req): Box[LiftResponse] = { + extractVersions(requestState.path.partPath) { handlerVersion => + LiftRules.cometLogger.debug("AJAX Request: " + liftSession.uniqueId + " " + requestState.params) + tryo { + LiftSession.onBeginServicing.foreach(_(liftSession, requestState)) + } + + def suspendingActor = { + new AjaxContinuationActor(requestState, liftSession, + response => { + requestState.request.resume((requestState, S.init(requestState, liftSession) + (response.map(LiftRules.performTransform) openOr EmptyResponse)))}) + } + + def waitingActorForFuture(future: LAFuture[Box[LiftResponse]]) = { + new AjaxContinuationActor(requestState, liftSession, + response => future.satisfy(response)) + } + + val possibleFuture = + if (requestState.request.suspendResumeSupport_?) + Empty + else + Full(new LAFuture[Box[LiftResponse]]) + val (cont, suspendRequest) = + possibleFuture.map { f => + (waitingActorForFuture(f), () => ()) + } openOr { + (suspendingActor, () => requestState.request.suspend(ajaxPostTimeout + 500L)) } - } catch { - case foc: LiftFlowOfControlException => throw foc - case e => S.assertExceptionThrown() ; NamedPF.applyBox((Props.mode, requestState, e), LiftRules.exceptionHandler.toList); + + val ret: Box[LiftResponse] = try { + liftSession.enterComet(cont -> requestState) + + val result: Box[LiftResponse] = + handleVersionedAjax(handlerVersion, cont, suspendRequest, requestState, liftSession) + + possibleFuture.map(_.get(ajaxPostTimeout) match { + case Full(response) => response + case _ => Empty + }) openOr result + } finally { + if (! requestState.request.suspendResumeSupport_?) + liftSession.exitComet(cont) } + tryo { LiftSession.onEndServicing.foreach(_(liftSession, requestState, ret)) } diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index dc206fd6c2..5733db1604 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -675,7 +675,6 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, * Executes the user's functions based on the query parameters */ def runParams(state: Req): List[Any] = { - val toRun = { // get all the commands, sorted by owner, (state.uploadedFiles.map(_.name) ::: state.paramNames).distinct. @@ -708,13 +707,28 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, val f = toRun.filter(_.owner == w) w match { // if it's going to a CometActor, batch up the commands - case Full(id) if asyncById.contains(id) => asyncById.get(id).toList.flatMap(a => - a.!?(a.cometProcessingTimeout, ActionMessageSet(f.map(i => buildFunc(i)), state)) match { + case Full(id) if asyncById.contains(id) => asyncById.get(id).toList.flatMap(a => { + val future = + a.!<(ActionMessageSet(f.map(i => buildFunc(i)), state)) + + def processResult(result: Any): List[Any] = result match { case Full(li: List[_]) => li case li: List[_] => li - case Empty => Full(a.cometProcessingTimeoutHandler()) + // We return the future so it can, from AJAX requests, be + // satisfied and update the pending ajax request map. + case Empty => + val processingFuture = new LAFuture[Any] + // Wait for and process the future on a separate thread. + Schedule.schedule(() => { + processingFuture.satisfy(processResult(future.get)) + }, 0 seconds) + List((a.cometProcessingTimeoutHandler, processingFuture)) case other => Nil - }) + } + + processResult(future.get(a.cometProcessingTimeout)) + }) + case _ => f.map(i => buildFunc(i).apply()) } } From 1c465306bd2049bcdeb8b81fce5c1fda5e774c95 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 18 Aug 2012 15:44:27 -0400 Subject: [PATCH 0149/1949] Delimit version in GUID string by a dash. Before we were relying on the expected length of the funcName, determined by calling nextFuncName. Because funcName isn't *always* the same length, we switch instead ot putting a - between the GUID and the version identifier in the path. We then look for it when extracting the version identifier. --- .../src/main/scala/net/liftweb/http/LiftServlet.scala | 8 ++++---- .../main/scala/net/liftweb/http/js/ScriptRenderer.scala | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala index e8e42c2290..8cc7a8ebbc 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala @@ -418,11 +418,11 @@ class LiftServlet extends Loggable { private object AjaxVersions { def unapply(ajaxPathPart: String) : Option[(String,Int)] = { - val funcLength = Helpers.nextFuncName.length - if (ajaxPathPart.length > funcLength) + val dash = ajaxPathPart.indexOf("-") + if (dash > -1 && ajaxPathPart.length > dash + 1) Some( - (ajaxPathPart.substring(0, funcLength), - ajaxPathPart.charAt(funcLength)) + (ajaxPathPart.substring(0, dash), + ajaxPathPart.charAt(dash + 1)) ) else None diff --git a/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala b/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala index 00bbef286a..dfc650e968 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala @@ -186,7 +186,7 @@ object ScriptRenderer { addPageNameAndVersion: function(url) { return """ + { if (LiftRules.enableLiftGC) { - "url.replace('" + LiftRules.ajaxPath + "', '" + LiftRules.ajaxPath + "/'+lift_page+(liftAjax.lift_ajaxVersion % 36).toString(36));" + "url.replace('" + LiftRules.ajaxPath + "', '" + LiftRules.ajaxPath + "/'+lift_page+('-'+liftAjax.lift_ajaxVersion%36).toString(36));" } else { "url;" } From 7d89340f0a4bb2020d271b9c334658b765ac0e66 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 18 Aug 2012 15:57:38 -0400 Subject: [PATCH 0150/1949] Move ajax request list into LiftSession with lastSeen. LiftSession is in charge of managing cleanup of non-recently-seen pages and such, so it needs to know about the AjaxRequestInfo list to clean it up when a given request hasn't been needed in sufficiently long. --- .../scala/net/liftweb/http/LiftServlet.scala | 31 ++++++---------- .../scala/net/liftweb/http/LiftSession.scala | 36 +++++++++++++++++++ 2 files changed, 46 insertions(+), 21 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala index 8cc7a8ebbc..11bc5a7a57 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala @@ -472,15 +472,7 @@ class LiftServlet extends Loggable { private case class AjaxResponseComplete(response: Box[LiftResponse]) - /** - * existingResponse is Empty if we have no response for this request - * yet. pendingActors is a list of actors who want to be notified when - * this response is received. - */ - private case class AjaxRequestInfo(requestVersion:Int, existingResponse:Box[Box[LiftResponse]], pendingActors:List[LiftActor]) - private lazy val ajaxPostTimeout: Long = LiftRules.ajaxPostTimeout * 1000L - private val currentAjaxRequests = scala.collection.mutable.Map[String,AjaxRequestInfo]() /** * Runs the actual AJAX processing. This includes handling __lift__GC, @@ -510,9 +502,6 @@ class LiftServlet extends Loggable { val renderVersion = RenderVersion.get liftSession.updateFuncByOwner(renderVersion, millis) - // FIXME We need to clean up currentAjaxRequests entries when - // FIXME the page expires. - Full(JavaScriptResponse(js.JsCmds.Noop)) case _ => @@ -626,28 +615,28 @@ class LiftServlet extends Loggable { val renderVersion = RenderVersion.get val toReturn: Box[Box[LiftResponse]] = - currentAjaxRequests.synchronized { + liftSession.withAjaxRequests { currentAjaxRequests => currentAjaxRequests.get(renderVersion).collect { - case AjaxRequestInfo(storedVersion, _, pendingActors) if handlerVersion != storedVersion => + case AjaxRequestInfo(storedVersion, _, pendingActors, _) if handlerVersion != storedVersion => // Break out of any actors for the stale version. pendingActors.foreach(_ ! BreakOut()) // Evict the older version's info. currentAjaxRequests += (renderVersion -> - AjaxRequestInfo(handlerVersion, Empty, cont :: Nil)) + AjaxRequestInfo(handlerVersion, Empty, cont :: Nil, millis)) suspendRequest() Empty // no response available, triggers the actual AJAX computation below - case AjaxRequestInfo(storedVersion, existingResponseBox @ Full(_), _) => + case AjaxRequestInfo(storedVersion, existingResponseBox @ Full(_), _, _) => existingResponseBox // return the Full response Box - case AjaxRequestInfo(storedVersion, _, pendingActors) => + case AjaxRequestInfo(storedVersion, _, pendingActors, _) => currentAjaxRequests += (renderVersion -> - AjaxRequestInfo(handlerVersion, Empty, cont :: pendingActors)) + AjaxRequestInfo(handlerVersion, Empty, cont :: pendingActors, millis)) suspendRequest() @@ -655,7 +644,7 @@ class LiftServlet extends Loggable { } openOr { currentAjaxRequests += (renderVersion -> - AjaxRequestInfo(handlerVersion, Empty, cont :: Nil)) + AjaxRequestInfo(handlerVersion, Empty, cont :: Nil, millis)) suspendRequest() @@ -670,13 +659,13 @@ class LiftServlet extends Loggable { // any waiting actors then clear the actor list and update the // request info to include the response in case any other // requests come in with this version. - currentAjaxRequests.synchronized { + liftSession.withAjaxRequests { currentAjaxRequests => currentAjaxRequests.get(renderVersion).collect { - case AjaxRequestInfo(storedVersion, _, pendingActors) if storedVersion == handlerVersion => + case AjaxRequestInfo(storedVersion, _, pendingActors, _) if storedVersion == handlerVersion => pendingActors.foreach(_ ! AjaxResponseComplete(Full(result))) currentAjaxRequests += (renderVersion -> - AjaxRequestInfo(handlerVersion, Full(Full(result)), Nil)) + AjaxRequestInfo(handlerVersion, Full(Full(result)), Nil, millis)) } } }))) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index 5733db1604..6e7aa8bd69 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -499,6 +499,16 @@ private final case class PostPageFunctions(renderVersion: String, } +/** + * existingResponse is Empty if we have no response for this request + * yet. pendingActors is a list of actors who want to be notified when + * this response is received. + */ +private[http] final case class AjaxRequestInfo(requestVersion:Int, + existingResponse:Box[Box[LiftResponse]], + pendingActors:List[LiftActor], + lastSeen: Long) + /** * The LiftSession class containg the session state information */ @@ -557,6 +567,16 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, */ private var postPageFunctions: Map[String, PostPageFunctions] = Map() + /** + * A list of AJAX requests that may or may not be pending for this + * session. There is up to one entry per RenderVersion. + */ + private var ajaxRequests = scala.collection.mutable.Map[String,AjaxRequestInfo]() + + private[http] def withAjaxRequests[T](fn: (scala.collection.mutable.Map[String, AjaxRequestInfo]) => T) = { + ajaxRequests.synchronized { fn(ajaxRequests) } + } + /** * The synchronization lock for the postPageFunctions */ @@ -850,6 +870,15 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, } } + withAjaxRequests { currentAjaxRequests => + for { + (version, requestInfo) <- currentAjaxRequests + if (now - requestInfo.lastSeen) > LiftRules.unusedFunctionsLifeTime + } { + currentAjaxRequests -= version + } + } + synchronized { messageCallback.foreach { case (k, f) => @@ -977,6 +1006,13 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, } postPageFunctions += (ownerName -> funcInfo.updateLastSeen) } + withAjaxRequests { currentAjaxRequests => + currentAjaxRequests.get(ownerName).foreach { + case info: AjaxRequestInfo => + currentAjaxRequests += (ownerName -> info.copy(lastSeen = time)) + } + } + synchronized { (0 /: messageCallback)((l, v) => l + (v._2.owner match { case Full(owner) if (owner == ownerName) => From 70ffcbaa3fca6760de3096da5bc64585eeaf9479 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 18 Aug 2012 16:47:53 -0400 Subject: [PATCH 0151/1949] Don't suspend requests too early for the first request. We were suspending the request before we got a chance to kick off the processing for the actual response to be run. --- .../src/main/scala/net/liftweb/http/LiftServlet.scala | 10 +++++----- .../src/main/scala/net/liftweb/http/LiftSession.scala | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala index 11bc5a7a57..477bf910be 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala @@ -626,8 +626,6 @@ class LiftServlet extends Loggable { (renderVersion -> AjaxRequestInfo(handlerVersion, Empty, cont :: Nil, millis)) - suspendRequest() - Empty // no response available, triggers the actual AJAX computation below case AjaxRequestInfo(storedVersion, existingResponseBox @ Full(_), _, _) => @@ -646,14 +644,12 @@ class LiftServlet extends Loggable { (renderVersion -> AjaxRequestInfo(handlerVersion, Empty, cont :: Nil, millis)) - suspendRequest() - Empty // no response available, triggers the actual AJAX computation below } } toReturn or { - Full(runAjax(requestState, liftSession, Full((result: LiftResponse) => { + val result = Full(runAjax(requestState, liftSession, Full((result: LiftResponse) => { // When we get the response, synchronizedly check that the // versions are still the same in the map, and, if so, update // any waiting actors then clear the actor list and update the @@ -669,6 +665,10 @@ class LiftServlet extends Loggable { } } }))) + + suspendRequest() + + result } openOr Empty case _ => diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index 6e7aa8bd69..fc95685855 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -573,7 +573,7 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, */ private var ajaxRequests = scala.collection.mutable.Map[String,AjaxRequestInfo]() - private[http] def withAjaxRequests[T](fn: (scala.collection.mutable.Map[String, AjaxRequestInfo]) => T) = { + private[http] def withAjaxRequests[T](fn: (scala.collection.mutable.Map[String, AjaxRequestInfo]) => T): T = { ajaxRequests.synchronized { fn(ajaxRequests) } } From a0a981ce310bc6826d2725fe49b937d55e799480 Mon Sep 17 00:00:00 2001 From: Joni Freeman Date: Sun, 19 Aug 2012 08:33:57 +0300 Subject: [PATCH 0152/1949] Add java.sql.Timestamp support to JSON --- core/json/src/main/scala/net/liftweb/json/Extraction.scala | 2 ++ core/json/src/main/scala/net/liftweb/json/Meta.scala | 3 ++- .../test/scala/net/liftweb/json/ExtractionExamplesSpec.scala | 5 +++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/core/json/src/main/scala/net/liftweb/json/Extraction.scala b/core/json/src/main/scala/net/liftweb/json/Extraction.scala index 2cf10eaa47..541bd47cab 100644 --- a/core/json/src/main/scala/net/liftweb/json/Extraction.scala +++ b/core/json/src/main/scala/net/liftweb/json/Extraction.scala @@ -20,6 +20,7 @@ package json import java.lang.reflect.{Constructor => JConstructor, Type} import java.lang.{Integer => JavaInteger, Long => JavaLong, Short => JavaShort, Byte => JavaByte, Boolean => JavaBoolean, Double => JavaDouble, Float => JavaFloat} import java.util.Date +import java.sql.Timestamp import scala.reflect.Manifest /** Function to extract values from JSON AST using case classes. @@ -392,6 +393,7 @@ object Extraction { case JString(s) if (targetType == classOf[String]) => s case JString(s) if (targetType == classOf[Symbol]) => Symbol(s) case JString(s) if (targetType == classOf[Date]) => formats.dateFormat.parse(s).getOrElse(fail("Invalid date '" + s + "'")) + case JString(s) if (targetType == classOf[Timestamp]) => new Timestamp(formats.dateFormat.parse(s).getOrElse(fail("Invalid date '" + s + "'")).getTime) case JBool(x) if (targetType == classOf[Boolean]) => x case JBool(x) if (targetType == classOf[JavaBoolean]) => new JavaBoolean(x) case j: JValue if (targetType == classOf[JValue]) => j diff --git a/core/json/src/main/scala/net/liftweb/json/Meta.scala b/core/json/src/main/scala/net/liftweb/json/Meta.scala index 6d8de641bc..9437dac36b 100644 --- a/core/json/src/main/scala/net/liftweb/json/Meta.scala +++ b/core/json/src/main/scala/net/liftweb/json/Meta.scala @@ -19,6 +19,7 @@ package json import java.lang.reflect.{Constructor => JConstructor, Field, Type, ParameterizedType, GenericArrayType} import java.util.Date +import java.sql.Timestamp case class TypeInfo(clazz: Class[_], parameterizedType: Option[ParameterizedType]) @@ -215,7 +216,7 @@ private[json] object Meta { classOf[Short], classOf[java.lang.Integer], classOf[java.lang.Long], classOf[java.lang.Double], classOf[java.lang.Float], classOf[java.lang.Byte], classOf[java.lang.Boolean], classOf[Number], - classOf[java.lang.Short], classOf[Date], classOf[Symbol], classOf[JValue], + classOf[java.lang.Short], classOf[Date], classOf[Timestamp], classOf[Symbol], classOf[JValue], classOf[JObject], classOf[JArray]).map((_, ()))) private val primaryConstructors = new Memo[Class[_], List[(String, Type)]] diff --git a/core/json/src/test/scala/net/liftweb/json/ExtractionExamplesSpec.scala b/core/json/src/test/scala/net/liftweb/json/ExtractionExamplesSpec.scala index a42eacc472..761fe34776 100644 --- a/core/json/src/test/scala/net/liftweb/json/ExtractionExamplesSpec.scala +++ b/core/json/src/test/scala/net/liftweb/json/ExtractionExamplesSpec.scala @@ -90,6 +90,11 @@ object ExtractionExamples extends Specification("Extraction Examples Specificati json.extract[Event] mustEqual Event("e1", date("2009-09-04T18:06:22Z")) } + "Timestamp extraction example" in { + val json = parse("""{"timestamp":"2009-09-04T18:06:22Z"}""") + new Date((json \ "timestamp").extract[java.sql.Timestamp].getTime) mustEqual date("2009-09-04T18:06:22Z") + } + "Option extraction example" in { val json = parse("""{ "name": null, "age": 5, "mother":{"name":"Marilyn"}}""") json.extract[OChild] mustEqual OChild(None, 5, Some(Parent("Marilyn")), None) From 11c83fe128e27f743df75f0c0603d6d6b3b28178 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 20 Aug 2012 11:25:43 -0400 Subject: [PATCH 0153/1949] Make ajaxRequests a val in LiftSession. It's a mutable Map, so there's no need for it to be a var. --- web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index fc95685855..37a5e10a39 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -571,7 +571,7 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, * A list of AJAX requests that may or may not be pending for this * session. There is up to one entry per RenderVersion. */ - private var ajaxRequests = scala.collection.mutable.Map[String,AjaxRequestInfo]() + private val ajaxRequests = scala.collection.mutable.Map[String,AjaxRequestInfo]() private[http] def withAjaxRequests[T](fn: (scala.collection.mutable.Map[String, AjaxRequestInfo]) => T): T = { ajaxRequests.synchronized { fn(ajaxRequests) } From 2d0709a40ab600bf27df602d1f5bbb187e0661b7 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 20 Aug 2012 11:26:11 -0400 Subject: [PATCH 0154/1949] Explain double Box in LiftServlet.handleVersionedAjax. --- .../src/main/scala/net/liftweb/http/LiftServlet.scala | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala index 477bf910be..09808ba64b 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala @@ -614,6 +614,12 @@ class LiftServlet extends Loggable { case Full(handlerVersion) => val renderVersion = RenderVersion.get + // An Empty in toReturn indicates there is no precomputed response for + // this AJAX request/version. Note that runAjax returns a + // Box[LiftResponse]. So we can have a Full(Empty) that indicates + // runAjax has computed a response, and that response was an Empty. + // That's why we have a double Box here. If we get an Empty back, + // we compute the actual response by calling runAjax below. val toReturn: Box[Box[LiftResponse]] = liftSession.withAjaxRequests { currentAjaxRequests => currentAjaxRequests.get(renderVersion).collect { @@ -626,7 +632,7 @@ class LiftServlet extends Loggable { (renderVersion -> AjaxRequestInfo(handlerVersion, Empty, cont :: Nil, millis)) - Empty // no response available, triggers the actual AJAX computation below + Empty case AjaxRequestInfo(storedVersion, existingResponseBox @ Full(_), _, _) => existingResponseBox // return the Full response Box @@ -644,7 +650,7 @@ class LiftServlet extends Loggable { (renderVersion -> AjaxRequestInfo(handlerVersion, Empty, cont :: Nil, millis)) - Empty // no response available, triggers the actual AJAX computation below + Empty } } From b9e4de93ceaf95ab4e6f6681f15ae6a25ab716ac Mon Sep 17 00:00:00 2001 From: Richard Dallaway Date: Fri, 17 Aug 2012 10:41:19 +0100 Subject: [PATCH 0155/1949] Added prominent section indicating no pull requests, in relation to comments in #1308 Also corrected a typo and updated link to the new modules repositories. --- README.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 97f59a7b89..cd00f2d60a 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,13 @@ Lift applications are: Because Lift applications are written in [Scala](http://www.scala-lang.org), an elegant JVM language, you can still use your favorite Java libraries and deploy to your favorite Servlet Container and app server. Use the code you've already written and deploy to the container you've already configured! +## No Pull Requests + +You must be a committer with signed committer agreement to submit patches. We do not accept pull requests from non-committers. + +Please discuss issues and improvements on the [mailing list](http://groups.google.com/forum/#!forum/liftweb), and read up on [other ways you can contribute](https://www.assembla.com/spaces/liftweb/wiki/Contributing). + + ## Getting Started You can create a new Lift project using your favorite build system by adding Lift as a dependency: @@ -78,7 +85,9 @@ There are a variety of other repositories available on the Lift GitHub page. Whi #### modules -The [modules](https://github.com/lift/modules) repository contains the many add-on modules that are part of the Lift project. If you don't find a module you need here, consider [making an external module](http://www.assembla.com/spaces/liftweb/wiki/Modules) and sharing it with the community. +The [modules](https://github.com/liftmodules) repository contains the many add-on modules that are part of the Lift project. If you don't find a module you need here, consider [creating a module](http://www.assembla.com/spaces/liftweb/wiki/Modules) and sharing it with the community. + +Please note that the modules project does accept pull requests. #### examples @@ -116,7 +125,7 @@ The ScalaDocs for each release of Lift, in additional to the actual JARs, are av ## License -Lift is open source software released under the **Apache 2.0 license**. You must be a committer with signed comitter agreement to submit patches. You can learn more about Lift's comitter policy on the Lift website. +Lift is open source software released under the **Apache 2.0 license**. You must be a committer with signed committer agreement to submit patches. You can learn more about Lift's committer policy on the Lift website. ## Continuous Integration From de31e5baffba58079e7cabb1b557463dbdb703a8 Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Tue, 21 Aug 2012 22:39:33 -0400 Subject: [PATCH 0156/1949] * Added build script --- unsafePublishLift.sh | 176 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 unsafePublishLift.sh diff --git a/unsafePublishLift.sh b/unsafePublishLift.sh new file mode 100644 index 0000000000..1660038e2d --- /dev/null +++ b/unsafePublishLift.sh @@ -0,0 +1,176 @@ +#!/bin/bash +#It is called unsafe so most people stay away from this +#But this script is safe to use if you are trying to publish Lift Framework to sonatype +#as a release version (including Milestones and RC's + + +## This scripts runs on mac's bash terminal + +# Exit on any errors and on unbound vars to be safe +set -o errexit +set -o nounset + + +BUILDLOG=/tmp/Lift-do-release-`date "+%Y%m%d-%H%M%S"`.log + +# This script is an attempt to automate the Lift release process +# +# From Indrajit, the steps on each module (superbuild, framework, examples) are: +# +# 1. git checkout -b +# 2. ./liftsh 'set project.version ' +# 3. Edit project/plugins/Plugins.scala to change the version of lift-sbt +# 4. git commit -v -a -m "Prepare for " +# 5. git push origin +# 6. git tag -release +# 7. git push origin -release +# 8. LIFTSH_OPTS="-Dpublish.remote=true -Dsbt.log.noformat=true" ./liftsh clean-cache clean-plugins reload +clean-lib +update +clean +publish +# 9. Wait for happiness + +SCRIPTVERSION=0.1 + +##### Utility functions (break these out into an include?) ##### +# Basically yes/no confirmation with customized messages +# Usage: confirm "prompt" +# Returns 0 for yes, 1 for no +function confirm { + while read -p "$1 [yes/no] " CONFIRM; do + case "`echo $CONFIRM | tr [:upper:] [:lower:]`" in + yes) + return 0 + ;; + no) + return 1 + ;; + *) + echo "Please enter yes or no" + ;; + esac + done +} + +function debug { + #echo $@ + echo -n "" +} + +function die { + echo $@ + exit 1 +} + +# Locate our base directory (taken from http://blog.eitchnet.ch/?p=242) +SCRIPT_NAME="${PWD##*/}" +SCRIPT_DIR="${PWD%/*}" + +# if the script was started from the base directory, then the +# expansion returns a period +if test "$SCRIPT_DIR" == "." ; then + SCRIPT_DIR="$PWD" +# if the script was not called with an absolute path, then we need to add the +# current working directory to the relative path of the script +elif test "${SCRIPT_DIR:0:1}" != "/" ; then + SCRIPT_DIR="$PWD/$SCRIPT_DIR" +fi + +echo -e "\n*********************************************************************" +echo -e "SCRIPT_DIR is ${SCRIPT_DIR}" +echo -e "\n*********************************************************************" + +##### End Utility Functions ##### + + +echo -e "\n*********************************************************************" +printf "* Lift Full Release build script version %-26s *\n" "$SCRIPTVERSION" +#echo "* Default choices for prompts are marked in capitals *" +printf "*********************************************************************\n\n" + +echo -e "Build output logged to $BUILDLOG\n" + + +# CouchDB will blow up with HTTP proxy set because it doesn't correctly interpret the return codes +set +o nounset +if [ ! -z "${http_proxy}" -o ! -z "${HTTP_PROXY}" ]; then + echo -e "CouchDB tests will fail with http_proxy set! Please unset and re-run.\n" + exit +fi +set -o nounset + +# First, let's confirm that we really want to release... +confirm "Are you certain you want a release build?" || die "Cancelling release build." + +echo -e "\nProceeding...\n" + +# Now we need to know what version we're releasing +read -p "Please enter the version of the release: " RELEASE_VERSION + +# Sanity check on the release version +if ! echo $RELEASE_VERSION | egrep -x '[0-9]+\.[0-9]+(-(M|RC)[0-9]+)?' > /dev/null; then + confirm "$RELEASE_VERSION does not appear to be a valid version. Are you sure?" || + die "Cencelling release build!" +fi + +# Perform a sanity check on the modules first +for MODULE in framework ; do + cd ${SCRIPT_DIR}/${MODULE} + + echo "We cd'ed into `pwd`" + + # ensure that we're on master, and that we're up-to-date + CURRENT_BRANCH=`git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/\1/'` + debug "Current branch for $MODULE is $CURRENT_BRANCH" + + if [ "${CURRENT_BRANCH}" != "master" ]; then + echo "Currently releases can only be built from master. $MODULE is on branch $CURRENT_BRANCH. Aborting build." + exit + fi + + # ensure that we don't have any outstanding changes + if git status | grep -q "Changes not staged for commit" ; then + die "There are outstanding changes in $MODULE. Aborting build." + else + echo "All changes are committed, moving on" + fi + +done + +echo -e "\nPre-build tests passed. Initiating release build of LiftWeb version $RELEASE_VERSION\n" + +# For the remaining modules, we follow indrajit's steps outlined above +for MODULE in framework ; do + echo -e "\nStarting build on $MODULE module" + cd ${SCRIPT_DIR}/${MODULE} || die "Could not change to $MODULE directory!" + + git checkout -b ${RELEASE_VERSION} >> ${BUILDLOG} || die "Error creating work branch!" + + + ./liftsh ";set version in ThisBuild := \"${RELEASE_VERSION}\" ; session save " >> ${BUILDLOG} || die "Could not update project version in SBT!" + + git commit -v -a -m "Prepare for ${RELEASE_VERSION}" >> ${BUILDLOG} || die "Could not commit project version change!" + +#git push origin ${RELEASE_VERSION} >> ${BUILDLOG} || die "Could not push project version change!" + + git tag ${RELEASE_VERSION}-release >> ${BUILDLOG} || die "Could not tag release!" + +#git push origin ${RELEASE_VERSION}-release >> ${BUILDLOG} || die "Could not push release tag!" + + # Do a separate build for each configured Scala version so we don't blow the heap + for SCALA_VERSION in $(grep crossScalaVersions build.sbt | cut -d '(' -f 2 | sed s/[,\)\"]//g ); do + echo -n " Building against Scala ${SCALA_VERSION}..." + if ! ./liftsh ++${SCALA_VERSION} clean update test publish-local >> ${BUILDLOG} ; then + echo "failed! See build log for details" + exit + fi + echo "complete" + done + + echo "Build complete for module ${MODULE}" + +done + +echo -e "\n\nRelease complete!" +echo -e "\n\nPlease update the lift_sbt_2.5 templates!" +echo -e "\n\nand write something about this release on the liftweb.net site." + + + From 8fc685f2880fe98c8e046df9c7ba874a725f9f92 Mon Sep 17 00:00:00 2001 From: David Pollak Date: Mon, 27 Aug 2012 14:25:08 -0700 Subject: [PATCH 0157/1949] Added a feature to CSS Selector Transforms that overrides attribute merging. Closes #1312 --- .../scala/net/liftweb/util/BindHelpers.scala | 19 ++++++++++--------- .../scala/net/liftweb/util/CssSelector.scala | 7 ++++++- .../net/liftweb/util/BindHelpersSpec.scala | 10 ++++++++++ project/plugins.sbt | 4 ++++ 4 files changed, 30 insertions(+), 10 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/BindHelpers.scala b/core/util/src/main/scala/net/liftweb/util/BindHelpers.scala index 3e4c0f556b..172fe0638f 100644 --- a/core/util/src/main/scala/net/liftweb/util/BindHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/BindHelpers.scala @@ -2069,7 +2069,7 @@ private class SelectorMap(binds: List[CssBind]) extends Function1[NodeSeq, NodeS ret.mkString(" ") } - def mergeAll(other: MetaData, stripId: Boolean): MetaData = { + def mergeAll(other: MetaData, stripId: Boolean, ignoreClass: Boolean): MetaData = { var oldAttrs = attrs - (if (stripId) "id" else "") var builtMeta: MetaData = Null @@ -2082,7 +2082,7 @@ private class SelectorMap(binds: List[CssBind]) extends Function1[NodeSeq, NodeS case up: UnprefixedAttribute if up.key == "class" => { oldAttrs.get("class") match { - case Some(ca) => { + case Some(ca) if !ignoreClass => { oldAttrs -= "class" builtMeta = new UnprefixedAttribute("class", uniqueClasses(up.value. @@ -2091,7 +2091,9 @@ private class SelectorMap(binds: List[CssBind]) extends Function1[NodeSeq, NodeS builtMeta) } - case _ => builtMeta = up.copy(builtMeta) + case _ => + oldAttrs -= "class" + builtMeta = up.copy(builtMeta) } } @@ -2125,9 +2127,8 @@ private class SelectorMap(binds: List[CssBind]) extends Function1[NodeSeq, NodeS builtMeta } - // we can do an open_! here because all the CssBind elems - // have been vetted - bind.css.open_!.subNodes match { + // + bind.css.openOrThrowException("we can do an open_! here because all the CssBind elems have been vetted").subNodes match { case Full(SelectThisNode(kids)) => { throw new RetryWithException(if (kids) realE.child else realE) } @@ -2160,7 +2161,7 @@ private class SelectorMap(binds: List[CssBind]) extends Function1[NodeSeq, NodeS } } - case x: EmptyBox => { + case x if Full(DontMergeAttributes) == x || x.isInstanceOf[EmptyBox] => { val calced = bind.calculate(realE).map(findElemIfThereIsOne _) calced.length match { @@ -2169,7 +2170,7 @@ private class SelectorMap(binds: List[CssBind]) extends Function1[NodeSeq, NodeS calced.head match { case Group(g) => g case e: Elem => new Elem(e.prefix, - e.label, mergeAll(e.attributes, false), + e.label, mergeAll(e.attributes, false, Full(DontMergeAttributes) == x), e.scope, e.child :_*) case x => x } @@ -2187,7 +2188,7 @@ private class SelectorMap(binds: List[CssBind]) extends Function1[NodeSeq, NodeS val targetId = e.attribute("id").map(_.toString) orElse (attrs.get("id")) val keepId = targetId map { id => ids.contains(id) } getOrElse (false) val newIds = targetId filter (_ => keepId) map (i => ids - i) getOrElse (ids) - val newElem = new Elem(e.prefix, e.label, mergeAll(e.attributes, ! keepId), e.scope, e.child: _*) + val newElem = new Elem(e.prefix, e.label, mergeAll(e.attributes, ! keepId, Full(DontMergeAttributes) == x), e.scope, e.child: _*) (newIds, newElem :: result) } case x => (ids, x :: result) diff --git a/core/util/src/main/scala/net/liftweb/util/CssSelector.scala b/core/util/src/main/scala/net/liftweb/util/CssSelector.scala index bc8073b3e6..63afb5e001 100644 --- a/core/util/src/main/scala/net/liftweb/util/CssSelector.scala +++ b/core/util/src/main/scala/net/liftweb/util/CssSelector.scala @@ -61,6 +61,10 @@ final case class PrependKidsSubNode() extends SubNode with WithKids { def transform(original: NodeSeq, newNs: NodeSeq): NodeSeq = newNs ++ original } +final case object DontMergeAttributes extends SubNode { + +} + final case class AppendKidsSubNode() extends SubNode with WithKids { def transform(original: NodeSeq, newNs: NodeSeq): NodeSeq = original ++ newNs } @@ -189,7 +193,8 @@ object CssSelectorParser extends Parsers with ImplicitConversions { name => AttrRemoveSubNode(name) }) | (opt('*') ~ '[' ~> attrName <~ ']' ^^ { name => AttrSubNode(name) - }) | + }) | + ('!' ~ '!' ^^ (a => DontMergeAttributes)) | ('-' ~ '*' ^^ (a => PrependKidsSubNode())) | ('>' ~ '*' ^^ (a => PrependKidsSubNode())) | ('*' ~ '+' ^^ (a => AppendKidsSubNode())) | diff --git a/core/util/src/test/scala/net/liftweb/util/BindHelpersSpec.scala b/core/util/src/test/scala/net/liftweb/util/BindHelpersSpec.scala index 930a009da8..96eb9c34d6 100644 --- a/core/util/src/test/scala/net/liftweb/util/BindHelpersSpec.scala +++ b/core/util/src/test/scala/net/liftweb/util/BindHelpersSpec.scala @@ -594,6 +594,16 @@ object CssBindHelpersSpec extends Specification { } + + "Remove a subnode's class attribute" in { + + val func = ".removeme !!" #> ("td [class!]" #> "removeme") + val res = func.apply(Hi) + + ((res \ "td") \ "@class").text must_== "fish" + } + + "not remove a non-existant class" in { val func = ".foo [class!]" #> "bar" val res = func() diff --git a/project/plugins.sbt b/project/plugins.sbt index 235f59622c..91927a54fb 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,3 +1,7 @@ DefaultOptions.addPluginResolvers addSbtPlugin("in.drajit.sbt" % "sbt-yui-compressor" % "0.2.1") + +resolvers += "sbt-idea-repo" at "http://mpeltonen.github.com/maven/" + +addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.1.0") \ No newline at end of file From 27bd39e7d601ec627aa447aac710c691e32fcaa1 Mon Sep 17 00:00:00 2001 From: David Pollak Date: Sat, 23 Jun 2012 08:31:41 +0200 Subject: [PATCH 0158/1949] WIP getting multiple CSS Selectors --- .../scala/net/liftweb/util/CssSelector.scala | 112 +++++++++++++++--- .../net/liftweb/util/CssSelectorSpec.scala | 10 +- 2 files changed, 102 insertions(+), 20 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/CssSelector.scala b/core/util/src/main/scala/net/liftweb/util/CssSelector.scala index 63afb5e001..befd6eb3ac 100644 --- a/core/util/src/main/scala/net/liftweb/util/CssSelector.scala +++ b/core/util/src/main/scala/net/liftweb/util/CssSelector.scala @@ -20,27 +20,46 @@ package util import scala.util.parsing.combinator.{Parsers, ImplicitConversions} import scala.xml.NodeSeq import net.liftweb.common._ +import util.EnclosedSelector sealed trait CssSelector { def subNodes: Box[SubNode] + def withSubnode(sn: SubNode): CssSelector } final case class ElemSelector(elem: String, subNodes: Box[SubNode]) extends - CssSelector + CssSelector { + def withSubnode(sn: SubNode): CssSelector = this.copy(subNodes = Full(sn)) +} -final case class StarSelector(subNodes: Box[SubNode]) extends CssSelector +final case class StarSelector(subNodes: Box[SubNode]) extends CssSelector { + def withSubnode(sn: SubNode): CssSelector = this.copy(subNodes = Full(sn)) +} final case class IdSelector(id: String, subNodes: Box[SubNode]) extends - CssSelector + CssSelector { + def withSubnode(sn: SubNode): CssSelector = this.copy(subNodes = Full(sn)) +} final case class ClassSelector(clss: String, subNodes: Box[SubNode]) extends - CssSelector + CssSelector { + def withSubnode(sn: SubNode): CssSelector = this.copy(subNodes = Full(sn)) +} final case class NameSelector(name: String, subNodes: Box[SubNode]) extends - CssSelector + CssSelector { + def withSubnode(sn: SubNode): CssSelector = this.copy(subNodes = Full(sn)) +} + +final case class EnclosedSelector(selector: CssSelector, kid: CssSelector) extends CssSelector { + def subNodes: Box[SubNode] = Empty + def withSubnode(sn: SubNode): CssSelector = this +} final case class AttrSelector(name: String, value: String, -subNodes: Box[SubNode]) extends CssSelector +subNodes: Box[SubNode]) extends CssSelector { + def withSubnode(sn: SubNode): CssSelector = this.copy(subNodes = Full(sn)) +} sealed trait SubNode @@ -121,7 +140,7 @@ object CssSelectorParser extends Parsers with ImplicitConversions { private implicit def str2chars(s: String): List[Char] = new scala.collection.immutable.WrappedString(s).toList - private lazy val topParser: Parser[CssSelector] = { + private lazy val _topParser: Parser[CssSelector] = { phrase(idMatch | nameMatch | classMatch | @@ -130,6 +149,33 @@ object CssSelectorParser extends Parsers with ImplicitConversions { starMatch | colonMatch) } + + private def fixAll(all: List[CssSelector], sn: Option[SubNode]): CssSelector = (all, sn) match { + case (r :: Nil, None) => r + case (r :: Nil, Some(sn)) => r.withSubnode(sn) + case (lst, None) => lst.reduceRight((b, a) => EnclosedSelector(a, b)) + case (lst, Some(sn)) => (lst.dropRight(1) ::: lst.takeRight(1).map(_.withSubnode(sn))).reduceRight((b, a) => EnclosedSelector(a, b)) + } + + private lazy val topParser: Parser[CssSelector] = + phrase(rep1(_idMatch | _nameMatch | _classMatch | _attrMatch | _elemMatch | + _colonMatch | _starMatch) ~ opt(subNode)) ^^ { + case all ~ None if all.takeRight(1).head == StarSelector(Empty) => + fixAll(all.dropRight(1), Some(KidsSubNode())) + case all ~ sn => fixAll(all, sn) + } + + private lazy val _colonMatch: Parser[CssSelector] = + (':' ~> id) ^? { + case "button" => AttrSelector("type", "button", Empty) + case "checkbox" => AttrSelector("type", "checkbox", Empty) + case "file" => AttrSelector("type", "file", Empty) + case "password" => AttrSelector("type", "password", Empty) + case "radio" => AttrSelector("type", "radio", Empty) + case "reset" => AttrSelector("type", "reset", Empty) + case "submit" => AttrSelector("type", "submit", Empty) + case "text" => AttrSelector("type", "text", Empty) + } private lazy val colonMatch: Parser[CssSelector] = ':' ~> id ~ opt(subNode) ^? { @@ -143,22 +189,63 @@ object CssSelectorParser extends Parsers with ImplicitConversions { case "text" ~ sn => AttrSelector("type", "text", sn) } + private lazy val idMatch: Parser[CssSelector] = '#' ~> id ~ opt(subNode) ^^ { case id ~ sn => IdSelector(id, sn) } + private lazy val _idMatch: Parser[CssSelector] = '#' ~> id ^^ { + case id => IdSelector(id, Empty) + } + private lazy val nameMatch: Parser[CssSelector] = '@' ~> id ~ opt(subNode) ^^ { case name ~ sn => NameSelector(name, sn) } + private lazy val _nameMatch: Parser[CssSelector] = '@' ~> id ^^ { + case name => NameSelector(name, Empty) + } + private lazy val elemMatch: Parser[CssSelector] = id ~ opt(subNode) ^^ { case elem ~ sn => ElemSelector(elem, sn) } + private lazy val _elemMatch: Parser[CssSelector] = id ^^ { + case elem => ElemSelector(elem, Empty) + } + private lazy val starMatch: Parser[CssSelector] = '*' ~> opt(subNode) ^^ { case sn => StarSelector(sn) } + private lazy val _starMatch: Parser[CssSelector] = '*' ^^ { + case sn => StarSelector(Empty) + } + + private lazy val classMatch: Parser[CssSelector] = + '.' ~> attrName ~ opt(subNode) ^^ { + case cls ~ sn => ClassSelector(cls, sn) + } + + private lazy val attrMatch: Parser[CssSelector] = + attrName ~ '=' ~ attrConst ~ opt(subNode) ^^ { + case "id" ~ _ ~ const ~ sn => IdSelector(const, sn) + case "name" ~ _ ~ const ~ sn => NameSelector(const, sn) + case n ~ _ ~ v ~ sn => AttrSelector(n, v, sn) + } + + private lazy val _classMatch: Parser[CssSelector] = + '.' ~> attrName ^^ { + case cls => ClassSelector(cls, Empty) + } + + private lazy val _attrMatch: Parser[CssSelector] = + attrName ~ '=' ~ attrConst ^^ { + case "id" ~ _ ~ const => IdSelector(const, Empty) + case "name" ~ _ ~ const => NameSelector(const, Empty) + case n ~ _ ~ v => AttrSelector(n, v, Empty) + } + private lazy val id: Parser[String] = letter ~ rep(letter | number | '-' | '_' | ':' | '.') ^^ { @@ -173,17 +260,6 @@ object CssSelectorParser extends Parsers with ImplicitConversions { private lazy val letter: Parser[Char] = elem("letter", isLetter) private lazy val number: Parser[Char] = elem("number", isNumber) - private lazy val classMatch: Parser[CssSelector] = - '.' ~> attrName ~ opt(subNode) ^^ { - case cls ~ sn => ClassSelector(cls, sn) - } - - private lazy val attrMatch: Parser[CssSelector] = - attrName ~ '=' ~ attrConst ~ opt(subNode) ^^ { - case "id" ~ _ ~ const ~ sn => IdSelector(const, sn) - case "name" ~ _ ~ const ~ sn => NameSelector(const, sn) - case n ~ _ ~ v ~ sn => AttrSelector(n, v, sn) - } private lazy val subNode: Parser[SubNode] = rep1(' ') ~> ((opt('*') ~ '[' ~> attrName <~ '+' ~ ']' ^^ { diff --git a/core/util/src/test/scala/net/liftweb/util/CssSelectorSpec.scala b/core/util/src/test/scala/net/liftweb/util/CssSelectorSpec.scala index d03750d2fc..8567b8245b 100644 --- a/core/util/src/test/scala/net/liftweb/util/CssSelectorSpec.scala +++ b/core/util/src/test/scala/net/liftweb/util/CssSelectorSpec.scala @@ -21,6 +21,7 @@ import org.specs2.mutable.Specification import common._ import BindHelpers._ +import util.{ElemSelector, ClassSelector} /** @@ -116,8 +117,6 @@ object CssSelectorSpec extends Specification { IdSelector("foo", Full(AttrRemoveSubNode("woof"))) } - - "select attr/val pair" in { CssSelectorParser.parse("frog=dog") must_== Full(AttrSelector("frog", "dog", Empty)) @@ -200,6 +199,13 @@ object CssSelectorSpec extends Specification { CssSelectorParser.parse(".foo [woof] ").open_! must_== ClassSelector("foo", Full(AttrSubNode("woof"))) } + + "select multiple depth" in { + CssSelectorParser.parse("div .foo [woof] ").open_! must_== + EnclosedSelector(ElemSelector("div", Empty), ClassSelector("foo", Full(AttrSubNode("woof")))) + } + + } } From e18d32fc36d21d8501bb8922dd51e20051a2cda2 Mon Sep 17 00:00:00 2001 From: David Pollak Date: Mon, 27 Aug 2012 14:47:59 -0700 Subject: [PATCH 0159/1949] Merged the type-class based CSS Selector transforms in --- .../scala/net/liftweb/util/BindHelpers.scala | 1078 +---------------- .../main/scala/net/liftweb/util/CssSel.scala | 865 +++++++++++++ .../scala/net/liftweb/util/CssSelector.scala | 30 +- .../net/liftweb/util/IterableConst.scala | 143 +++ .../scala/net/liftweb/util/IterableFunc.scala | 47 + .../net/liftweb/util/StringPromotable.scala | 41 + .../main/scala/net/liftweb/util/package.scala | 36 +- .../net/liftweb/util/BindHelpersSpec.scala | 40 +- .../net/liftweb/util/CssSelectorSpec.scala | 21 +- 9 files changed, 1198 insertions(+), 1103 deletions(-) create mode 100644 core/util/src/main/scala/net/liftweb/util/CssSel.scala create mode 100644 core/util/src/main/scala/net/liftweb/util/IterableConst.scala create mode 100644 core/util/src/main/scala/net/liftweb/util/IterableFunc.scala create mode 100644 core/util/src/main/scala/net/liftweb/util/StringPromotable.scala diff --git a/core/util/src/main/scala/net/liftweb/util/BindHelpers.scala b/core/util/src/main/scala/net/liftweb/util/BindHelpers.scala index 172fe0638f..b902197f35 100644 --- a/core/util/src/main/scala/net/liftweb/util/BindHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/BindHelpers.scala @@ -19,8 +19,7 @@ package util import scala.xml._ import common._ -import scala.collection.mutable.ListBuffer -import java.util.{List => JavaList} + /** * This trait is used to identify an object that is representable as a {@link NodeSeq}. @@ -694,7 +693,7 @@ trait BindHelpers { * Remove all the tags, just leaving the child tags */ def stripHead(in: NodeSeq): NodeSeq = { - ("head" #> ((ns: NodeSeq) => ns.asInstanceOf[Elem].child))(in) + ("head" #> ((ns: NodeSeq) => ns.asInstanceOf[Elem].child)).apply(in) } /** @@ -702,7 +701,7 @@ trait BindHelpers { * nodeseq. */ def replaceIdNode(in: NodeSeq, id: String, replacement: NodeSeq): NodeSeq = { - (("#"+id) #> replacement)(in) + (("#"+id) #> replacement).apply(in) } /** @@ -1396,1078 +1395,7 @@ trait BindHelpers { } -/** - * An intermediate class used to promote a String or a CssSelector to - * something that can be associated with a value to apply to the selector - */ -final class ToCssBindPromoter(stringSelector: Box[String], css: Box[CssSelector]) { - - /** - * Inserts a String constant according to the CssSelector rules - */ - def #>(str: String): CssSel = new CssBindImpl(stringSelector, css) { - def calculate(in: NodeSeq): Seq[NodeSeq] = - List(if (null eq str) NodeSeq.Empty else Text(str)) - } - - - /** - * Inserts a NodeSeq constant according to the CssSelector rules - */ - def #>(ns: NodeSeq): CssSel = new CssBindImpl(stringSelector, css) { - def calculate(in: NodeSeq): Seq[NodeSeq] = List(ns) - } - - /** - * A function that transforms the content according to the CssSelector rules - */ - def #>(nsFunc: NodeSeq => NodeSeq): CssSel = new CssBindImpl(stringSelector, css) { - def calculate(in: NodeSeq): Seq[NodeSeq] = List(nsFunc(in)) - } - - /** - * Inserts a Bindable constant according to the CssSelector rules. - * Mapper and Record fields implement Bindable. - */ - def #>(bindable: Bindable): CssSel = new CssBindImpl(stringSelector, css) { - def calculate(in: NodeSeq): Seq[NodeSeq] = List(bindable.asHtml) - } - - /** - * Inserts a StringPromotable constant according to the CssSelector rules. - * StringPromotable includes Int, Long, Boolean, and Symbol - */ - def #>(strPromo: StringPromotable): CssSel = new CssBindImpl(stringSelector, css) { - def calculate(in: NodeSeq): Seq[NodeSeq] = - List(Text(strPromo.toString)) - } - - /** - * Applies the N constants according to the CssSelector rules. - * This allows for Seq[String], Seq[NodeSeq], Box[String], - * Box[NodeSeq], Option[String], Option[NodeSeq] - */ - def #>(itrConst: IterableConst): CssSel = new CssBindImpl(stringSelector, css) { - def calculate(in: NodeSeq): Seq[NodeSeq] = itrConst.constList(in) - } - - /** - * Apply the function and then apply the results account the the CssSelector - * rules. - * This allows for NodeSeq => Seq[String], NodeSeq =>Seq[NodeSeq], - * NodeSeq => Box[String], - * NodeSeq => Box[NodeSeq], NodeSeq => Option[String], - * NodeSeq =>Option[NodeSeq] - */ - def #>(itrFunc: IterableFunc): CssSel = new CssBindImpl(stringSelector, css) { - def calculate(in: NodeSeq): Seq[NodeSeq] = itrFunc(in) - } - - /** - * Inserts a String constant according to the CssSelector rules - */ - def replaceWith(str: String): CssSel = new CssBindImpl(stringSelector, css) { - def calculate(in: NodeSeq): Seq[NodeSeq] = - List(if (null eq str) NodeSeq.Empty else Text(str)) - } - - /** - * Inserts a NodeSeq constant according to the CssSelector rules - */ - def replaceWith(ns: NodeSeq): CssSel = new CssBindImpl(stringSelector, css) { - def calculate(in: NodeSeq): Seq[NodeSeq] = List(ns) - } - - /** - * A function that transforms the content according to the CssSelector rules - */ - def replaceWith(nsFunc: NodeSeq => NodeSeq): CssSel = new CssBindImpl(stringSelector, css) { - def calculate(in: NodeSeq): Seq[NodeSeq] = List(nsFunc(in)) - } - - /** - * Inserts a Bindable constant according to the CssSelector rules. - * Mapper and Record fields implement Bindable. - */ - def replaceWith(bindable: Bindable): CssSel = new CssBindImpl(stringSelector, css) { - def calculate(in: NodeSeq): Seq[NodeSeq] = List(bindable.asHtml) - } - - /** - * Inserts a StringPromotable constant according to the CssSelector rules. - * StringPromotable includes Int, Long, Boolean, and Symbol - */ - def replaceWith(strPromo: StringPromotable): CssSel = new CssBindImpl(stringSelector, css) { - def calculate(in: NodeSeq): Seq[NodeSeq] = strPromo.toString match { - case null => NodeSeq.Empty - case str => List(Text(str)) - } - } - - /** - * Applies the N constants according to the CssSelector rules. - * This allows for Seq[String], Seq[NodeSeq], Box[String], - * Box[NodeSeq], Option[String], Option[NodeSeq] - */ - def replaceWith(itrConst: IterableConst): CssSel = new CssBindImpl(stringSelector, css) { - def calculate(in: NodeSeq): Seq[NodeSeq] = itrConst.constList(in) - } - - /** - * Apply the function and then apply the results account the the CssSelector - * rules. - * This allows for NodeSeq => Seq[String], NodeSeq =>Seq[NodeSeq], - * NodeSeq => Box[String], - * NodeSeq => Box[NodeSeq], NodeSeq => Option[String], - * NodeSeq =>Option[NodeSeq] - */ - def replaceWith(itrFunc: IterableFunc): CssSel = new CssBindImpl(stringSelector, css) { - def calculate(in: NodeSeq): Seq[NodeSeq] = itrFunc(in) - } -} - -/** - * A trait that has some helpful implicit conversions from - * Iterable[NodeSeq], Seq[String], Box[String], and Option[String] - */ -trait IterableConst { - def constList(nodeSeq: NodeSeq): Seq[NodeSeq] -} - -import scala.collection.JavaConversions._ - -/** - * The implementation for a NodeSeq Iterable Const - */ -final case class NodeSeqIterableConst(it: Iterable[NodeSeq]) extends IterableConst { - def this(it: JavaList[NodeSeq]) = this(it: Iterable[NodeSeq]) - - def constList(nodeSeq: NodeSeq): Seq[NodeSeq] = it.toSeq -} - -/** - * The implementation for a NodeSeq => NodeSeq Iterable Const - */ -final case class NodeSeqFuncIterableConst(it: Iterable[NodeSeq => NodeSeq]) extends IterableConst { - def this(it: JavaList[NodeSeq => NodeSeq]) = this(it: Iterable[NodeSeq => NodeSeq]) - - def constList(nodeSeq: NodeSeq): Seq[NodeSeq] = Helpers.ensureUniqueId(it.map(_(nodeSeq)).toSeq) -} - -/** - * The implementation for a Box[NodeSeq => Node] Iterable Const - */ -final case class BoxNodeSeqFuncIterableConst(it: Box[NodeSeq => NodeSeq]) extends IterableConst { - - def constList(nodeSeq: NodeSeq): Seq[NodeSeq] = it.toList.map(_(nodeSeq)) -} - -/** - * The implementation for a Option[NodeSeq => Node] Iterable Const - */ -final case class OptionNodeSeqFuncIterableConst(it: Option[NodeSeq => NodeSeq]) extends IterableConst { - - def constList(nodeSeq: NodeSeq): Seq[NodeSeq] = it.toList.map(_(nodeSeq)) -} - -/** - * Sequence of String iterable const - */ -final case class SeqStringIterableConst(it: Iterable[String]) extends IterableConst { - def this(it: JavaList[String]) = this(it: Iterable[String]) - - def constList(nodeSeq: NodeSeq): Seq[NodeSeq] = it.map(a => Text(a)).toSeq -} - -/** - * Sequence of Bindable iterable const - */ -final case class SeqBindableIterableConst(it: Iterable[Bindable]) extends IterableConst { - def this(it: JavaList[Bindable]) = this(it: Iterable[Bindable]) - - def constList(nodeSeq: NodeSeq): Seq[NodeSeq] = it.map(_.asHtml).toSeq -} - -/** - * The companion object that does the helpful promotion of common - * collection types into an IterableConst, - * e.g. Iterable[NodeSeq], Seq[String], Box[String], and Option[String] - */ -object IterableConst { - /** - * Converts anything that can be converted into an Iterable[NodeSeq] - * into an IterableConst. This includes Seq[NodeSeq] - */ - implicit def itNodeSeq(it: Iterable[NodeSeq]): IterableConst = - NodeSeqIterableConst(it) - - /** - * Converts anything that can be converted into an Box[NodeSeq] - */ - implicit def boxNodeSeq(it: Box[NodeSeq]): IterableConst = - NodeSeqIterableConst(it.toList) - - /** - * Converts anything that can be converted into an Box[NodeSeq] - */ - implicit def optionNodeSeq(it: Option[NodeSeq]): IterableConst = - NodeSeqIterableConst(it.toList) - - /** - * Converts anything that can be converted into an Iterable[NodeSeq] - * into an IterableConst. This includes Seq[NodeSeq], Option[NodeSeq], - * and Box[NodeSeq] - */ - implicit def itNodeSeq(it: JavaList[NodeSeq]): IterableConst = - new NodeSeqIterableConst(it) - - implicit def itNodeSeqFunc(it: Iterable[NodeSeq => NodeSeq]): IterableConst = - NodeSeqFuncIterableConst(it) - - implicit def itNodeSeqFunc(it: JavaList[NodeSeq => NodeSeq]): IterableConst = - new NodeSeqFuncIterableConst(it) - - implicit def boxNodeSeqFunc(it: Box[NodeSeq => NodeSeq]): IterableConst = - BoxNodeSeqFuncIterableConst(it) - - implicit def optionNodeSeqFunc(it: Option[NodeSeq => NodeSeq]): IterableConst = - OptionNodeSeqFuncIterableConst(it) - - implicit def itStringPromotable(it: Iterable[String]): IterableConst = - SeqStringIterableConst(it) - - implicit def javaListStringPromotable(it: JavaList[String]): IterableConst = - new SeqStringIterableConst(it) - - implicit def boxString(it: Box[String]): IterableConst = - SeqStringIterableConst(it.toList) - - implicit def optionString(it: Option[String]): IterableConst = - SeqStringIterableConst(it.toList) - - implicit def itBindable(it: Iterable[Bindable]): IterableConst = - SeqBindableIterableConst(it) - - implicit def itBindable(it: JavaList[Bindable]): IterableConst = - new SeqBindableIterableConst(it) - - - implicit def boxBindablePromotable(it: Box[Bindable]): IterableConst = - SeqBindableIterableConst(it.toList) - - implicit def optionBindablePromotable(it: Option[Bindable]): IterableConst = - SeqBindableIterableConst(it.toList) - - implicit def optionStringPromotable[T](o: Option[T])(implicit view:T=>StringPromotable) = optionString(o.map(view(_).toString)) -} - -sealed trait IterableFunc extends Function1[NodeSeq, Seq[NodeSeq]] { - def apply(ns: NodeSeq): Seq[NodeSeq] -} - -object IterableFunc { - implicit def itNodeSeq[C <% Iterable[NodeSeq]](it: NodeSeq => C): IterableFunc = - new IterableFunc { - def apply(in: NodeSeq): Seq[NodeSeq] = it(in).toSeq - } - - implicit def itNodeSeqPromotable(it: NodeSeq => NodeSeq): IterableFunc = - new IterableFunc { - def apply(in: NodeSeq): Seq[NodeSeq] = List(it(in)) - } - - - implicit def itStringFuncPromotable(it: NodeSeq => String): IterableFunc = - new IterableFunc { - def apply(in: NodeSeq): Seq[NodeSeq] = it(in) match { - case null => List(NodeSeq.Empty) - case str => List(Text(str))} - } - - - implicit def itStringPromotable(it: NodeSeq => Seq[String]): IterableFunc = - new IterableFunc { - def apply(in: NodeSeq): Seq[NodeSeq] = it(in).filter(_ ne null).map(a => Text(a)) - } - - implicit def boxStringPromotable(it: NodeSeq => Box[String]): IterableFunc = - new IterableFunc { - def apply(in: NodeSeq): Seq[NodeSeq] = it(in).filter(_ ne null).toList.map(a => Text(a)) - } - - - implicit def optionStringPromotable(it: NodeSeq => Option[String]): IterableFunc = - new IterableFunc { - def apply(in: NodeSeq): Seq[NodeSeq] = it(in).filter(_ ne null).toList.map(a => Text(a)) - } -} - - -/** - * This trait marks something that can be promoted into a String. - * The companion object has helpful conversions from Int, - * Symbol, Long, and Boolean - */ -trait StringPromotable - -object StringPromotable { - implicit def jsCmdToStrPromo(in: ToJsCmd): StringPromotable = - new StringPromotable { - override val toString = in.toJsCmd - } - - implicit def jsCmdToStrPromo(in: (_, ToJsCmd)): StringPromotable = - new StringPromotable { - override val toString = in._2.toJsCmd - } - - - implicit def intToStrPromo(in: Int): StringPromotable = - new StringPromotable { - override val toString = in.toString - } - - implicit def symbolToStrPromo(in: Symbol): StringPromotable = - new StringPromotable { - override val toString = in.name - } - - implicit def longToStrPromo(in: Long): StringPromotable = - new StringPromotable { - override val toString = in.toString - } - - implicit def booleanToStrPromo(in: Boolean): StringPromotable = - new StringPromotable { - override val toString = in.toString - } -} - -/** - * This trait is both a NodeSeq => NodeSeq and has the ability - * to chain CssSel instances so that they can be applied - * en masse to incoming NodeSeq and do the transformation. - */ -sealed trait CssSel extends Function1[NodeSeq, NodeSeq] { - def &(other: CssSel): CssSel = (this, other) match { - case (AggregatedCssBindFunc(a), AggregatedCssBindFunc(b)) => - AggregatedCssBindFunc(a ::: b) - case (AggregatedCssBindFunc(a), o: CssBind) => - AggregatedCssBindFunc(a ::: List(o)) - case (t: CssBind, AggregatedCssBindFunc(a)) => - AggregatedCssBindFunc(t :: a) - case (t: CssBind, o: CssBind) => AggregatedCssBindFunc(List(t, o)) - } - - /** - * A Java callable aggregator - */ - def and(that: CssSel): CssSel = this & that - - /** - * promote a String to a ToCssBindPromotor - */ - private implicit def strToCssBindPromoter(str: String): ToCssBindPromoter = - new ToCssBindPromoter(Full(str), CssSelectorParser.parse(str)) - - - /** - * Inserts a String constant according to the CssSelector rules - */ - def sel(selector: String, str: String): CssSel = this & (selector #> str) - - /** - * Inserts a String constant according to the CssSelector rules - */ - def sel(selector: String, str: NodeSeq): CssSel = this & (selector #> str) - - - /** - * Inserts a String constant according to the CssSelector rules - */ - def sel(selector: String, str: NodeSeq => NodeSeq): CssSel = this & (selector #> str) - - /** - * Inserts a String constant according to the CssSelector rules - */ - def sel(selector: String, str: Bindable): CssSel = this & (selector #> str) - - /** - * Inserts a String constant according to the CssSelector rules - */ - def sel(selector: String, str: StringPromotable): CssSel = this & (selector #> str) - - /** - * Inserts a String constant according to the CssSelector rules - */ - def sel(selector: String, str: IterableConst): CssSel = this & (selector #> str) - - /** - * Inserts a String constant according to the CssSelector rules - */ - def sel(selector: String, str: IterableFunc): CssSel = this & (selector #> str) -} - -/** - * A passthrough function that does not change the nodes - * - * @tag CssFunction - */ -object PassThru extends Function1[NodeSeq, NodeSeq] { - def apply(in: NodeSeq): NodeSeq = in -} - -/** - * Replaces the nodes with an Empty NodeSeq. Useful - * for removing unused nodes - * - * @tag CssFunction - */ -object ClearNodes extends Function1[NodeSeq, NodeSeq] { - def apply(in: NodeSeq): NodeSeq = NodeSeq.Empty -} - - - -private final case class AggregatedCssBindFunc(binds: List[CssBind]) extends CssSel { - private lazy val (good, bad) = binds.partition{_.css.isDefined} - private lazy val selectorMap = new SelectorMap(good) - - def apply(in: NodeSeq): NodeSeq = bad match { - case Nil => selectorMap(in) - case bv => bad.flatMap(_(in)) ++ selectorMap(in) - } -} - -/** - * This CssBind will clear all nodes marked with the class - * clearable. Designers can mark extra nodes in markup with - * class="clearable" and this Bind will make them go away - */ -class ClearClearable extends CssBindImpl(Full(".clearable"), CssSelectorParser.parse(".clearable")) { - - def calculate(in: NodeSeq): Seq[NodeSeq] = Nil -} -/** - * This CssBind will clear all nodes marked with the class - * clearable. Designers can mark extra nodes in markup with - * class="clearable" and this Bind will make them go away - */ -object ClearClearable extends ClearClearable - -private class SelectorMap(binds: List[CssBind]) extends Function1[NodeSeq, NodeSeq] { - - // The KidsSubNode always has to go last or else we - // get into an issue where we're trying to apply the whole - // transform to the whole shooting match - private def sortBinds(lst: List[CssBind]): List[CssBind] = { - lst.sortWith { - case (SubNode(me: EmptyBox), SubNode(_)) => true - case (SubNode(_), SubNode(them: EmptyBox)) => false - case (SubNode(Full(KidsSubNode())), SubNode(_)) => false - case (SubNode(Full(PrependKidsSubNode())), SubNode(_)) => false - case (SubNode(Full(AppendKidsSubNode())), SubNode(_)) => false - case (SubNode(_), SubNode(Full(KidsSubNode()))) => true - case (SubNode(_), SubNode(Full(PrependKidsSubNode()))) => true - case (SubNode(_), SubNode(Full(AppendKidsSubNode()))) => true - case _ => true - } - } - - private val (idMap, nameMap, clzMap, attrMap, elemMap, - starFunc, selectThis: Box[CssBind]) = { - var idMap: Map[String, List[CssBind]] = Map() - var nameMap: Map[String, List[CssBind]] = Map() - var clzMap: Map[String, List[CssBind]] = Map() - var attrMap: Map[String, Map[String, List[CssBind]]] = Map() - var elemMap: Map[String, List[CssBind]] = Map() - var starFunc: Box[List[CssBind]] = Empty - - val selThis: Box[CssBind] = binds.flatMap { - b => - b.css.open_!.subNodes match { - case Full(SelectThisNode(_)) => List(b) - case _ => Nil - } - }.headOption - - binds.foreach { - case i @ CssBind(IdSelector(id, _)) => - idMap += (id -> sortBinds(i :: idMap.getOrElse(id, Nil))) - - case i @ CssBind(ElemSelector(id, _)) => - elemMap += (id -> sortBinds(i :: elemMap.getOrElse(id, Nil))) - - - case i @ CssBind(StarSelector(_)) => starFunc = Full(sortBinds(i :: starFunc.openOr(Nil))) - - case i @ CssBind(NameSelector(name, _)) => - nameMap += (name -> sortBinds(i :: nameMap.getOrElse(name, Nil))) - - case i @ CssBind(ClassSelector(clz, _)) => - clzMap += (clz -> sortBinds(i :: clzMap.getOrElse(clz, Nil))) - - case i @ CssBind(AttrSelector(name, value, _)) => { - val oldMap = attrMap.getOrElse(name, Map()) - attrMap += (name -> (oldMap + (value -> sortBinds(i :: oldMap.getOrElse(value, Nil))))) - } - } - - (idMap, nameMap, clzMap, attrMap, elemMap, starFunc, selThis) - } - - private def findElemIfThereIsOne(in: NodeSeq): NodeSeq = in match { - case e: Elem => e - case ns if ns.length == 1 && ns(0).isInstanceOf[Elem] => ns(0) - case ns => ns - } - - private abstract class SlurpedAttrs(val id: Box[String],val name: Box[String]) { - def attrs: Map[String, String] - def classes: List[String] - - def removeId(in: MetaData) = in.filter { - case up: UnprefixedAttribute => up.key != "id" - case _ => true - } - - private final def isSelThis(bind: CssBind): Boolean = - bind.css.open_!.subNodes match { - case Full(SelectThisNode(_)) => true - case _ => false - } - - final def applyRule(bindList: List[CssBind], realE: Elem, onlySelThis: Boolean): NodeSeq = - bindList match { - case Nil => realE - - // ignore selectThis commands outside the - // select context - case bind :: xs - if onlySelThis && isSelThis(bind) => applyRule(xs, realE, onlySelThis) - - case bind :: xs => { - applyRule(bind, realE) flatMap { - case e: Elem => applyRule(xs, e, onlySelThis) - case x => x - } - } - } - - final def applyAttributeRules(bindList: List[CssBind], elem: Elem): Elem = { - bindList.map(b => (b, b.css.open_!.subNodes.open_!)). - foldLeft(elem){ - case (elem, (bind, AttrSubNode(attr))) => { - val calced = bind.calculate(elem).map(findElemIfThereIsOne _) - val filtered = elem.attributes.filter{ - case up: UnprefixedAttribute => up.key != attr - case _ => true - } - - val newAttr = if (calced.isEmpty) { - filtered - } else { - val flat: NodeSeq = calced.flatMap(a => a) - new UnprefixedAttribute(attr, flat, filtered) - } - - new Elem(elem.prefix, - elem.label, newAttr, - elem.scope, elem.child :_*) - } - - case (elem, (bind, AttrAppendSubNode(attr))) => { - val org: NodeSeq = elem.attribute(attr).getOrElse(NodeSeq.Empty) - val calced = bind.calculate(elem).toList.map(findElemIfThereIsOne _) - - - if (calced.isEmpty) { - elem - } else { - val filtered = elem.attributes.filter{ - case up: UnprefixedAttribute => up.key != attr - case _ => true - } - - val flat: NodeSeq = if (attr == "class") { - if (org.isEmpty) { - calced.dropRight(1).flatMap(a => a ++ Text(" ")) ++ - calced.takeRight(1).head - } else { - org ++ Text(" ") ++ - calced.dropRight(1).flatMap(a => a ++ Text(" ")) ++ - calced.takeRight(1).head - } - } else { - org ++ (calced.flatMap(a => a): NodeSeq) - } - - val newAttr = new UnprefixedAttribute(attr, flat, filtered) - - new Elem(elem.prefix, - elem.label, newAttr, - elem.scope, elem.child :_*) - - } - } - - case (elem, (bind, AttrRemoveSubNode(attr))) => { - val org: NodeSeq = elem.attribute(attr).getOrElse(NodeSeq.Empty) - val calced = bind.calculate(elem).toList.map(findElemIfThereIsOne _) - - if (calced.isEmpty || org.isEmpty) { // if either is empty, then return the Elem unmodified - elem - } else { - val filtered = elem.attributes.filter{ - case up: UnprefixedAttribute => up.key != attr - case _ => true - } - - val flat: Box[NodeSeq] = if (attr == "class") { - val set = Set(calced.map(_.text) :_*) - SuperString(org.text).charSplit(' ').toList. - filter(_.length > 0).filter(s => !set.contains(s)) match { - case Nil => Empty - case xs => Full(Text(xs.mkString(" "))) - } - } else { - if (org.text == calced.flatMap(a => a).text) Empty else Full(org) - } - - val newAttr = flat match { - case Full(a) => new UnprefixedAttribute(attr, a, filtered) - case _ => filtered - } - - new Elem(elem.prefix, - elem.label, newAttr, - elem.scope, elem.child :_*) - - } - } - } - } - - - // This is where the rules are applied - final def applyRule(bind: CssBind, realE: Elem): NodeSeq = { - def uniqueClasses(cv: String*): String = { - import Helpers._ - - val ls: List[String] = cv.toList.flatMap(_.charSplit(' ')) - import scala.collection.mutable._ - val hs: HashSet[String] = new HashSet() - val ret: ListBuffer[String] = new ListBuffer() - ls.foreach { - v => - if (!hs.contains(v)) { - hs += v - ret += v - } - } - ret.mkString(" ") - } - - def mergeAll(other: MetaData, stripId: Boolean, ignoreClass: Boolean): MetaData = { - var oldAttrs = attrs - (if (stripId) "id" else "") - - var builtMeta: MetaData = Null - var pos = other - - while (pos != Null) { - pos match { - case up: UnprefixedAttribute if stripId && up.key == "id" => - // ignore the id attribute - - case up: UnprefixedAttribute if up.key == "class" => { - oldAttrs.get("class") match { - case Some(ca) if !ignoreClass => { - oldAttrs -= "class" - builtMeta = new UnprefixedAttribute("class", - uniqueClasses(up.value. - text, - ca), - builtMeta) - } - - case _ => - oldAttrs -= "class" - builtMeta = up.copy(builtMeta) - } - } - - case up: UnprefixedAttribute => { - oldAttrs -= up.key - builtMeta = up.copy(builtMeta) - } - - case pa: PrefixedAttribute => { - oldAttrs -= (pa.pre+":"+pa.key) - builtMeta = pa.copy(builtMeta) - } - case _ => - } - - pos = pos.next - } - - for { - (k, v) <- oldAttrs - } { - import Helpers._ - k.charSplit(':') match { - case p :: k :: _ => - builtMeta = new PrefixedAttribute(p, k, v, builtMeta) - case k :: _ => builtMeta = new UnprefixedAttribute(k, v, builtMeta) - case _ => - } - } - - builtMeta - } - - // - bind.css.openOrThrowException("we can do an open_! here because all the CssBind elems have been vetted").subNodes match { - case Full(SelectThisNode(kids)) => { - throw new RetryWithException(if (kids) realE.child else realE) - } - - case Full(todo: WithKids) => { - val calced = bind.calculate(realE.child) - calced.length match { - case 0 => new Elem(realE.prefix, realE.label, realE.attributes, realE.scope) - case 1 => new Elem(realE.prefix, realE.label, - realE.attributes, realE.scope, - todo.transform(realE.child, calced.head) :_*) - case _ if id.isEmpty => - calced.map(kids => new Elem(realE.prefix, realE.label, - realE.attributes, realE.scope, - todo.transform(realE.child, kids) :_*)) - - case _ => { - val noId = removeId(realE.attributes) - calced.toList.zipWithIndex.map { - case (kids, 0) => - new Elem(realE.prefix, realE.label, - realE.attributes, realE.scope, - todo.transform(realE.child, kids) :_*) - case (kids, _) => - new Elem(realE.prefix, realE.label, - noId, realE.scope, - todo.transform(realE.child, kids) :_*) - } - } - } - } - - case x if Full(DontMergeAttributes) == x || x.isInstanceOf[EmptyBox] => { - val calced = bind.calculate(realE).map(findElemIfThereIsOne _) - - calced.length match { - case 0 => NodeSeq.Empty - case 1 => { - calced.head match { - case Group(g) => g - case e: Elem => new Elem(e.prefix, - e.label, mergeAll(e.attributes, false, Full(DontMergeAttributes) == x), - e.scope, e.child :_*) - case x => x - } - } - - case n => { - val calcedList = calced.toList - val availableIds = (attrs.get("id").toList ++ - calcedList.collect({ case e:Elem => e.attribute("id") }).flatten.map(_.toString)).toSet - val merged = calcedList.foldLeft((availableIds, Nil: List[Seq[xml.Node]])) { (idsAndResult, a) => - val (ids, result) = idsAndResult - a match { - case Group(g) => (ids, g :: result) - case e:Elem => { - val targetId = e.attribute("id").map(_.toString) orElse (attrs.get("id")) - val keepId = targetId map { id => ids.contains(id) } getOrElse (false) - val newIds = targetId filter (_ => keepId) map (i => ids - i) getOrElse (ids) - val newElem = new Elem(e.prefix, e.label, mergeAll(e.attributes, ! keepId, Full(DontMergeAttributes) == x), e.scope, e.child: _*) - (newIds, newElem :: result) - } - case x => (ids, x :: result) - } - } - merged._2.reverse.flatten - } - } - } - } - } - - - final def forId(in: Elem, buff: ListBuffer[CssBind]) { - for { - rid <- id - bind <- idMap.get(rid) - } buff ++= bind - } - - final def forElem(in: Elem, buff: ListBuffer[CssBind]) { - for { - bind <- elemMap.get(in.label) - } buff ++= bind - } - - final def forStar(buff: ListBuffer[CssBind]) { - for { - bind <- starFunc - } buff ++= bind - } - - final def forName(in: Elem, buff: ListBuffer[CssBind]) { - for { - rid <- name - bind <- nameMap.get(rid) - } buff ++= bind - } - - def findClass(clz: List[String], buff: ListBuffer[CssBind]) { - clz match { - case Nil => () - case x :: xs => { - clzMap.get(x) match { - case Some(cb) => buff ++= cb - case _ => - } - findClass(xs, buff) - } - } - } - - def forClass(in: Elem, buff: ListBuffer[CssBind]) { - findClass(classes, buff) - } - - def forAttr(in: Elem, buff: ListBuffer[CssBind]) { - if (attrMap.isEmpty || attrs.isEmpty) () - else { - for { - (key, map) <- attrMap - v <- attrs.get(key) - cb <- map.get(v) - } buff ++= cb - } - } - } - - private def slurpAttrs(in: MetaData): SlurpedAttrs = { - var id: Box[String] = Empty - var cur: MetaData = in - var name: Box[String] = Empty - var clzs: List[String] = Nil - var theAttrs: Map[String, String] = Map() - - while (cur != Null) { - cur match { - case up: UnprefixedAttribute if (null ne up.value) => { - val key = up.key - val value = up.value.text - import Helpers._ - key match { - case "id" => id = Full(value) - case "name" => name = Full(value) - case "class" => clzs = value.charSplit(' ') - case _ => - } - - theAttrs += key -> value - } - - case pa: PrefixedAttribute if (null ne pa.value) => { - theAttrs += ((pa.pre+":"+pa.key) -> pa.value.text) - } - - case _ => - } - - cur = cur.next - } - - new SlurpedAttrs(id, name) { - def attrs: Map[String, String] = theAttrs - def classes: List[String] = clzs - } - } - - final private def treatElem(e: Elem, onlySel: Boolean): NodeSeq = { - val slurp = slurpAttrs(e.attributes) - val lb = new ListBuffer[CssBind] - - slurp.forId(e, lb) - slurp.forName(e, lb) - slurp.forClass(e, lb) - slurp.forElem(e, lb) - slurp.forAttr(e, lb) - slurp.forStar(lb) - - if (onlySel) { - lb.toList.filter(_.selectThis_?) match { - case Nil => { - run(e.child, onlySel) - NodeSeq.Empty - } - - case csb :: _ => - throw new RetryWithException(if (csb.selectThisChildren_?) - e.child else e) - } - } else { - lb.toList.filterNot(_.selectThis_?) match { - case Nil => new Elem(e.prefix, e.label, - e.attributes, e.scope, run(e.child, onlySel) :_*) - case csb => - // do attributes first, then the body - csb.partition(_.attrSel_?) match { - case (Nil, rules) => slurp.applyRule(rules, e, onlySel) - case (attrs, Nil) => { - val elem = slurp.applyAttributeRules(attrs, e) - new Elem(elem.prefix, elem.label, - elem.attributes, elem.scope, run(elem.child, onlySel) :_*) - } - - case (attrs, rules) => { - slurp.applyRule(rules, - slurp.applyAttributeRules(attrs, e), - onlySel) - } - } - // slurp.applyRule(csb, e, onlySel) - } - } - } - - final def apply(in: NodeSeq): NodeSeq = selectThis match { - case Full(_) => { - try { - run(in, true) - } catch { - case RetryWithException(newElem) => - run(newElem, false) - } - } - - case _ => run(in, false) - } - - final private def run(in: NodeSeq, onlyRunSel: Boolean): NodeSeq = - in flatMap { - case Group(g) => run(g, onlyRunSel) - case e: Elem => treatElem(e, onlyRunSel) - case x => x - } -} - -private case class RetryWithException(e: NodeSeq) extends Exception() - -object CssBind { - def unapply(in: CssBind): Option[CssSelector] = in.css -} - -sealed trait CssBind extends CssSel { - def stringSelector: Box[String] - def css: Box[CssSelector] - - override def toString(): String = "CssBind("+stringSelector+", "+ - css+")" - - def apply(in: NodeSeq): NodeSeq = css match { - case Full(c) => selectorMap(in) - case _ => Helpers.errorDiv( -
    - Syntax error in CSS selector definition: {stringSelector openOr "N/A"}. - The selector will not be applied. -
    ) openOr NodeSeq.Empty - } - - /** - * Is this CssBind a SelectThis bind? - */ - private[util] def selectThis_? : Boolean = css match { - case Full(sel) => { - sel.subNodes match { - case Full(SelectThisNode(_)) => true - case _ => false - } - } - - case _ => false - } - - /** - * Is this an Attribute mutating node? - */ - private[util] def attrSel_? : Boolean = css match { - case Full(sel) => { - sel.subNodes match { - case Full(x: AttributeRule) => true - case _ => false - } - } - - case _ => false - } - - private[util] def selectThisChildren_? : Boolean = css match { - case Full(sel) => { - sel.subNodes match { - case Full(SelectThisNode(children)) => children - case _ => false - } - } - - case _ => false - } - - private lazy val selectorMap: SelectorMap = new SelectorMap(List(this)) - - def calculate(in: NodeSeq): Seq[NodeSeq] -} - -/** - * An abstract implementation of CssBind. You can instantiate - * this class and create a custom calculate method - */ -abstract class CssBindImpl(val stringSelector: Box[String], val css: Box[CssSelector]) extends CssBind { - def calculate(in: NodeSeq): Seq[NodeSeq] -} - -/** - * Bridge from Java-land to Scala - */ -final class CssJBridge { - /** - * promote a String to a ToCssBindPromotor - */ - private implicit def strToCssBindPromoter(str: String): ToCssBindPromoter = - new ToCssBindPromoter(Full(str), CssSelectorParser.parse(str)) - - def sel(selector: String, value: String): CssSel = selector #> value - def sel(selector: String, value: NodeSeq): CssSel = selector #> value - def sel(selector: String, value: NodeSeq => NodeSeq): CssSel = selector #> value - def sel(selector: String, value: Bindable): CssSel = selector #> value - - /** - * Inserts a String constant according to the CssSelector rules - */ - def sel(selector: String, str: StringPromotable): CssSel = (selector #> str) - - /** - * Inserts a String constant according to the CssSelector rules - */ - def sel(selector: String, str: IterableConst): CssSel = (selector #> str) - - /** - * Inserts a String constant according to the CssSelector rules - */ - def sel(selector: String, str: IterableFunc): CssSel = (selector #> str) - } // vim: set ts=2 sw=2 et: diff --git a/core/util/src/main/scala/net/liftweb/util/CssSel.scala b/core/util/src/main/scala/net/liftweb/util/CssSel.scala new file mode 100644 index 0000000000..de821cce8a --- /dev/null +++ b/core/util/src/main/scala/net/liftweb/util/CssSel.scala @@ -0,0 +1,865 @@ +package net.liftweb +package util + +import common._ +import xml._ +import collection.mutable.ListBuffer + +/** + * Created with IntelliJ IDEA. + * User: dpp + * Date: 6/25/12 + * Time: 3:34 PM + * + */ + +/** + * This trait is both a NodeSeq => NodeSeq and has the ability + * to chain CssSel instances so that they can be applied + * en masse to incoming NodeSeq and do the transformation. + */ +trait CssSel extends Function1[NodeSeq, NodeSeq] { + def &(other: CssSel): CssSel = (this, other) match { + case (AggregatedCssBindFunc(a), AggregatedCssBindFunc(b)) => + AggregatedCssBindFunc(a ::: b) + case (AggregatedCssBindFunc(a), o: CssBind) => + AggregatedCssBindFunc(a ::: List(o)) + case (t: CssBind, AggregatedCssBindFunc(a)) => + AggregatedCssBindFunc(t :: a) + case (t: CssBind, o: CssBind) => AggregatedCssBindFunc(List(t, o)) + } + + /** + * A Java callable aggregator + */ + def and(that: CssSel): CssSel = this & that + + /** + * promote a String to a ToCssBindPromotor + */ + private implicit def strToCssBindPromoter(str: String): ToCssBindPromoter = + new ToCssBindPromoter(Full(str), CssSelectorParser.parse(str)) + +} + +/** + * A passthrough function that does not change the nodes + * + * @tag CssFunction + */ +object PassThru extends Function1[NodeSeq, NodeSeq] { + def apply(in: NodeSeq): NodeSeq = in +} + +/** + * Replaces the nodes with an Empty NodeSeq. Useful + * for removing unused nodes + * + * @tag CssFunction + */ +object ClearNodes extends Function1[NodeSeq, NodeSeq] { + def apply(in: NodeSeq): NodeSeq = NodeSeq.Empty +} + + + +private final case class AggregatedCssBindFunc(binds: List[CssBind]) extends CssSel { + private lazy val (good, bad) = binds.partition{_.css.isDefined} + private lazy val selectorMap = new SelectorMap(good) + + def apply(in: NodeSeq): NodeSeq = bad match { + case Nil => selectorMap(in) + case bv => bad.flatMap(_(in)) ++ selectorMap(in) + } +} + +/** + * This CssBind will clear all nodes marked with the class + * clearable. Designers can mark extra nodes in markup with + * class="clearable" and this Bind will make them go away + */ +class ClearClearable extends CssBindImpl(Full(".clearable"), CssSelectorParser.parse(".clearable")) { + + def calculate(in: NodeSeq): Seq[NodeSeq] = Nil +} + +/** + * This CssBind will clear all nodes marked with the class + * clearable. Designers can mark extra nodes in markup with + * class="clearable" and this Bind will make them go away + */ +object ClearClearable extends ClearClearable + +private class SelectorMap(binds: List[CssBind]) extends Function1[NodeSeq, NodeSeq] { + + // The KidsSubNode always has to go last or else we + // get into an issue where we're trying to apply the whole + // transform to the whole shooting match + private def sortBinds(lst: List[CssBind]): List[CssBind] = { + lst.sortWith { + case (SubNode(me: EmptyBox), SubNode(_)) => true + case (SubNode(_), SubNode(them: EmptyBox)) => false + case (SubNode(Full(KidsSubNode())), SubNode(_)) => false + case (SubNode(Full(PrependKidsSubNode())), SubNode(_)) => false + case (SubNode(Full(AppendKidsSubNode())), SubNode(_)) => false + case (SubNode(_), SubNode(Full(KidsSubNode()))) => true + case (SubNode(_), SubNode(Full(PrependKidsSubNode()))) => true + case (SubNode(_), SubNode(Full(SurroundKids()))) => true + case (SubNode(_), SubNode(Full(AppendKidsSubNode()))) => true + case _ => true + } + } + + private val (idMap, nameMap, clzMap, attrMap, elemMap, + starFunc, selectThis: Box[CssBind]) = { + var idMap: Map[String, List[CssBind]] = Map() + var nameMap: Map[String, List[CssBind]] = Map() + var clzMap: Map[String, List[CssBind]] = Map() + var attrMap: Map[String, Map[String, List[CssBind]]] = Map() + var elemMap: Map[String, List[CssBind]] = Map() + var starFunc: Box[List[CssBind]] = Empty + + val selThis: Box[CssBind] = binds.flatMap { + b => + b.css.open_!.subNodes match { + case Full(SelectThisNode(_)) => List(b) + case _ => Nil + } + }.headOption + + binds.foreach { + case i @ CssBind(IdSelector(id, _)) => + idMap += (id -> sortBinds(i :: idMap.getOrElse(id, Nil))) + + case i @ CssBind(ElemSelector(id, _)) => + elemMap += (id -> sortBinds(i :: elemMap.getOrElse(id, Nil))) + + + case i @ CssBind(StarSelector(_)) => starFunc = Full(sortBinds(i :: starFunc.openOr(Nil))) + + case i @ CssBind(NameSelector(name, _)) => + nameMap += (name -> sortBinds(i :: nameMap.getOrElse(name, Nil))) + + case i @ CssBind(ClassSelector(clz, _)) => + clzMap += (clz -> sortBinds(i :: clzMap.getOrElse(clz, Nil))) + + case i @ CssBind(AttrSelector(name, value, _)) => { + val oldMap = attrMap.getOrElse(name, Map()) + attrMap += (name -> (oldMap + (value -> sortBinds(i :: oldMap.getOrElse(value, Nil))))) + } + } + + (idMap, nameMap, clzMap, attrMap, elemMap, starFunc, selThis) + } + + private def findElemIfThereIsOne(in: NodeSeq): NodeSeq = in match { + case e: Elem => e + case ns if ns.length == 1 && ns(0).isInstanceOf[Elem] => ns(0) + case ns => ns + } + + private abstract class SlurpedAttrs(val id: Box[String],val name: Box[String]) { + def attrs: Map[String, String] + def classes: List[String] + + def removeId(in: MetaData) = in.filter { + case up: UnprefixedAttribute => up.key != "id" + case _ => true + } + + private final def isSelThis(bind: CssBind): Boolean = + bind.css.open_!.subNodes match { + case Full(SelectThisNode(_)) => true + case _ => false + } + + final def applyRule(bindList: List[CssBind], realE: Elem, onlySelThis: Boolean): NodeSeq = + bindList match { + case Nil => realE + + // ignore selectThis commands outside the + // select context + case bind :: xs + if onlySelThis && isSelThis(bind) => applyRule(xs, realE, onlySelThis) + + case bind :: xs => { + applyRule(bind, realE) flatMap { + case e: Elem => applyRule(xs, e, onlySelThis) + case x => x + } + } + } + + final def applyAttributeRules(bindList: List[CssBind], elem: Elem): Elem = { + bindList.map(b => (b, b.css.open_!.subNodes.open_!)). + foldLeft(elem){ + case (elem, (bind, AttrSubNode(attr))) => { + val calced = bind.calculate(elem).map(findElemIfThereIsOne _) + val filtered = elem.attributes.filter{ + case up: UnprefixedAttribute => up.key != attr + case _ => true + } + + val newAttr = if (calced.isEmpty) { + filtered + } else { + val flat: NodeSeq = calced.flatMap(a => a) + new UnprefixedAttribute(attr, flat, filtered) + } + + new Elem(elem.prefix, + elem.label, newAttr, + elem.scope, elem.child :_*) + } + + case (elem, (bind, AttrAppendSubNode(attr))) => { + val org: NodeSeq = elem.attribute(attr).getOrElse(NodeSeq.Empty) + val calced = bind.calculate(elem).toList.map(findElemIfThereIsOne _) + + + if (calced.isEmpty) { + elem + } else { + val filtered = elem.attributes.filter{ + case up: UnprefixedAttribute => up.key != attr + case _ => true + } + + val flat: NodeSeq = if (attr == "class") { + if (org.isEmpty) { + calced.dropRight(1).flatMap(a => a ++ Text(" ")) ++ + calced.takeRight(1).head + } else { + org ++ Text(" ") ++ + calced.dropRight(1).flatMap(a => a ++ Text(" ")) ++ + calced.takeRight(1).head + } + } else { + org ++ (calced.flatMap(a => a): NodeSeq) + } + + val newAttr = new UnprefixedAttribute(attr, flat, filtered) + + new Elem(elem.prefix, + elem.label, newAttr, + elem.scope, elem.child :_*) + + } + } + + case (elem, (bind, AttrRemoveSubNode(attr))) => { + val org: NodeSeq = elem.attribute(attr).getOrElse(NodeSeq.Empty) + val calced = bind.calculate(elem).toList.map(findElemIfThereIsOne _) + + if (calced.isEmpty || org.isEmpty) { // if either is empty, then return the Elem unmodified + elem + } else { + val filtered = elem.attributes.filter{ + case up: UnprefixedAttribute => up.key != attr + case _ => true + } + + val flat: Box[NodeSeq] = if (attr == "class") { + val set = Set(calced.map(_.text) :_*) + SuperString(org.text).charSplit(' ').toList. + filter(_.length > 0).filter(s => !set.contains(s)) match { + case Nil => Empty + case xs => Full(Text(xs.mkString(" "))) + } + } else { + if (org.text == calced.flatMap(a => a).text) Empty else Full(org) + } + + val newAttr = flat match { + case Full(a) => new UnprefixedAttribute(attr, a, filtered) + case _ => filtered + } + + new Elem(elem.prefix, + elem.label, newAttr, + elem.scope, elem.child :_*) + + } + } + } + } + + + // This is where the rules are applied + final def applyRule(bind: CssBind, realE: Elem): NodeSeq = { + def uniqueClasses(cv: String*): String = { + import Helpers._ + + val ls: List[String] = cv.toList.flatMap(_.charSplit(' ')) + import scala.collection.mutable._ + val hs: HashSet[String] = new HashSet() + val ret: ListBuffer[String] = new ListBuffer() + ls.foreach { + v => + if (!hs.contains(v)) { + hs += v + ret += v + } + } + ret.mkString(" ") + } + + def mergeAll(other: MetaData, stripId: Boolean, skipClassMerge: Boolean): MetaData = { + var oldAttrs = attrs - (if (stripId) "id" else "") + + var builtMeta: MetaData = Null + var pos = other + + while (pos != Null) { + pos match { + case up: UnprefixedAttribute if stripId && up.key == "id" => + // ignore the id attribute + + case up: UnprefixedAttribute if up.key == "class" => { + oldAttrs.get("class") match { + case Some(ca) if !skipClassMerge => { + oldAttrs -= "class" + builtMeta = new UnprefixedAttribute("class", + uniqueClasses(up.value. + text, + ca), + builtMeta) + } + + case _ => + oldAttrs -= "class" + builtMeta = up.copy(builtMeta) + } + } + + case up: UnprefixedAttribute => { + oldAttrs -= up.key + builtMeta = up.copy(builtMeta) + } + + case pa: PrefixedAttribute => { + oldAttrs -= (pa.pre+":"+pa.key) + builtMeta = pa.copy(builtMeta) + } + case _ => + } + + pos = pos.next + } + + for { + (k, v) <- oldAttrs + } { + import Helpers._ + k.charSplit(':') match { + case p :: k :: _ => + builtMeta = new PrefixedAttribute(p, k, v, builtMeta) + case k :: _ => builtMeta = new UnprefixedAttribute(k, v, builtMeta) + case _ => + } + } + + builtMeta + } + + // we can do an open_! here because all the CssBind elems + // have been vetted + bind.css.open_!.subNodes match { + case Full(SelectThisNode(kids)) => { + throw new RetryWithException(if (kids) realE.child else realE) + } + + case Full(todo: WithKids) => { + val calced = bind.calculate(realE.child) + calced.length match { + case 0 => new Elem(realE.prefix, realE.label, realE.attributes, realE.scope) + case 1 => new Elem(realE.prefix, realE.label, + realE.attributes, realE.scope, + todo.transform(realE.child, calced.head) :_*) + case _ if id.isEmpty => + calced.map(kids => new Elem(realE.prefix, realE.label, + realE.attributes, realE.scope, + todo.transform(realE.child, kids) :_*)) + + case _ => { + val noId = removeId(realE.attributes) + calced.toList.zipWithIndex.map { + case (kids, 0) => + new Elem(realE.prefix, realE.label, + realE.attributes, realE.scope, + todo.transform(realE.child, kids) :_*) + case (kids, _) => + new Elem(realE.prefix, realE.label, + noId, realE.scope, + todo.transform(realE.child, kids) :_*) + } + } + } + } + + case x if x.isInstanceOf[EmptyBox] || x == Full(DontMergeAttributes) => { + val calced = bind.calculate(realE).map(findElemIfThereIsOne _) + + calced.length match { + case 0 => NodeSeq.Empty + case 1 => { + calced.head match { + case Group(g) => g + case e: Elem => new Elem(e.prefix, + e.label, mergeAll(e.attributes, false, x == Full(DontMergeAttributes)), + e.scope, e.child :_*) + case x => x + } + } + + case n => { + val calcedList = calced.toList + val availableIds = (attrs.get("id").toList ++ + calcedList.collect({ case e:Elem => e.attribute("id") }).flatten.map(_.toString)).toSet + val merged = calcedList.foldLeft((availableIds, Nil: List[Seq[xml.Node]])) { (idsAndResult, a) => + val (ids, result) = idsAndResult + a match { + case Group(g) => (ids, g :: result) + case e:Elem => { + val targetId = e.attribute("id").map(_.toString) orElse (attrs.get("id")) + val keepId = targetId map { id => ids.contains(id) } getOrElse (false) + val newIds = targetId filter (_ => keepId) map (i => ids - i) getOrElse (ids) + val newElem = new Elem(e.prefix, e.label, mergeAll(e.attributes, ! keepId, x == Full(DontMergeAttributes)), e.scope, e.child: _*) + (newIds, newElem :: result) + } + case x => (ids, x :: result) + } + } + merged._2.reverse.flatten + } + } + } + } + } + + + final def forId(in: Elem, buff: ListBuffer[CssBind]) { + for { + rid <- id + bind <- idMap.get(rid) + } buff ++= bind + } + + final def forElem(in: Elem, buff: ListBuffer[CssBind]) { + for { + bind <- elemMap.get(in.label) + } buff ++= bind + } + + final def forStar(buff: ListBuffer[CssBind]) { + for { + bind <- starFunc + } buff ++= bind + } + + final def forName(in: Elem, buff: ListBuffer[CssBind]) { + for { + rid <- name + bind <- nameMap.get(rid) + } buff ++= bind + } + + def findClass(clz: List[String], buff: ListBuffer[CssBind]) { + clz match { + case Nil => () + case x :: xs => { + clzMap.get(x) match { + case Some(cb) => buff ++= cb + case _ => + } + findClass(xs, buff) + } + } + } + + def forClass(in: Elem, buff: ListBuffer[CssBind]) { + findClass(classes, buff) + } + + def forAttr(in: Elem, buff: ListBuffer[CssBind]) { + if (attrMap.isEmpty || attrs.isEmpty) () + else { + for { + (key, map) <- attrMap + v <- attrs.get(key) + cb <- map.get(v) + } buff ++= cb + } + } + } + + private def slurpAttrs(in: MetaData): SlurpedAttrs = { + var id: Box[String] = Empty + var cur: MetaData = in + var name: Box[String] = Empty + var clzs: List[String] = Nil + var theAttrs: Map[String, String] = Map() + + while (cur != Null) { + cur match { + case up: UnprefixedAttribute if (null ne up.value) => { + val key = up.key + val value = up.value.text + import Helpers._ + key match { + case "id" => id = Full(value) + case "name" => name = Full(value) + case "class" => clzs = value.charSplit(' ') + case _ => + } + + theAttrs += key -> value + } + + case pa: PrefixedAttribute if (null ne pa.value) => { + theAttrs += ((pa.pre+":"+pa.key) -> pa.value.text) + } + + case _ => + } + + cur = cur.next + } + + new SlurpedAttrs(id, name) { + def attrs: Map[String, String] = theAttrs + def classes: List[String] = clzs + } + } + + final private def treatElem(e: Elem, onlySel: Boolean): NodeSeq = { + val slurp = slurpAttrs(e.attributes) + val lb = new ListBuffer[CssBind] + + slurp.forId(e, lb) + slurp.forName(e, lb) + slurp.forClass(e, lb) + slurp.forElem(e, lb) + slurp.forAttr(e, lb) + slurp.forStar(lb) + + if (onlySel) { + lb.toList.filter(_.selectThis_?) match { + case Nil => { + run(e.child, onlySel) + NodeSeq.Empty + } + + case csb :: _ => + throw new RetryWithException(if (csb.selectThisChildren_?) + e.child else e) + } + } else { + lb.toList.filterNot(_.selectThis_?) match { + case Nil => new Elem(e.prefix, e.label, + e.attributes, e.scope, run(e.child, onlySel) :_*) + case csb => + // do attributes first, then the body + csb.partition(_.attrSel_?) match { + case (Nil, rules) => slurp.applyRule(rules, e, onlySel) + case (attrs, Nil) => { + val elem = slurp.applyAttributeRules(attrs, e) + new Elem(elem.prefix, elem.label, + elem.attributes, elem.scope, run(elem.child, onlySel) :_*) + } + + case (attrs, rules) => { + slurp.applyRule(rules, + slurp.applyAttributeRules(attrs, e), + onlySel) + } + } + // slurp.applyRule(csb, e, onlySel) + } + } + } + + final def apply(in: NodeSeq): NodeSeq = selectThis match { + case Full(_) => { + try { + run(in, true) + } catch { + case RetryWithException(newElem) => + run(newElem, false) + } + } + + case _ => run(in, false) + } + + final private def run(in: NodeSeq, onlyRunSel: Boolean): NodeSeq = + in flatMap { + case Group(g) => run(g, onlyRunSel) + case e: Elem => treatElem(e, onlyRunSel) + case x => x + } +} + +private case class RetryWithException(e: NodeSeq) extends Exception() + +object CssBind { + def unapply(in: CssBind): Option[CssSelector] = in.css +} + +trait CssBind extends CssSel { + def stringSelector: Box[String] + def css: Box[CssSelector] + + override def toString(): String = "CssBind("+stringSelector+", "+ + css+")" + + def apply(in: NodeSeq): NodeSeq = css match { + case Full(c) => selectorMap(in) + case _ => Helpers.errorDiv( +
    + Syntax error in CSS selector definition: {stringSelector openOr "N/A"}. + The selector will not be applied. +
    ) openOr NodeSeq.Empty + } + + /** + * Is this CssBind a SelectThis bind? + */ + private[util] def selectThis_? : Boolean = css match { + case Full(sel) => { + sel.subNodes match { + case Full(SelectThisNode(_)) => true + case _ => false + } + } + + case _ => false + } + + /** + * Is this an Attribute mutating node? + */ + private[util] def attrSel_? : Boolean = css match { + case Full(sel) => { + sel.subNodes match { + case Full(x: AttributeRule) => true + case _ => false + } + } + + case _ => false + } + + private[util] def selectThisChildren_? : Boolean = css match { + case Full(sel) => { + sel.subNodes match { + case Full(SelectThisNode(children)) => children + case _ => false + } + } + + case _ => false + } + + private lazy val selectorMap: SelectorMap = new SelectorMap(List(this)) + + def calculate(in: NodeSeq): Seq[NodeSeq] +} + +/** + * An abstract implementation of CssBind. You can instantiate + * this class and create a custom calculate method + */ +abstract class CssBindImpl(val stringSelector: Box[String], val css: Box[CssSelector]) extends CssBind { + def calculate(in: NodeSeq): Seq[NodeSeq] +} + +/** + * Bridge from Java-land to Scala + */ + +final class CssJBridge { + /** + * promote a String to a ToCssBindPromotor + */ + private implicit def strToCssBindPromoter(str: String): ToCssBindPromoter = + new ToCssBindPromoter(Full(str), CssSelectorParser.parse(str)) + + def sel(selector: String, value: String): CssSel = selector #> value + def sel(selector: String, value: NodeSeq): CssSel = selector #> value + def sel(selector: String, value: NodeSeq => NodeSeq): CssSel = selector #> value + def sel(selector: String, value: Bindable): CssSel = selector #> value + + /** + * Inserts a String constant according to the CssSelector rules + */ + def sel(selector: String, str: StringPromotable): CssSel = (selector #> str.toString) + + /** + * Inserts a String constant according to the CssSelector rules + */ + def sel(selector: String, str: IterableConst): CssSel = (selector #> str) + + /** + * Inserts a String constant according to the CssSelector rules + */ + def sel(selector: String, str: IterableFunc): CssSel = (selector #> str) + +} + +trait ComputeTransformRules[-T] { + def computeTransform(it: => T, ns: NodeSeq): Seq[NodeSeq] +} + +object ComputeTransformRules { + implicit def stringTransform: ComputeTransformRules[String] = new ComputeTransformRules[String] { + def computeTransform(str: => String, ns: NodeSeq): Seq[NodeSeq] = { + val s = str + List(if (null eq s) NodeSeq.Empty else Text(s)) + } + } + + implicit def bindableTransform: ComputeTransformRules[Bindable] = new ComputeTransformRules[Bindable] { + def computeTransform(str: => Bindable, ns: NodeSeq): Seq[NodeSeq] = List(str.asHtml) + } + + implicit def jsCmdTransform: ComputeTransformRules[ToJsCmd] = new ComputeTransformRules[ToJsCmd] { + def computeTransform(str: => ToJsCmd, ns: NodeSeq): Seq[NodeSeq] = List(Text(str.toJsCmd)) + } + + + + implicit def jsCmdPairTransform: ComputeTransformRules[(_, ToJsCmd)] = new ComputeTransformRules[(_, ToJsCmd)] { + def computeTransform(str: => (_, ToJsCmd), ns: NodeSeq): Seq[NodeSeq] = List(Text(str._2.toJsCmd)) + } + + implicit def intTransform: ComputeTransformRules[Int] = new ComputeTransformRules[Int] { + def computeTransform(str: => Int, ns: NodeSeq): Seq[NodeSeq] = List(Text(str.toString)) + } + + implicit def stringPromoteTransform: ComputeTransformRules[StringPromotable] = new ComputeTransformRules[StringPromotable] { + def computeTransform(str: => StringPromotable, ns: NodeSeq): Seq[NodeSeq] = List(Text(str.toString)) + } + + implicit def symbolTransform: ComputeTransformRules[Symbol] = new ComputeTransformRules[Symbol] { + def computeTransform(str: => Symbol, ns: NodeSeq): Seq[NodeSeq] = List(Text(str.name)) + } + + implicit def longTransform: ComputeTransformRules[Long] = new ComputeTransformRules[Long] { + def computeTransform(str: => Long, ns: NodeSeq): Seq[NodeSeq] = List(Text(str.toString)) + } + + implicit def boolTransform: ComputeTransformRules[Boolean] = new ComputeTransformRules[Boolean] { + def computeTransform(str: => Boolean, ns: NodeSeq): Seq[NodeSeq] = List(Text(str.toString)) + } + + + + implicit def nodeSeqTransform: ComputeTransformRules[NodeSeq] = new ComputeTransformRules[NodeSeq] { + def computeTransform(param: => NodeSeq, ns: NodeSeq): Seq[NodeSeq] = List(param) + } + + implicit def nodeSeqFuncTransform: ComputeTransformRules[NodeSeq => NodeSeq] = new ComputeTransformRules[NodeSeq => NodeSeq] { + def computeTransform(func: => NodeSeq => NodeSeq, ns: NodeSeq): Seq[NodeSeq] = List(func(ns)) + } + + implicit def nodeSeqSeqFuncTransform: ComputeTransformRules[NodeSeq => Seq[Node]] = new ComputeTransformRules[NodeSeq => Seq[Node]] { + def computeTransform(func: => NodeSeq => Seq[Node], ns: NodeSeq): Seq[NodeSeq] = Helpers.ensureUniqueId(List(func(ns))) + } + + implicit def nodeFuncTransform: ComputeTransformRules[NodeSeq => Node] = new ComputeTransformRules[NodeSeq => Node] { + def computeTransform(func: => NodeSeq => Node, ns: NodeSeq): Seq[NodeSeq] = List(func(ns)) + } + + implicit def iterableNodeTransform[T[_]](implicit f: T[NodeSeq] => Iterable[NodeSeq]): ComputeTransformRules[T[NodeSeq]] = + new ComputeTransformRules[T[NodeSeq]] { + def computeTransform(info: => T[NodeSeq], ns: NodeSeq): Seq[NodeSeq] = Helpers.ensureUniqueId(f(info).toSeq) + } + + implicit def iterableElemTransform[T[_]](implicit f: T[Elem] => Iterable[Elem]): ComputeTransformRules[T[Elem]] = + new ComputeTransformRules[T[Elem]] { + def computeTransform(info: => T[Elem], ns: NodeSeq): Seq[NodeSeq] = Helpers.ensureUniqueId(f(info).toSeq.map(a => a:NodeSeq)) + } + + + implicit def iterableStringTransform[T[_]](implicit f: T[String] => Iterable[String]): ComputeTransformRules[T[String]] = + new ComputeTransformRules[T[String]] { + def computeTransform(info: => T[String], ns: NodeSeq): Seq[NodeSeq] = f(info).toSeq.map(a => Text(a)) + } + + implicit def iterableBindableTransform[T[_]](implicit f: T[Bindable] => Iterable[Bindable]): ComputeTransformRules[T[Bindable]] = + new ComputeTransformRules[T[Bindable]] { + def computeTransform(info: => T[Bindable], ns: NodeSeq): Seq[NodeSeq] = Helpers.ensureUniqueId(f(info).toSeq.map(_.asHtml)) + } + + + implicit def iterableStringPromotableTransform[T[_], PM](implicit f: T[PM] => Iterable[PM], + prom: PM => StringPromotable ): + ComputeTransformRules[T[PM]] = + new ComputeTransformRules[T[PM]] { + def computeTransform(info: => T[PM], ns: NodeSeq): Seq[NodeSeq] = f(info).toSeq.map(a => Text(prom(a).toString)) + } + + implicit def iterableNodeFuncTransform[T[_], F <: NodeSeq => NodeSeq](implicit f: T[F] => Iterable[F]): ComputeTransformRules[T[F]] = + new ComputeTransformRules[T[F]] { + def computeTransform(info: => T[F], ns: NodeSeq): Seq[NodeSeq] = Helpers.ensureUniqueId(f(info).toSeq.map(_.apply(ns))) + } + + implicit def funcIterableTransform[T[_], F <: NodeSeq](implicit f: T[F] => Iterable[F]): ComputeTransformRules[ NodeSeq => T[F]] = + new ComputeTransformRules[ NodeSeq => T[F]] { + def computeTransform(info: => NodeSeq => T[F], ns: NodeSeq): Seq[NodeSeq] = Helpers.ensureUniqueId(f(info(ns)).toSeq) + } + + implicit def stringFuncTransform: ComputeTransformRules[NodeSeq => String] = + new ComputeTransformRules[ NodeSeq => String] { + def computeTransform(info: => NodeSeq => String, ns: NodeSeq): Seq[NodeSeq] = List(Text(info(ns))) + } + + implicit def stringIterFuncTransform[T[_]](implicit f: T[String] => Iterable[String]): ComputeTransformRules[NodeSeq => T[String]] = + new ComputeTransformRules[NodeSeq => T[String]] { + def computeTransform(info: => NodeSeq => T[String], ns: NodeSeq): Seq[NodeSeq] = f(info(ns)).toSeq.map(Text(_)) + } + + + implicit def iterableConstFuncTransform: ComputeTransformRules[IterableConst] = + new ComputeTransformRules[IterableConst] { + def computeTransform(info: => IterableConst, ns: NodeSeq): Seq[NodeSeq] = info.constList(ns) + } +} + + +/** + * An intermediate class used to promote a String or a CssSelector to + * something that can be associated with a value to apply to the selector + */ +final case class ToCssBindPromoter(stringSelector: Box[String], css: Box[CssSelector]) { + + /** + * Transform a DOM (NodeSeq) based on rules + * + * @param it the thing to use in the replacement rules + * @param computer the implicit parameter that transforms T into something that will make the correct changes + * @tparam T the type of it + * @return the function that will transform an incoming DOM based on the transform rules + */ + def #>[T](it: => T)(implicit computer: ComputeTransformRules[T]): CssSel = css match { + case Full(EnclosedSelector(a, b)) => null + (ToCssBindPromoter(stringSelector, Full(a))).#>(nsFunc(ns =>{ + println("nesting "+ns+" for "+b) + ToCssBindPromoter(stringSelector, Full(b)).#>(it)(computer)(ns)})) // (ComputeTransformRules.nodeSeqFuncTransform) + case _ => + new CssBindImpl(stringSelector, css) { + def calculate(in: NodeSeq): Seq[NodeSeq] = computer.computeTransform(it, in) + } + } + + /** + * Transform a DOM (NodeSeq) based on rules + * + * @param it the thing to use in the replacement rules + * @param computer the implicit parameter that transforms T into something that will make the correct changes + * @tparam T the type of it + * @return the function that will transform an incoming DOM based on the transform rules + */ + def replaceWith[T](it: => T)(implicit computer: ComputeTransformRules[T]): CssSel = this.#>(it)(computer) +} \ No newline at end of file diff --git a/core/util/src/main/scala/net/liftweb/util/CssSelector.scala b/core/util/src/main/scala/net/liftweb/util/CssSelector.scala index befd6eb3ac..91e1d3bb1e 100644 --- a/core/util/src/main/scala/net/liftweb/util/CssSelector.scala +++ b/core/util/src/main/scala/net/liftweb/util/CssSelector.scala @@ -17,10 +17,10 @@ package net.liftweb package util -import scala.util.parsing.combinator.{Parsers, ImplicitConversions} + +import scala.util.parsing.combinator.{PackratParsers, Parsers, ImplicitConversions} import scala.xml.NodeSeq import net.liftweb.common._ -import util.EnclosedSelector sealed trait CssSelector { def subNodes: Box[SubNode] @@ -81,13 +81,17 @@ final case class PrependKidsSubNode() extends SubNode with WithKids { } final case object DontMergeAttributes extends SubNode { + } +final case class SurroundKids() extends SubNode with WithKids { + def transform(original: NodeSeq, newNs: NodeSeq): NodeSeq = newNs ++ original // FIXME } final case class AppendKidsSubNode() extends SubNode with WithKids { def transform(original: NodeSeq, newNs: NodeSeq): NodeSeq = original ++ newNs } + sealed trait AttributeRule final case class AttrSubNode(attr: String) extends SubNode with AttributeRule @@ -99,7 +103,7 @@ final case class SelectThisNode(kids: Boolean) extends SubNode /** * Parse a subset of CSS into the appropriate selector objects */ -object CssSelectorParser extends Parsers with ImplicitConversions { +object CssSelectorParser extends PackratParsers with ImplicitConversions { private val cache = new LRUMap[String, CssSelector](25000) /** @@ -150,16 +154,18 @@ object CssSelectorParser extends Parsers with ImplicitConversions { colonMatch) } - private def fixAll(all: List[CssSelector], sn: Option[SubNode]): CssSelector = (all, sn) match { - case (r :: Nil, None) => r - case (r :: Nil, Some(sn)) => r.withSubnode(sn) - case (lst, None) => lst.reduceRight((b, a) => EnclosedSelector(a, b)) - case (lst, Some(sn)) => (lst.dropRight(1) ::: lst.takeRight(1).map(_.withSubnode(sn))).reduceRight((b, a) => EnclosedSelector(a, b)) + private def fixAll(all: List[CssSelector], sn: Option[SubNode]): CssSelector = { + (all, sn) match { + case (r :: Nil, None) => r + case (r :: Nil, Some(sn)) => r.withSubnode(sn) + case (lst, None) => lst.reduceRight((b, a) => EnclosedSelector(b, a)) + case (lst, Some(sn)) => (lst.dropRight(1) ::: lst.takeRight(1).map(_.withSubnode(sn))).reduceRight((b, a) => EnclosedSelector(b, a)) + } } private lazy val topParser: Parser[CssSelector] = - phrase(rep1(_idMatch | _nameMatch | _classMatch | _attrMatch | _elemMatch | - _colonMatch | _starMatch) ~ opt(subNode)) ^^ { + phrase(rep1((_idMatch | _nameMatch | _classMatch | _attrMatch | _elemMatch | + _colonMatch | _starMatch) <~ (rep1(' ') | 26.toChar)) ~ opt(subNode)) ^^ { case all ~ None if all.takeRight(1).head == StarSelector(Empty) => fixAll(all.dropRight(1), Some(KidsSubNode())) case all ~ sn => fixAll(all, sn) @@ -261,7 +267,7 @@ object CssSelectorParser extends Parsers with ImplicitConversions { private lazy val number: Parser[Char] = elem("number", isNumber) - private lazy val subNode: Parser[SubNode] = rep1(' ') ~> + private lazy val subNode: Parser[SubNode] = rep(' ') ~> ((opt('*') ~ '[' ~> attrName <~ '+' ~ ']' ^^ { name => AttrAppendSubNode(name) }) | @@ -270,7 +276,9 @@ object CssSelectorParser extends Parsers with ImplicitConversions { }) | (opt('*') ~ '[' ~> attrName <~ ']' ^^ { name => AttrSubNode(name) }) | + ('!' ~ '!' ^^ (a => DontMergeAttributes)) | + ('<' ~ '*' ~ '>') ^^ (a => SurroundKids()) | ('-' ~ '*' ^^ (a => PrependKidsSubNode())) | ('>' ~ '*' ^^ (a => PrependKidsSubNode())) | ('*' ~ '+' ^^ (a => AppendKidsSubNode())) | diff --git a/core/util/src/main/scala/net/liftweb/util/IterableConst.scala b/core/util/src/main/scala/net/liftweb/util/IterableConst.scala new file mode 100644 index 0000000000..681802280c --- /dev/null +++ b/core/util/src/main/scala/net/liftweb/util/IterableConst.scala @@ -0,0 +1,143 @@ +package net.liftweb +package util + +import common._ +import xml._ +import java.util.{List => JavaList} +// import scala.collection.JavaConverters._ + +/** + * A trait that has some helpful implicit conversions from + * Iterable[NodeSeq], Seq[String], Box[String], and Option[String] + */ +trait IterableConst { + def constList(nodeSeq: NodeSeq): Seq[NodeSeq] +} + +import scala.collection.JavaConversions._ + +/** + * The implementation for a NodeSeq Iterable Const + */ +final case class NodeSeqIterableConst(it: Iterable[NodeSeq]) extends IterableConst { + def this(it: JavaList[NodeSeq]) = this(it: Iterable[NodeSeq]) + + def constList(nodeSeq: NodeSeq): Seq[NodeSeq] = it.toSeq +} + +/** + * The implementation for a NodeSeq => NodeSeq Iterable Const + */ +final case class NodeSeqFuncIterableConst(it: Iterable[NodeSeq => NodeSeq]) extends IterableConst { + def this(it: JavaList[NodeSeq => NodeSeq]) = this(it: Iterable[NodeSeq => NodeSeq]) + + def constList(nodeSeq: NodeSeq): Seq[NodeSeq] = Helpers.ensureUniqueId(it.map(_(nodeSeq)).toSeq) +} + +/** + * The implementation for a Box[NodeSeq => Node] Iterable Const + */ +final case class BoxNodeSeqFuncIterableConst(it: Box[NodeSeq => NodeSeq]) extends IterableConst { + + def constList(nodeSeq: NodeSeq): Seq[NodeSeq] = it.toList.map(_(nodeSeq)) +} + +/** + * The implementation for a Option[NodeSeq => Node] Iterable Const + */ +final case class OptionNodeSeqFuncIterableConst(it: Option[NodeSeq => NodeSeq]) extends IterableConst { + + def constList(nodeSeq: NodeSeq): Seq[NodeSeq] = it.toList.map(_(nodeSeq)) +} + +/** + * Sequence of String iterable const + */ +final case class SeqStringIterableConst(it: Iterable[String]) extends IterableConst { + def this(it: JavaList[String]) = this(it: Iterable[String]) + + def constList(nodeSeq: NodeSeq): Seq[NodeSeq] = it.map(a => Text(a)).toSeq +} + +/** + * Sequence of Bindable iterable const + */ +final case class SeqBindableIterableConst(it: Iterable[Bindable]) extends IterableConst { + def this(it: JavaList[Bindable]) = this(it: Iterable[Bindable]) + + def constList(nodeSeq: NodeSeq): Seq[NodeSeq] = it.map(_.asHtml).toSeq +} + +/** + * The companion object that does the helpful promotion of common + * collection types into an IterableConst, + * e.g. Iterable[NodeSeq], Seq[String], Box[String], and Option[String] + */ +object IterableConst { + /** + * Converts anything that can be converted into an Iterable[NodeSeq] + * into an IterableConst. This includes Seq[NodeSeq] + */ + implicit def itNodeSeq(it: Iterable[NodeSeq]): IterableConst = + NodeSeqIterableConst(it) + + /** + * Converts anything that can be converted into an Box[NodeSeq] + */ + implicit def boxNodeSeq(it: Box[NodeSeq]): IterableConst = + NodeSeqIterableConst(it.toList) + + /** + * Converts anything that can be converted into an Box[NodeSeq] + */ + implicit def optionNodeSeq(it: Option[NodeSeq]): IterableConst = + NodeSeqIterableConst(it.toList) + + /** + * Converts anything that can be converted into an Iterable[NodeSeq] + * into an IterableConst. This includes Seq[NodeSeq], Option[NodeSeq], + * and Box[NodeSeq] + */ + implicit def itNodeSeq(it: JavaList[NodeSeq]): IterableConst = + new NodeSeqIterableConst(it) + + implicit def itNodeSeqFunc(it: Iterable[NodeSeq => NodeSeq]): IterableConst = + NodeSeqFuncIterableConst(it) + + implicit def itNodeSeqFunc(it: JavaList[NodeSeq => NodeSeq]): IterableConst = + new NodeSeqFuncIterableConst(it) + + implicit def boxNodeSeqFunc(it: Box[NodeSeq => NodeSeq]): IterableConst = + BoxNodeSeqFuncIterableConst(it) + + implicit def optionNodeSeqFunc(it: Option[NodeSeq => NodeSeq]): IterableConst = + OptionNodeSeqFuncIterableConst(it) + + implicit def itStringPromotable(it: Iterable[String]): IterableConst = + SeqStringIterableConst(it) + + implicit def javaListStringPromotable(it: JavaList[String]): IterableConst = + new SeqStringIterableConst(it) + + implicit def boxString(it: Box[String]): IterableConst = + SeqStringIterableConst(it.toList) + + implicit def optionString(it: Option[String]): IterableConst = + SeqStringIterableConst(it.toList) + + implicit def itBindable(it: Iterable[Bindable]): IterableConst = + SeqBindableIterableConst(it) + + implicit def itBindable(it: JavaList[Bindable]): IterableConst = + new SeqBindableIterableConst(it) + + + implicit def boxBindablePromotable(it: Box[Bindable]): IterableConst = + SeqBindableIterableConst(it.toList) + + implicit def optionBindablePromotable(it: Option[Bindable]): IterableConst = + SeqBindableIterableConst(it.toList) + + implicit def optionStringPromotable[T](o: Option[T])(implicit view:T=>StringPromotable) = optionString(o.map(view(_).toString)) +} + diff --git a/core/util/src/main/scala/net/liftweb/util/IterableFunc.scala b/core/util/src/main/scala/net/liftweb/util/IterableFunc.scala new file mode 100644 index 0000000000..9f5b4888aa --- /dev/null +++ b/core/util/src/main/scala/net/liftweb/util/IterableFunc.scala @@ -0,0 +1,47 @@ +package net.liftweb +package util + +import common._ +import xml.{Text, NodeSeq} + +sealed trait IterableFunc extends Function1[NodeSeq, Seq[NodeSeq]] { + def apply(ns: NodeSeq): Seq[NodeSeq] +} + +object IterableFunc { + implicit def itNodeSeq[C <% Iterable[NodeSeq]](it: NodeSeq => C): IterableFunc = + new IterableFunc { + def apply(in: NodeSeq): Seq[NodeSeq] = it(in).toSeq + } + + implicit def itNodeSeqPromotable(it: NodeSeq => NodeSeq): IterableFunc = + new IterableFunc { + def apply(in: NodeSeq): Seq[NodeSeq] = List(it(in)) + } + + + implicit def itStringFuncPromotable(it: NodeSeq => String): IterableFunc = + new IterableFunc { + def apply(in: NodeSeq): Seq[NodeSeq] = it(in) match { + case null => List(NodeSeq.Empty) + case str => List(Text(str))} + } + + + implicit def itStringPromotable(it: NodeSeq => Seq[String]): IterableFunc = + new IterableFunc { + def apply(in: NodeSeq): Seq[NodeSeq] = it(in).filter(_ ne null).map(a => Text(a)) + } + + implicit def boxStringPromotable(it: NodeSeq => Box[String]): IterableFunc = + new IterableFunc { + def apply(in: NodeSeq): Seq[NodeSeq] = it(in).filter(_ ne null).toList.map(a => Text(a)) + } + + + implicit def optionStringPromotable(it: NodeSeq => Option[String]): IterableFunc = + new IterableFunc { + def apply(in: NodeSeq): Seq[NodeSeq] = it(in).filter(_ ne null).toList.map(a => Text(a)) + } +} + diff --git a/core/util/src/main/scala/net/liftweb/util/StringPromotable.scala b/core/util/src/main/scala/net/liftweb/util/StringPromotable.scala new file mode 100644 index 0000000000..64fa57402e --- /dev/null +++ b/core/util/src/main/scala/net/liftweb/util/StringPromotable.scala @@ -0,0 +1,41 @@ +package net.liftweb.util + +/** + * This trait marks something that can be promoted into a String. + * The companion object has helpful conversions from Int, + * Symbol, Long, and Boolean + */ +trait StringPromotable + +object StringPromotable { + implicit def jsCmdToStrPromo(in: ToJsCmd): StringPromotable = + new StringPromotable { + override val toString = in.toJsCmd + } + + implicit def jsCmdToStrPromo(in: (_, ToJsCmd)): StringPromotable = + new StringPromotable { + override val toString = in._2.toJsCmd + } + + + implicit def intToStrPromo(in: Int): StringPromotable = + new StringPromotable { + override val toString = in.toString + } + + implicit def symbolToStrPromo(in: Symbol): StringPromotable = + new StringPromotable { + override val toString = in.name + } + + implicit def longToStrPromo(in: Long): StringPromotable = + new StringPromotable { + override val toString = in.toString + } + + implicit def booleanToStrPromo(in: Boolean): StringPromotable = + new StringPromotable { + override val toString = in.toString + } +} diff --git a/core/util/src/main/scala/net/liftweb/util/package.scala b/core/util/src/main/scala/net/liftweb/util/package.scala index 9d52552589..10d796fdc9 100644 --- a/core/util/src/main/scala/net/liftweb/util/package.scala +++ b/core/util/src/main/scala/net/liftweb/util/package.scala @@ -14,7 +14,10 @@ * limitations under the License. */ -package net.liftweb +package net.liftweb + +import common.{Empty, Full} +import xml.NodeSeq /** @@ -28,4 +31,35 @@ package object util { */ @deprecated("Use Schedule", "2.3") val ActorPing = Schedule + + /** + * promote a String to a ToCssBindPromotor + */ + implicit def strToCssBindPromoter(str: String): ToCssBindPromoter = + new ToCssBindPromoter(Full(str), CssSelectorParser.parse(str)) + + /** + * promote a String to a ToCssBindPromotor + */ + implicit def cssSelectorToCssBindPromoter(sel: CssSelector): ToCssBindPromoter = + new ToCssBindPromoter(Empty, Full(sel)) + + /** + * Wrap a function and make sure it's a NodeSeq => NodeSeq. Much easier + * than explicitly casting the first parameter + * + * @param f the function + * @return a NodeSeq => NodeSeq + */ + def nsFunc(f: NodeSeq => NodeSeq): NodeSeq => NodeSeq = f + + /** + * Promote to an IterableConst when implicits won't do it for you + * + * @param ic the thing that can be promoted to an IterableConst + * @param f the implicit function that takes T and makes it an IterableConst + * @tparam T the type of the parameter + * @return an IterableConst + */ + def itConst[T](ic: T)(implicit f: T => IterableConst): IterableConst = f(ic) } diff --git a/core/util/src/test/scala/net/liftweb/util/BindHelpersSpec.scala b/core/util/src/test/scala/net/liftweb/util/BindHelpersSpec.scala index 96eb9c34d6..58e3241ecd 100644 --- a/core/util/src/test/scala/net/liftweb/util/BindHelpersSpec.scala +++ b/core/util/src/test/scala/net/liftweb/util/BindHelpersSpec.scala @@ -355,7 +355,7 @@ object CssBindHelpersSpec extends Specification { } "substitute a String by id" in { - ("#foo" #> "hello")() must ==/ (hello) + ("#foo" #> "hello").apply() must ==/ (hello) } @@ -434,11 +434,27 @@ object CssBindHelpersSpec extends Specification { } "substitute a String by id" in { - ("#foo" replaceWith "hello")() must ==/ (hello) + ("#foo" replaceWith "hello").apply() must ==/ (hello) } + "substitute a String by nested class" in { + ("div .foo" #> "hello").apply(
    ) must ==/ (
    hello
    ) + } + + "substitute a String by deep nested class" in { + ("#baz div .foo" #> "hello").apply( +
    ) must ==/ (
    hello
    ) + } + + "insert a String by deep nested class" in { + ("#baz div .foo *" #> "hello").apply( +
    ) must ==/ (
    hello
    ) + } + + + "Select a node" in { - ("#foo ^^" #> "hello")(
    ) must ==/ () + ("#foo ^^" #> "hello").apply(
    ) must ==/ () } "Another nested select" in { @@ -564,19 +580,19 @@ object CssBindHelpersSpec extends Specification { "option transform on *" in { val opt: Option[String] = None - val res = ("* *" #> opt.map(ignore => "Dog"))(cat) + val res = ("* *" #> opt.map(ignore => "Dog")).apply(cat) res.head must_== } "append attribute to a class with spaces" in { val stuff = List("a", "b") - val res = ("* [class+]" #> stuff)(cat) + val res = ("* [class+]" #> stuff).apply(cat) (res \ "@class").text must_== "q a b" } "append attribute to an href" in { val stuff = List("&a=b", "&b=d") - val res = ("* [href+]" #> stuff)(cat) + val res = ("* [href+]" #> stuff).apply(cat) (res \ "@href").text must_== "q?z=r&a=b&b=d" } @@ -629,35 +645,35 @@ object CssBindHelpersSpec extends Specification { "option transform on *" in { val opt: Option[Int] = Full(44) - val res = ("* *" #> opt.map(ignore => "Dog"))(cat) + val res = ("* *" #> opt.map(ignore => "Dog")).apply(cat) res must ==/ (Dog) } "option transform on *" in { val opt: Box[String] = Empty - val res = ("* *" #> opt.map(ignore => "Dog"))(cat) + val res = ("* *" #> opt.map(ignore => "Dog")).apply(cat) res.head must_== } "option transform on *" in { val opt: Box[Int] = Some(44) - val res = ("* *" #> opt.map(ignore => "Dog"))(cat) + val res = ("* *" #> opt.map(ignore => "Dog")).apply(cat) res must ==/ (Dog) } "transform on *" in { - val res = ("* *" #> "Dog")(cat) + val res = ("* *" #> "Dog").apply(cat) res must ==/ (Dog) } "transform child content on *+" in { - val res = ("* *+" #> "moose")(
    I like ) + val res = ("* *+" #> "moose").apply(I like ) res.text must_== "I like moose" } "transform child content on -*" in { - val res = ("* -*" #> "moose")( I like) + val res = ("* -*" #> "moose").apply( I like) res.text must_== "moose I like" } diff --git a/core/util/src/test/scala/net/liftweb/util/CssSelectorSpec.scala b/core/util/src/test/scala/net/liftweb/util/CssSelectorSpec.scala index 8567b8245b..5887895627 100644 --- a/core/util/src/test/scala/net/liftweb/util/CssSelectorSpec.scala +++ b/core/util/src/test/scala/net/liftweb/util/CssSelectorSpec.scala @@ -20,8 +20,6 @@ package util import org.specs2.mutable.Specification import common._ -import BindHelpers._ -import util.{ElemSelector, ClassSelector} /** @@ -40,7 +38,7 @@ object CssSelectorSpec extends Specification { } "a selector with cruft at the end must fail" in { - CssSelectorParser.parse("#foo I like yaks").isDefined must_== false + CssSelectorParser.parse("#foo I li**ke yaks").isDefined must_== false } ":yak must not parse" in { @@ -151,7 +149,12 @@ object CssSelectorSpec extends Specification { "select name/val pair" in { CssSelectorParser.parse("@dog -*") must_== - Full(NameSelector("dog", Full(PrependKidsSubNode()))) + Full(NameSelector("dog", Full(PrependKidsSubNode()))) + } + + "select name/val pair surround" in { + CssSelectorParser.parse("@dog <*>") must_== + Full(NameSelector("dog", Full(SurroundKids()))) } "select name/val pair" in { @@ -205,6 +208,16 @@ object CssSelectorSpec extends Specification { EnclosedSelector(ElemSelector("div", Empty), ClassSelector("foo", Full(AttrSubNode("woof")))) } + "select multiple depth with star" in { + CssSelectorParser.parse("div .foo * ").open_! must_== + EnclosedSelector(ElemSelector("div", Empty), ClassSelector("foo", Full(KidsSubNode()))) + } + + "select multiple super depth with star" in { + CssSelectorParser.parse("span div .foo * ").open_! must_== + EnclosedSelector(ElemSelector("span", Empty), EnclosedSelector(ElemSelector("div", Empty), ClassSelector("foo", Full(KidsSubNode())))) + } + } From 39c4081981d254d957916813f0f124068bfdec68 Mon Sep 17 00:00:00 2001 From: David Pollak Date: Thu, 28 Jun 2012 09:53:07 -0700 Subject: [PATCH 0160/1949] WIP for CSS Selectors --- build.sbt | 1 + core/util/src/main/scala/net/liftweb/util/CssSel.scala | 4 ++-- project/Build.scala | 2 +- project/Dependencies.scala | 1 + 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/build.sbt b/build.sbt index 09f73571c5..9b82164410 100644 --- a/build.sbt +++ b/build.sbt @@ -14,6 +14,7 @@ organizationName in ThisBuild := "WorldWide Conferencing, LLC" crossScalaVersions in ThisBuild := Seq("2.9.2", "2.9.1-1", "2.9.1", "2.9.0-1", "2.9.0") + libraryDependencies in ThisBuild ++= Seq(specs2, scalacheck) // Settings for Sonatype compliance diff --git a/core/util/src/main/scala/net/liftweb/util/CssSel.scala b/core/util/src/main/scala/net/liftweb/util/CssSel.scala index de821cce8a..9affe4b238 100644 --- a/core/util/src/main/scala/net/liftweb/util/CssSel.scala +++ b/core/util/src/main/scala/net/liftweb/util/CssSel.scala @@ -37,9 +37,9 @@ trait CssSel extends Function1[NodeSeq, NodeSeq] { /** * promote a String to a ToCssBindPromotor */ + /* private implicit def strToCssBindPromoter(str: String): ToCssBindPromoter = - new ToCssBindPromoter(Full(str), CssSelectorParser.parse(str)) - + new ToCssBindPromoter(Full(str), CssSelectorParser.parse(str)) */ } /** diff --git a/project/Build.scala b/project/Build.scala index 190b6e19fa..aee0a723f9 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -70,7 +70,7 @@ object BuildDef extends Build { .dependsOn(actor, json) .settings(description := "Utilities Library", parallelExecution in Test := false, - libraryDependencies ++= Seq(joda_time, commons_codec, javamail, log4j, htmlparser)) + libraryDependencies <++= scalaVersion {sv => Seq(scala_compiler(sv), joda_time, commons_codec, javamail, log4j, htmlparser)}) // Web Projects diff --git a/project/Dependencies.scala b/project/Dependencies.scala index fad08470d2..6fb5be5f2d 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -44,6 +44,7 @@ object Dependencies { lazy val paranamer = "com.thoughtworks.paranamer" % "paranamer" % "2.4.1" lazy val scalajpa = "org.scala-libs" % "scalajpa" % "1.4" cross CVMappingAll lazy val scalap: ModuleMap = "org.scala-lang" % "scalap" % _ + lazy val scala_compiler: ModuleMap = "org.scala-lang" % "scala-compiler" % _ lazy val scalaz_core: ModuleMap = sv => scalazGroup(sv) % "scalaz-core" % scalazVersion(sv) cross CVMappingAll lazy val slf4j_api = "org.slf4j" % "slf4j-api" % slf4jVersion lazy val squeryl = "org.squeryl" % "squeryl" % "0.9.5-1" cross crossMapped("2.9.1-1" -> "2.9.1", "2.8.2" -> "2.8.1") From aa74d13cc1ad04d15fef064019b24c44ae43d12b Mon Sep 17 00:00:00 2001 From: David Pollak Date: Thu, 28 Jun 2012 10:25:03 -0700 Subject: [PATCH 0161/1949] Misc fixes --- core/util/src/main/scala/net/liftweb/util/CssSel.scala | 1 - .../src/main/scala/net/liftweb/http/StatefulSnippet.scala | 2 +- .../src/main/scala/net/liftweb/http/js/JsCommands.scala | 4 ++-- web/webkit/src/test/scala/net/liftweb/http/SHtmlSpec.scala | 1 + 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/CssSel.scala b/core/util/src/main/scala/net/liftweb/util/CssSel.scala index 9affe4b238..e9c0483fac 100644 --- a/core/util/src/main/scala/net/liftweb/util/CssSel.scala +++ b/core/util/src/main/scala/net/liftweb/util/CssSel.scala @@ -845,7 +845,6 @@ final case class ToCssBindPromoter(stringSelector: Box[String], css: Box[CssSele def #>[T](it: => T)(implicit computer: ComputeTransformRules[T]): CssSel = css match { case Full(EnclosedSelector(a, b)) => null (ToCssBindPromoter(stringSelector, Full(a))).#>(nsFunc(ns =>{ - println("nesting "+ns+" for "+b) ToCssBindPromoter(stringSelector, Full(b)).#>(it)(computer)(ns)})) // (ComputeTransformRules.nodeSeqFuncTransform) case _ => new CssBindImpl(stringSelector, css) { diff --git a/web/webkit/src/main/scala/net/liftweb/http/StatefulSnippet.scala b/web/webkit/src/main/scala/net/liftweb/http/StatefulSnippet.scala index 2cc3be42de..6ecd2a2821 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/StatefulSnippet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/StatefulSnippet.scala @@ -113,7 +113,7 @@ trait StatefulSnippet extends DispatchSnippet { if (formElem.isDefined) { import util.Helpers._ - ("form *" #> ((kids: NodeSeq) => toMerge ++ kids))(res) + ("form *" #> ((kids: NodeSeq) => toMerge ++ kids)).apply(res) } else if (isForm) { toMerge ++ res } else { diff --git a/web/webkit/src/main/scala/net/liftweb/http/js/JsCommands.scala b/web/webkit/src/main/scala/net/liftweb/http/js/JsCommands.scala index 6ec29f527c..58fffd0929 100755 --- a/web/webkit/src/main/scala/net/liftweb/http/js/JsCommands.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/js/JsCommands.scala @@ -613,7 +613,7 @@ trait HtmlFixer { import scala.collection.mutable.ListBuffer val lb = new ListBuffer[JsCmd] - val revised = ("script" #> ((ns: NodeSeq) => { + val revised = ("script" #> nsFunc(ns => { ns match { case FindScript(e) => { lb += JE.JsRaw(ns.text).cmd @@ -621,7 +621,7 @@ trait HtmlFixer { } case x => x } - }))(xhtml) + })).apply(xhtml) S.htmlProperties.htmlWriter(Group(revised), w) diff --git a/web/webkit/src/test/scala/net/liftweb/http/SHtmlSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/SHtmlSpec.scala index 1ff04b3b67..3bb74426d8 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/SHtmlSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/SHtmlSpec.scala @@ -26,6 +26,7 @@ object SHtmlSpec extends Specification { "NamedCometPerTabSpec Specification".title val html1= + val inputField1= testS("/")( ("#number" #> SHtml.number(0, println(_), 0, 100))(html1) ) val inputField2= testS("/")( ("#number" #> SHtml.number(0, println(_: Double), 0, 100, 0.1))(html1) ) val inputField3= testS("/")( ("#number" #> SHtml.number(0, println(_: Double), 0, 100, 1))(html1) ) From e3b2e1064613c74eae333bcad6d9ef38becd5bdb Mon Sep 17 00:00:00 2001 From: David Pollak Date: Thu, 28 Jun 2012 10:50:40 -0700 Subject: [PATCH 0162/1949] Redid some implicits to balance in favor of converting things to a NodeSeq --- .../main/scala/net/liftweb/util/CssSel.scala | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/CssSel.scala b/core/util/src/main/scala/net/liftweb/util/CssSel.scala index e9c0483fac..7144d426c3 100644 --- a/core/util/src/main/scala/net/liftweb/util/CssSel.scala +++ b/core/util/src/main/scala/net/liftweb/util/CssSel.scala @@ -755,8 +755,8 @@ object ComputeTransformRules { - implicit def nodeSeqTransform: ComputeTransformRules[NodeSeq] = new ComputeTransformRules[NodeSeq] { - def computeTransform(param: => NodeSeq, ns: NodeSeq): Seq[NodeSeq] = List(param) + implicit def nodeSeqTransform[T](implicit f : T => NodeSeq): ComputeTransformRules[T] = new ComputeTransformRules[T] { + def computeTransform(param: => T, ns: NodeSeq): Seq[NodeSeq] = List(f(param)) } implicit def nodeSeqFuncTransform: ComputeTransformRules[NodeSeq => NodeSeq] = new ComputeTransformRules[NodeSeq => NodeSeq] { @@ -771,16 +771,20 @@ object ComputeTransformRules { def computeTransform(func: => NodeSeq => Node, ns: NodeSeq): Seq[NodeSeq] = List(func(ns)) } - implicit def iterableNodeTransform[T[_]](implicit f: T[NodeSeq] => Iterable[NodeSeq]): ComputeTransformRules[T[NodeSeq]] = - new ComputeTransformRules[T[NodeSeq]] { - def computeTransform(info: => T[NodeSeq], ns: NodeSeq): Seq[NodeSeq] = Helpers.ensureUniqueId(f(info).toSeq) + implicit def iterableNodeTransform[NST](implicit f2: NST => NodeSeq): ComputeTransformRules[Iterable[NST]] = + new ComputeTransformRules[Iterable[NST]] { + def computeTransform(info: => Iterable[NST], ns: NodeSeq): Seq[NodeSeq] = Helpers.ensureUniqueId(info.toSeq.map(f2)) } - implicit def iterableElemTransform[T[_]](implicit f: T[Elem] => Iterable[Elem]): ComputeTransformRules[T[Elem]] = - new ComputeTransformRules[T[Elem]] { - def computeTransform(info: => T[Elem], ns: NodeSeq): Seq[NodeSeq] = Helpers.ensureUniqueId(f(info).toSeq.map(a => a:NodeSeq)) + implicit def boxNodeTransform[NST](implicit f2: NST => NodeSeq): ComputeTransformRules[Box[NST]] = + new ComputeTransformRules[Box[NST]] { + def computeTransform(info: => Box[NST], ns: NodeSeq): Seq[NodeSeq] = info.toList.map(f2) } + implicit def optionNodeTransform[NST](implicit f2: NST => NodeSeq): ComputeTransformRules[Option[NST]] = + new ComputeTransformRules[Option[NST]] { + def computeTransform(info: => Option[NST], ns: NodeSeq): Seq[NodeSeq] = info.toList.map(f2) + } implicit def iterableStringTransform[T[_]](implicit f: T[String] => Iterable[String]): ComputeTransformRules[T[String]] = new ComputeTransformRules[T[String]] { From 0b6145eb6e681c0811e7bc651e19220a1f4189b4 Mon Sep 17 00:00:00 2001 From: David Pollak Date: Mon, 27 Aug 2012 15:13:43 -0700 Subject: [PATCH 0163/1949] Got java.lang.Number working for CSS Selector Transforms --- .../main/scala/net/liftweb/util/CssSel.scala | 15 ++++++++++ .../scala/net/liftweb/util/CssSelector.scala | 1 + .../net/liftweb/util/BindHelpersSpec.scala | 30 +++++++++++++++++++ .../net/liftweb/util/CssSelectorSpec.scala | 1 + 4 files changed, 47 insertions(+) diff --git a/core/util/src/main/scala/net/liftweb/util/CssSel.scala b/core/util/src/main/scala/net/liftweb/util/CssSel.scala index 7144d426c3..e3d6ab0fe2 100644 --- a/core/util/src/main/scala/net/liftweb/util/CssSel.scala +++ b/core/util/src/main/scala/net/liftweb/util/CssSel.scala @@ -723,6 +723,15 @@ object ComputeTransformRules { def computeTransform(str: => Bindable, ns: NodeSeq): Seq[NodeSeq] = List(str.asHtml) } + + implicit def numberTransform[T <: java.lang.Number]: ComputeTransformRules[T] = new ComputeTransformRules[java.lang.Number] { + def computeTransform(str: => java.lang.Number, ns: NodeSeq): Seq[NodeSeq] = { + val num = str + List(if (null eq num) NodeSeq.Empty else Text(num.toString)) + } + } + + implicit def jsCmdTransform: ComputeTransformRules[ToJsCmd] = new ComputeTransformRules[ToJsCmd] { def computeTransform(str: => ToJsCmd, ns: NodeSeq): Seq[NodeSeq] = List(Text(str.toJsCmd)) } @@ -791,6 +800,12 @@ object ComputeTransformRules { def computeTransform(info: => T[String], ns: NodeSeq): Seq[NodeSeq] = f(info).toSeq.map(a => Text(a)) } + implicit def iterableNumberTransform[T[_], N <: java.lang.Number](implicit f: T[N] => Iterable[N]): ComputeTransformRules[T[N]] = + new ComputeTransformRules[T[N]] { + def computeTransform(info: => T[N], ns: NodeSeq): Seq[NodeSeq] = f(info).toSeq.flatMap(a => + if (a eq null) Nil else List(Text(a.toString))) + } + implicit def iterableBindableTransform[T[_]](implicit f: T[Bindable] => Iterable[Bindable]): ComputeTransformRules[T[Bindable]] = new ComputeTransformRules[T[Bindable]] { def computeTransform(info: => T[Bindable], ns: NodeSeq): Seq[NodeSeq] = Helpers.ensureUniqueId(f(info).toSeq.map(_.asHtml)) diff --git a/core/util/src/main/scala/net/liftweb/util/CssSelector.scala b/core/util/src/main/scala/net/liftweb/util/CssSelector.scala index 91e1d3bb1e..0204393704 100644 --- a/core/util/src/main/scala/net/liftweb/util/CssSelector.scala +++ b/core/util/src/main/scala/net/liftweb/util/CssSelector.scala @@ -156,6 +156,7 @@ object CssSelectorParser extends PackratParsers with ImplicitConversions { private def fixAll(all: List[CssSelector], sn: Option[SubNode]): CssSelector = { (all, sn) match { + // case (Nil, Some()) case (r :: Nil, None) => r case (r :: Nil, Some(sn)) => r.withSubnode(sn) case (lst, None) => lst.reduceRight((b, a) => EnclosedSelector(b, a)) diff --git a/core/util/src/test/scala/net/liftweb/util/BindHelpersSpec.scala b/core/util/src/test/scala/net/liftweb/util/BindHelpersSpec.scala index 58e3241ecd..badd0747f6 100644 --- a/core/util/src/test/scala/net/liftweb/util/BindHelpersSpec.scala +++ b/core/util/src/test/scala/net/liftweb/util/BindHelpersSpec.scala @@ -650,6 +650,36 @@ object CssBindHelpersSpec extends Specification { } + "Java number support" in { + val f = "a *" #> Full(new java.lang.Long(12)) + val xml = Hello + + f(xml) must ==/ (12) + } + + + "Andreas's thing doesn't blow up" in { + def cachedMessageList: Box[Box[String]] = Empty + + def messageListId = "Hello" + + def collapseUnless[A](isEmptyCond: Boolean)(f: => A): Box[A] = { + if (!isEmptyCond) { + Empty + } else { + Full(f) + } + } + + ".noMail" #> collapseUnless(cachedMessageList.map(_.isEmpty).openOr(true)) { + "tbody [id]" #> messageListId & + "*" #> PassThru + } + + true must_== true + } + + "option transform on *" in { val opt: Box[String] = Empty val res = ("* *" #> opt.map(ignore => "Dog")).apply(cat) diff --git a/core/util/src/test/scala/net/liftweb/util/CssSelectorSpec.scala b/core/util/src/test/scala/net/liftweb/util/CssSelectorSpec.scala index 5887895627..c149680d69 100644 --- a/core/util/src/test/scala/net/liftweb/util/CssSelectorSpec.scala +++ b/core/util/src/test/scala/net/liftweb/util/CssSelectorSpec.scala @@ -50,6 +50,7 @@ object CssSelectorSpec extends Specification { AttrSelector("type", "button", Empty) } + ":checkbox must parse" in { CssSelectorParser.parse(":checkbox").open_! must_== AttrSelector("type", "checkbox", Empty) From 2674d4af4d52424802ed0bf5d081788d73be95a4 Mon Sep 17 00:00:00 2001 From: David Pollak Date: Mon, 27 Aug 2012 15:29:14 -0700 Subject: [PATCH 0164/1949] Cleaned up a spec --- web/webkit/src/test/scala/net/liftweb/http/SHtmlSpec.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/webkit/src/test/scala/net/liftweb/http/SHtmlSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/SHtmlSpec.scala index 3bb74426d8..c83f8090a1 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/SHtmlSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/SHtmlSpec.scala @@ -27,9 +27,9 @@ object SHtmlSpec extends Specification { val html1= - val inputField1= testS("/")( ("#number" #> SHtml.number(0, println(_), 0, 100))(html1) ) - val inputField2= testS("/")( ("#number" #> SHtml.number(0, println(_: Double), 0, 100, 0.1))(html1) ) - val inputField3= testS("/")( ("#number" #> SHtml.number(0, println(_: Double), 0, 100, 1))(html1) ) + val inputField1= testS("/")( ("#number" #> SHtml.number(0, println(_), 0, 100)).apply(html1) ) + val inputField2= testS("/")( ("#number" #> SHtml.number(0, println(_: Double), 0, 100, 0.1)).apply(html1) ) + val inputField3= testS("/")( ("#number" #> SHtml.number(0, println(_: Double), 0, 100, 1)).apply(html1) ) "SHtml" should { "create a number input field" in { From c13b8f4dd398b9652107b1dce843ea2fb5680dcc Mon Sep 17 00:00:00 2001 From: David Pollak Date: Mon, 27 Aug 2012 16:01:29 -0700 Subject: [PATCH 0165/1949] Added a test for star and also did the surround kids --- .../scala/net/liftweb/util/CssSelector.scala | 18 ++++++++++-- .../net/liftweb/util/BindHelpersSpec.scala | 28 +++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/CssSelector.scala b/core/util/src/main/scala/net/liftweb/util/CssSelector.scala index 0204393704..f7851d2283 100644 --- a/core/util/src/main/scala/net/liftweb/util/CssSelector.scala +++ b/core/util/src/main/scala/net/liftweb/util/CssSelector.scala @@ -19,7 +19,7 @@ package util import scala.util.parsing.combinator.{PackratParsers, Parsers, ImplicitConversions} -import scala.xml.NodeSeq +import xml.{Elem, NodeSeq} import net.liftweb.common._ sealed trait CssSelector { @@ -84,7 +84,20 @@ final case object DontMergeAttributes extends SubNode { } final case class SurroundKids() extends SubNode with WithKids { - def transform(original: NodeSeq, newNs: NodeSeq): NodeSeq = newNs ++ original // FIXME + def transform(original: NodeSeq, newNs: NodeSeq): NodeSeq = { + var changed = false + + val res: NodeSeq = newNs.flatMap{ + case e: Elem if !changed => + changed = true + new Elem(e.prefix, + e.label, e.attributes, + e.scope, e.child ++ original :_*) + case x => x + } + + if (changed) res else newNs ++ original + } } final case class AppendKidsSubNode() extends SubNode with WithKids { @@ -167,6 +180,7 @@ object CssSelectorParser extends PackratParsers with ImplicitConversions { private lazy val topParser: Parser[CssSelector] = phrase(rep1((_idMatch | _nameMatch | _classMatch | _attrMatch | _elemMatch | _colonMatch | _starMatch) <~ (rep1(' ') | 26.toChar)) ~ opt(subNode)) ^^ { + case (one :: Nil) ~ sn => fixAll(List(one), sn) case all ~ None if all.takeRight(1).head == StarSelector(Empty) => fixAll(all.dropRight(1), Some(KidsSubNode())) case all ~ sn => fixAll(all, sn) diff --git a/core/util/src/test/scala/net/liftweb/util/BindHelpersSpec.scala b/core/util/src/test/scala/net/liftweb/util/BindHelpersSpec.scala index badd0747f6..5bdd956f3e 100644 --- a/core/util/src/test/scala/net/liftweb/util/BindHelpersSpec.scala +++ b/core/util/src/test/scala/net/liftweb/util/BindHelpersSpec.scala @@ -658,6 +658,13 @@ object CssBindHelpersSpec extends Specification { } + "Surround kids" in { + val f = "a <*>" #>
    + val xml = Meow Cat woof + + f(xml) must ==/ (Meow
    Cat
    woof
    ) + } + "Andreas's thing doesn't blow up" in { def cachedMessageList: Box[Box[String]] = Empty @@ -679,6 +686,27 @@ object CssBindHelpersSpec extends Specification { true must_== true } + "other Andreas test" in { + def renderBlogEntrySummary = { + ".blogEntry" #> ((ns: NodeSeq) => { + ("*" #> "Horse").apply(ns) + }) + } + + + + def render = { + + "*" #> ((ns: NodeSeq) => + renderBlogEntrySummary.apply(ns) ++ hi + ) + } + + render + + true must_== true + } + "option transform on *" in { val opt: Box[String] = Empty From 2d7622067352ad7c3bd0d119d486693854d7987d Mon Sep 17 00:00:00 2001 From: Joni Freeman Date: Fri, 31 Aug 2012 20:39:31 +0300 Subject: [PATCH 0166/1949] Optimize caching with volatile var instead of synchronizing the whole block. --- core/json/src/main/scala/net/liftweb/json/Meta.scala | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/json/src/main/scala/net/liftweb/json/Meta.scala b/core/json/src/main/scala/net/liftweb/json/Meta.scala index 9437dac36b..e9fa49218d 100644 --- a/core/json/src/main/scala/net/liftweb/json/Meta.scala +++ b/core/json/src/main/scala/net/liftweb/json/Meta.scala @@ -191,15 +191,14 @@ private[json] object Meta { private[json] def fail(msg: String, cause: Exception = null) = throw new MappingException(msg, cause) private class Memo[A, R] { - private var cache = Map[A, R]() + @volatile private var cache = Map[A, R]() - def memoize(x: A, f: A => R): R = synchronized { + def memoize(x: A, f: A => R): R = if (cache contains x) cache(x) else { val ret = f(x) cache += (x -> ret) ret } - } } object Reflection { From 430da4a551855dee72ea2a878207007fcbd3a14f Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Fri, 31 Aug 2012 14:32:58 -0400 Subject: [PATCH 0167/1949] Revert "Explain double Box in LiftServlet.handleVersionedAjax." This reverts commit 2d0709a40ab600bf27df602d1f5bbb187e0661b7. --- .../src/main/scala/net/liftweb/http/LiftServlet.scala | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala index 09808ba64b..477bf910be 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala @@ -614,12 +614,6 @@ class LiftServlet extends Loggable { case Full(handlerVersion) => val renderVersion = RenderVersion.get - // An Empty in toReturn indicates there is no precomputed response for - // this AJAX request/version. Note that runAjax returns a - // Box[LiftResponse]. So we can have a Full(Empty) that indicates - // runAjax has computed a response, and that response was an Empty. - // That's why we have a double Box here. If we get an Empty back, - // we compute the actual response by calling runAjax below. val toReturn: Box[Box[LiftResponse]] = liftSession.withAjaxRequests { currentAjaxRequests => currentAjaxRequests.get(renderVersion).collect { @@ -632,7 +626,7 @@ class LiftServlet extends Loggable { (renderVersion -> AjaxRequestInfo(handlerVersion, Empty, cont :: Nil, millis)) - Empty + Empty // no response available, triggers the actual AJAX computation below case AjaxRequestInfo(storedVersion, existingResponseBox @ Full(_), _, _) => existingResponseBox // return the Full response Box @@ -650,7 +644,7 @@ class LiftServlet extends Loggable { (renderVersion -> AjaxRequestInfo(handlerVersion, Empty, cont :: Nil, millis)) - Empty + Empty // no response available, triggers the actual AJAX computation below } } From 1d9840e75e770808ee1cd884a0bf75fc8ee7f059 Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Fri, 31 Aug 2012 14:33:13 -0400 Subject: [PATCH 0168/1949] Revert "Make ajaxRequests a val in LiftSession." This reverts commit 11c83fe128e27f743df75f0c0603d6d6b3b28178. --- web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index 37a5e10a39..fc95685855 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -571,7 +571,7 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, * A list of AJAX requests that may or may not be pending for this * session. There is up to one entry per RenderVersion. */ - private val ajaxRequests = scala.collection.mutable.Map[String,AjaxRequestInfo]() + private var ajaxRequests = scala.collection.mutable.Map[String,AjaxRequestInfo]() private[http] def withAjaxRequests[T](fn: (scala.collection.mutable.Map[String, AjaxRequestInfo]) => T): T = { ajaxRequests.synchronized { fn(ajaxRequests) } From e6866826e62ba6c627dc23e819b8c9ed8a340090 Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Fri, 31 Aug 2012 14:35:14 -0400 Subject: [PATCH 0169/1949] Revert "Don't suspend requests too early for the first request." This reverts commit 70ffcbaa3fca6760de3096da5bc64585eeaf9479. --- .../src/main/scala/net/liftweb/http/LiftServlet.scala | 10 +++++----- .../src/main/scala/net/liftweb/http/LiftSession.scala | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala index 477bf910be..11bc5a7a57 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala @@ -626,6 +626,8 @@ class LiftServlet extends Loggable { (renderVersion -> AjaxRequestInfo(handlerVersion, Empty, cont :: Nil, millis)) + suspendRequest() + Empty // no response available, triggers the actual AJAX computation below case AjaxRequestInfo(storedVersion, existingResponseBox @ Full(_), _, _) => @@ -644,12 +646,14 @@ class LiftServlet extends Loggable { (renderVersion -> AjaxRequestInfo(handlerVersion, Empty, cont :: Nil, millis)) + suspendRequest() + Empty // no response available, triggers the actual AJAX computation below } } toReturn or { - val result = Full(runAjax(requestState, liftSession, Full((result: LiftResponse) => { + Full(runAjax(requestState, liftSession, Full((result: LiftResponse) => { // When we get the response, synchronizedly check that the // versions are still the same in the map, and, if so, update // any waiting actors then clear the actor list and update the @@ -665,10 +669,6 @@ class LiftServlet extends Loggable { } } }))) - - suspendRequest() - - result } openOr Empty case _ => diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index fc95685855..6e7aa8bd69 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -573,7 +573,7 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, */ private var ajaxRequests = scala.collection.mutable.Map[String,AjaxRequestInfo]() - private[http] def withAjaxRequests[T](fn: (scala.collection.mutable.Map[String, AjaxRequestInfo]) => T): T = { + private[http] def withAjaxRequests[T](fn: (scala.collection.mutable.Map[String, AjaxRequestInfo]) => T) = { ajaxRequests.synchronized { fn(ajaxRequests) } } From 1348a66e7f8a53fa7995a17a9fe825badac7c5d9 Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Fri, 31 Aug 2012 14:35:32 -0400 Subject: [PATCH 0170/1949] Revert "Move ajax request list into LiftSession with lastSeen." This reverts commit 7d89340f0a4bb2020d271b9c334658b765ac0e66. --- .../scala/net/liftweb/http/LiftServlet.scala | 31 ++++++++++------ .../scala/net/liftweb/http/LiftSession.scala | 36 ------------------- 2 files changed, 21 insertions(+), 46 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala index 11bc5a7a57..8cc7a8ebbc 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala @@ -472,7 +472,15 @@ class LiftServlet extends Loggable { private case class AjaxResponseComplete(response: Box[LiftResponse]) + /** + * existingResponse is Empty if we have no response for this request + * yet. pendingActors is a list of actors who want to be notified when + * this response is received. + */ + private case class AjaxRequestInfo(requestVersion:Int, existingResponse:Box[Box[LiftResponse]], pendingActors:List[LiftActor]) + private lazy val ajaxPostTimeout: Long = LiftRules.ajaxPostTimeout * 1000L + private val currentAjaxRequests = scala.collection.mutable.Map[String,AjaxRequestInfo]() /** * Runs the actual AJAX processing. This includes handling __lift__GC, @@ -502,6 +510,9 @@ class LiftServlet extends Loggable { val renderVersion = RenderVersion.get liftSession.updateFuncByOwner(renderVersion, millis) + // FIXME We need to clean up currentAjaxRequests entries when + // FIXME the page expires. + Full(JavaScriptResponse(js.JsCmds.Noop)) case _ => @@ -615,28 +626,28 @@ class LiftServlet extends Loggable { val renderVersion = RenderVersion.get val toReturn: Box[Box[LiftResponse]] = - liftSession.withAjaxRequests { currentAjaxRequests => + currentAjaxRequests.synchronized { currentAjaxRequests.get(renderVersion).collect { - case AjaxRequestInfo(storedVersion, _, pendingActors, _) if handlerVersion != storedVersion => + case AjaxRequestInfo(storedVersion, _, pendingActors) if handlerVersion != storedVersion => // Break out of any actors for the stale version. pendingActors.foreach(_ ! BreakOut()) // Evict the older version's info. currentAjaxRequests += (renderVersion -> - AjaxRequestInfo(handlerVersion, Empty, cont :: Nil, millis)) + AjaxRequestInfo(handlerVersion, Empty, cont :: Nil)) suspendRequest() Empty // no response available, triggers the actual AJAX computation below - case AjaxRequestInfo(storedVersion, existingResponseBox @ Full(_), _, _) => + case AjaxRequestInfo(storedVersion, existingResponseBox @ Full(_), _) => existingResponseBox // return the Full response Box - case AjaxRequestInfo(storedVersion, _, pendingActors, _) => + case AjaxRequestInfo(storedVersion, _, pendingActors) => currentAjaxRequests += (renderVersion -> - AjaxRequestInfo(handlerVersion, Empty, cont :: pendingActors, millis)) + AjaxRequestInfo(handlerVersion, Empty, cont :: pendingActors)) suspendRequest() @@ -644,7 +655,7 @@ class LiftServlet extends Loggable { } openOr { currentAjaxRequests += (renderVersion -> - AjaxRequestInfo(handlerVersion, Empty, cont :: Nil, millis)) + AjaxRequestInfo(handlerVersion, Empty, cont :: Nil)) suspendRequest() @@ -659,13 +670,13 @@ class LiftServlet extends Loggable { // any waiting actors then clear the actor list and update the // request info to include the response in case any other // requests come in with this version. - liftSession.withAjaxRequests { currentAjaxRequests => + currentAjaxRequests.synchronized { currentAjaxRequests.get(renderVersion).collect { - case AjaxRequestInfo(storedVersion, _, pendingActors, _) if storedVersion == handlerVersion => + case AjaxRequestInfo(storedVersion, _, pendingActors) if storedVersion == handlerVersion => pendingActors.foreach(_ ! AjaxResponseComplete(Full(result))) currentAjaxRequests += (renderVersion -> - AjaxRequestInfo(handlerVersion, Full(Full(result)), Nil, millis)) + AjaxRequestInfo(handlerVersion, Full(Full(result)), Nil)) } } }))) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index 6e7aa8bd69..5733db1604 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -499,16 +499,6 @@ private final case class PostPageFunctions(renderVersion: String, } -/** - * existingResponse is Empty if we have no response for this request - * yet. pendingActors is a list of actors who want to be notified when - * this response is received. - */ -private[http] final case class AjaxRequestInfo(requestVersion:Int, - existingResponse:Box[Box[LiftResponse]], - pendingActors:List[LiftActor], - lastSeen: Long) - /** * The LiftSession class containg the session state information */ @@ -567,16 +557,6 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, */ private var postPageFunctions: Map[String, PostPageFunctions] = Map() - /** - * A list of AJAX requests that may or may not be pending for this - * session. There is up to one entry per RenderVersion. - */ - private var ajaxRequests = scala.collection.mutable.Map[String,AjaxRequestInfo]() - - private[http] def withAjaxRequests[T](fn: (scala.collection.mutable.Map[String, AjaxRequestInfo]) => T) = { - ajaxRequests.synchronized { fn(ajaxRequests) } - } - /** * The synchronization lock for the postPageFunctions */ @@ -870,15 +850,6 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, } } - withAjaxRequests { currentAjaxRequests => - for { - (version, requestInfo) <- currentAjaxRequests - if (now - requestInfo.lastSeen) > LiftRules.unusedFunctionsLifeTime - } { - currentAjaxRequests -= version - } - } - synchronized { messageCallback.foreach { case (k, f) => @@ -1006,13 +977,6 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, } postPageFunctions += (ownerName -> funcInfo.updateLastSeen) } - withAjaxRequests { currentAjaxRequests => - currentAjaxRequests.get(ownerName).foreach { - case info: AjaxRequestInfo => - currentAjaxRequests += (ownerName -> info.copy(lastSeen = time)) - } - } - synchronized { (0 /: messageCallback)((l, v) => l + (v._2.owner match { case Full(owner) if (owner == ownerName) => From 3188c0c6450fd6e2cd2ac3206e5d7df322b628c4 Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Fri, 31 Aug 2012 14:36:00 -0400 Subject: [PATCH 0171/1949] Revert "Delimit version in GUID string by a dash." This reverts commit 1c465306bd2049bcdeb8b81fce5c1fda5e774c95. --- .../src/main/scala/net/liftweb/http/LiftServlet.scala | 8 ++++---- .../main/scala/net/liftweb/http/js/ScriptRenderer.scala | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala index 8cc7a8ebbc..e8e42c2290 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala @@ -418,11 +418,11 @@ class LiftServlet extends Loggable { private object AjaxVersions { def unapply(ajaxPathPart: String) : Option[(String,Int)] = { - val dash = ajaxPathPart.indexOf("-") - if (dash > -1 && ajaxPathPart.length > dash + 1) + val funcLength = Helpers.nextFuncName.length + if (ajaxPathPart.length > funcLength) Some( - (ajaxPathPart.substring(0, dash), - ajaxPathPart.charAt(dash + 1)) + (ajaxPathPart.substring(0, funcLength), + ajaxPathPart.charAt(funcLength)) ) else None diff --git a/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala b/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala index dfc650e968..00bbef286a 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala @@ -186,7 +186,7 @@ object ScriptRenderer { addPageNameAndVersion: function(url) { return """ + { if (LiftRules.enableLiftGC) { - "url.replace('" + LiftRules.ajaxPath + "', '" + LiftRules.ajaxPath + "/'+lift_page+('-'+liftAjax.lift_ajaxVersion%36).toString(36));" + "url.replace('" + LiftRules.ajaxPath + "', '" + LiftRules.ajaxPath + "/'+lift_page+(liftAjax.lift_ajaxVersion % 36).toString(36));" } else { "url;" } From 30d97e0bad74cd743f434d4124b024f49047c8ed Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Fri, 31 Aug 2012 14:36:21 -0400 Subject: [PATCH 0172/1949] Revert "AJAX version-based deduplication." This reverts commit ba0b3130949459fbcb3dc07d89e23106c046aa03. --- .../scala/net/liftweb/http/LiftServlet.scala | 321 ++---------------- .../scala/net/liftweb/http/LiftSession.scala | 24 +- 2 files changed, 40 insertions(+), 305 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala index e8e42c2290..9622f6f5eb 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala @@ -416,146 +416,48 @@ class LiftServlet extends Loggable { toReturn } - private object AjaxVersions { - def unapply(ajaxPathPart: String) : Option[(String,Int)] = { - val funcLength = Helpers.nextFuncName.length - if (ajaxPathPart.length > funcLength) - Some( - (ajaxPathPart.substring(0, funcLength), - ajaxPathPart.charAt(funcLength)) - ) - else - None - } - } - /** - * Extracts two versions from a given AJAX path: - * - The RenderVersion, which is used for GC purposes. - * - The requestVersion, which lets us determine if this is - * a request we've already dealt with or are currently dealing - * with (so we don't rerun the associated handler). See - * handleVersionedAjax for more. - * - * The requestVersion is passed to the function that is passed in. - */ - private def extractVersions[T](path: List[String])(f: (Box[Int]) => T): T = { + private def extractVersion[T](path: List[String])(f: => T): T = { path match { - case first :: AjaxVersions(renderVersion, requestVersion) :: _ => - RenderVersion.doWith(renderVersion)(f(Full(requestVersion))) - case _ => f(Empty) + case first :: second :: _ => RenderVersion.doWith(second)(f) + case _ => f } } - /** - * An actor that manages AJAX continuations from container (Jetty style). - */ - class AjaxContinuationActor(request: Req, session: LiftSession, - onBreakout: Box[LiftResponse] => Unit) extends LiftActor { - private var response: Box[LiftResponse] = Empty - private var done = false - - def messageHandler = { - case AjaxResponseComplete(completeResponse) => - response = completeResponse - LAPinger.schedule(this, BreakOut(), 5 millis) - - case BreakOut() if ! done => - done = true - session.exitComet(this) - onBreakout(response) - - case _ => - } - - override def toString = "AJAX Continuation Actor" - } + private def handleAjax(liftSession: LiftSession, + requestState: Req): Box[LiftResponse] = { + extractVersion(requestState.path.partPath) { - private case class AjaxResponseComplete(response: Box[LiftResponse]) + LiftRules.cometLogger.debug("AJAX Request: " + liftSession.uniqueId + " " + requestState.params) + tryo { + LiftSession.onBeginServicing.foreach(_(liftSession, requestState)) + } - /** - * existingResponse is Empty if we have no response for this request - * yet. pendingActors is a list of actors who want to be notified when - * this response is received. - */ - private case class AjaxRequestInfo(requestVersion:Int, existingResponse:Box[Box[LiftResponse]], pendingActors:List[LiftActor]) + val ret = try { + requestState.param("__lift__GC") match { + case Full(_) => + liftSession.updateFuncByOwner(RenderVersion.get, millis) + Full(JavaScriptResponse(js.JsCmds.Noop)) - private lazy val ajaxPostTimeout: Long = LiftRules.ajaxPostTimeout * 1000L - private val currentAjaxRequests = scala.collection.mutable.Map[String,AjaxRequestInfo]() + case _ => + try { + val what = flatten(try { + liftSession.runParams(requestState) + } catch { + case ResponseShortcutException(_, Full(to), _) => + import js.JsCmds._ + List(RedirectTo(to)) + }) - /** - * Runs the actual AJAX processing. This includes handling __lift__GC, - * or running the parameters in the session. onComplete is run when the - * AJAX request has completed with a response that is meant for the - * user. In cases where the request is taking too long to respond, - * an LAFuture may be used to delay the real response (and thus the - * invocation of onComplete) while this function returns an empty - * response. - */ - private def runAjax(requestState: Req, - liftSession: LiftSession, - onCompleteFn: Box[(LiftResponse)=>Unit]): Box[LiftResponse] = { - // We don't want to call the onComplete function if we're returning - // an error response, as if there is a comet timeout while - // processing. This is because the onComplete function is meant to - // indicate a successful completion, and will short-circuit any - // other AJAX requests with the same version with the same - // response. Error responses are specific to the AJAX request, - // rather than to the version of the AJAX request. Successful - // responses are for all AJAX requests with the same version. - var callCompleteFn = true - - val ret = try { - requestState.param("__lift__GC") match { - case Full(_) => - val renderVersion = RenderVersion.get - liftSession.updateFuncByOwner(renderVersion, millis) - - // FIXME We need to clean up currentAjaxRequests entries when - // FIXME the page expires. - - Full(JavaScriptResponse(js.JsCmds.Noop)) - - case _ => - try { - val what = flatten(try { - liftSession.runParams(requestState) - } catch { - case ResponseShortcutException(_, Full(to), _) => - import js.JsCmds._ - List(RedirectTo(to)) - }) - - def processResponse(response: List[Any]): LiftResponse = { - val what2 = response.flatMap { + val what2 = what.flatMap { case js: JsCmd => List(js) case jv: JValue => List(jv) case n: NodeSeq => List(n) case js: JsCommands => List(js) case r: LiftResponse => List(r) - - // This strange case comes from ajax requests run in - // a comet context that don't complete in the - // cometTimeout time frame. The js is the result of the - // comet processing timeout handler, while the future - // gives us access to the eventual result of the ajax - // request. - case (js, future: LAFuture[List[Any]]) if onCompleteFn.isDefined => - // Wait for the future on a separate thread. - Schedule.schedule(() => { - val result = flatten(future.get) - onCompleteFn.foreach(_(processResponse(result))) - }, 0 seconds) - - // But this request is done for. Return the comet - // processing timeout result, but don't mark the - // request complete; that happens whenever we satisfy - // the future above. - callCompleteFn = false - List(js) case s => Nil } - what2 match { + val ret: LiftResponse = what2 match { case (json: JsObj) :: Nil => JsonResponse(json) case (jv: JValue) :: Nil => JsonResponse(jv) case (js: JsCmd) :: xs => { @@ -572,174 +474,21 @@ class LiftServlet extends Loggable { case (r: LiftResponse) :: _ => r case _ => JsCommands(S.noticesToJsCmd :: JsCmds.Noop :: S.jsToAppend).toResponse } - } - val ret: LiftResponse = processResponse(what) + LiftRules.cometLogger.debug("AJAX Response: " + liftSession.uniqueId + " " + ret) - LiftRules.cometLogger.debug("AJAX Response: " + liftSession.uniqueId + " " + ret) - - Full(ret) - } finally { - if (S.functionMap.size > 0) { - liftSession.updateFunctionMap(S.functionMap, RenderVersion.get, millis) - S.clearFunctionMap - } - } - } - } catch { - case foc: LiftFlowOfControlException => throw foc - case e => S.assertExceptionThrown() ; NamedPF.applyBox((Props.mode, requestState, e), LiftRules.exceptionHandler.toList); - } - - for { - response <- ret if callCompleteFn - onComplete <- onCompleteFn - } { - onComplete(response) - } - - ret - } - - /** - * Handles the details of versioned AJAX requests. If the version is - * Empty, runs the request through a normal AJAX flow with no - * continuations or special handling. Repeated calls will cause - * repeated evaluation. - * - * If the version is Full, the request is tracked. Subsequent requests - * for the same version will suspend until a response is available, - * then they will return the response. - * - * cont is the AjaxContinuationActor for this request, and - * suspendRequest is the function that will suspend the request. - * These are present to support non-continuation containers; these - * will present a no-op to suspendRequest. - */ - private def handleVersionedAjax(handlerVersion: Box[Int], - cont: AjaxContinuationActor, - suspendRequest: () => Any, - requestState: Req, - liftSession: LiftSession): Box[LiftResponse] = { - handlerVersion match { - case Full(handlerVersion) => - val renderVersion = RenderVersion.get - - val toReturn: Box[Box[LiftResponse]] = - currentAjaxRequests.synchronized { - currentAjaxRequests.get(renderVersion).collect { - case AjaxRequestInfo(storedVersion, _, pendingActors) if handlerVersion != storedVersion => - // Break out of any actors for the stale version. - pendingActors.foreach(_ ! BreakOut()) - - // Evict the older version's info. - currentAjaxRequests += - (renderVersion -> - AjaxRequestInfo(handlerVersion, Empty, cont :: Nil)) - - suspendRequest() - - Empty // no response available, triggers the actual AJAX computation below - - case AjaxRequestInfo(storedVersion, existingResponseBox @ Full(_), _) => - existingResponseBox // return the Full response Box - - case AjaxRequestInfo(storedVersion, _, pendingActors) => - currentAjaxRequests += - (renderVersion -> - AjaxRequestInfo(handlerVersion, Empty, cont :: pendingActors)) - - suspendRequest() - - Full(Full(EmptyResponse)) - } openOr { - currentAjaxRequests += - (renderVersion -> - AjaxRequestInfo(handlerVersion, Empty, cont :: Nil)) - - suspendRequest() - - Empty // no response available, triggers the actual AJAX computation below - } - } - - toReturn or { - Full(runAjax(requestState, liftSession, Full((result: LiftResponse) => { - // When we get the response, synchronizedly check that the - // versions are still the same in the map, and, if so, update - // any waiting actors then clear the actor list and update the - // request info to include the response in case any other - // requests come in with this version. - currentAjaxRequests.synchronized { - currentAjaxRequests.get(renderVersion).collect { - case AjaxRequestInfo(storedVersion, _, pendingActors) if storedVersion == handlerVersion => - pendingActors.foreach(_ ! AjaxResponseComplete(Full(result))) - currentAjaxRequests += - (renderVersion -> - AjaxRequestInfo(handlerVersion, Full(Full(result)), Nil)) + Full(ret) + } finally { + if (S.functionMap.size > 0) { + liftSession.updateFunctionMap(S.functionMap, RenderVersion.get, millis) + S.clearFunctionMap } } - }))) - } openOr Empty - - case _ => - runAjax(requestState, liftSession, Empty) - } - } - - /** - * Kick off AJAX handling. Extracts relevant versions and handles the - * begin/end servicing requests, as well as generation of - * ContinuationActors and choosing between continuation and - * continuationless request handling. - */ - private def handleAjax(liftSession: LiftSession, - requestState: Req): Box[LiftResponse] = { - extractVersions(requestState.path.partPath) { handlerVersion => - LiftRules.cometLogger.debug("AJAX Request: " + liftSession.uniqueId + " " + requestState.params) - tryo { - LiftSession.onBeginServicing.foreach(_(liftSession, requestState)) - } - - def suspendingActor = { - new AjaxContinuationActor(requestState, liftSession, - response => { - requestState.request.resume((requestState, S.init(requestState, liftSession) - (response.map(LiftRules.performTransform) openOr EmptyResponse)))}) - } - - def waitingActorForFuture(future: LAFuture[Box[LiftResponse]]) = { - new AjaxContinuationActor(requestState, liftSession, - response => future.satisfy(response)) - } - - val possibleFuture = - if (requestState.request.suspendResumeSupport_?) - Empty - else - Full(new LAFuture[Box[LiftResponse]]) - val (cont, suspendRequest) = - possibleFuture.map { f => - (waitingActorForFuture(f), () => ()) - } openOr { - (suspendingActor, () => requestState.request.suspend(ajaxPostTimeout + 500L)) } - - val ret: Box[LiftResponse] = try { - liftSession.enterComet(cont -> requestState) - - val result: Box[LiftResponse] = - handleVersionedAjax(handlerVersion, cont, suspendRequest, requestState, liftSession) - - possibleFuture.map(_.get(ajaxPostTimeout) match { - case Full(response) => response - case _ => Empty - }) openOr result - } finally { - if (! requestState.request.suspendResumeSupport_?) - liftSession.exitComet(cont) + } catch { + case foc: LiftFlowOfControlException => throw foc + case e => S.assertExceptionThrown() ; NamedPF.applyBox((Props.mode, requestState, e), LiftRules.exceptionHandler.toList); } - tryo { LiftSession.onEndServicing.foreach(_(liftSession, requestState, ret)) } diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index 5733db1604..dc206fd6c2 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -675,6 +675,7 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, * Executes the user's functions based on the query parameters */ def runParams(state: Req): List[Any] = { + val toRun = { // get all the commands, sorted by owner, (state.uploadedFiles.map(_.name) ::: state.paramNames).distinct. @@ -707,28 +708,13 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, val f = toRun.filter(_.owner == w) w match { // if it's going to a CometActor, batch up the commands - case Full(id) if asyncById.contains(id) => asyncById.get(id).toList.flatMap(a => { - val future = - a.!<(ActionMessageSet(f.map(i => buildFunc(i)), state)) - - def processResult(result: Any): List[Any] = result match { + case Full(id) if asyncById.contains(id) => asyncById.get(id).toList.flatMap(a => + a.!?(a.cometProcessingTimeout, ActionMessageSet(f.map(i => buildFunc(i)), state)) match { case Full(li: List[_]) => li case li: List[_] => li - // We return the future so it can, from AJAX requests, be - // satisfied and update the pending ajax request map. - case Empty => - val processingFuture = new LAFuture[Any] - // Wait for and process the future on a separate thread. - Schedule.schedule(() => { - processingFuture.satisfy(processResult(future.get)) - }, 0 seconds) - List((a.cometProcessingTimeoutHandler, processingFuture)) + case Empty => Full(a.cometProcessingTimeoutHandler()) case other => Nil - } - - processResult(future.get(a.cometProcessingTimeout)) - }) - + }) case _ => f.map(i => buildFunc(i).apply()) } } From bb80eab94092311a5d60ae373eaa8e1fedc389f1 Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Fri, 31 Aug 2012 14:36:40 -0400 Subject: [PATCH 0173/1949] Revert "Rename continuation-related code to clarify Comet association." This reverts commit 0f7b081642948f8b3ca0c75af77936101d3323d3. --- .../scala/net/liftweb/http/LiftServlet.scala | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala index 9622f6f5eb..8bafb9ce59 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala @@ -496,10 +496,10 @@ class LiftServlet extends Loggable { } } - /** - * An actor that manages comet continuations from container (Jetty style) +/** + * An actor that manages continuations from container (Jetty style) */ - class CometContinuationActor(request: Req, session: LiftSession, + class ContinuationActor(request: Req, session: LiftSession, actors: List[(LiftCometActor, Long)], onBreakout: List[AnswerRender] => Unit) extends LiftActor { private var answers: List[AnswerRender] = Nil @@ -529,15 +529,15 @@ class LiftServlet extends Loggable { case _ => } - override def toString = "Continuation Actor dude " + seqId + override def toString = "Actor dude " + seqId } private object BeginContinuation private lazy val cometTimeout: Long = (LiftRules.cometRequestTimeout openOr 120) * 1000L - private def setupCometContinuation(request: Req, session: LiftSession, actors: List[(LiftCometActor, Long)]): Any = { - val cont = new CometContinuationActor(request, session, actors, + private def setupContinuation(request: Req, session: LiftSession, actors: List[(LiftCometActor, Long)]): Any = { + val cont = new ContinuationActor(request, session, actors, answers => request.request.resume( (request, S.init(request, session) (LiftRules.performTransform( @@ -566,7 +566,7 @@ class LiftServlet extends Loggable { if (actors.isEmpty) Left(Full(new JsCommands(LiftRules.noCometSessionCmd.vend :: js.JE.JsRaw("lift_toWatch = {};").cmd :: Nil).toResponse)) else requestState.request.suspendResumeSupport_? match { case true => { - setupCometContinuation(requestState, sessionActor, actors) + setupContinuation(requestState, sessionActor, actors) Left(Full(EmptyResponse)) } @@ -614,7 +614,7 @@ class LiftServlet extends Loggable { private def handleNonContinuationComet(request: Req, session: LiftSession, actors: List[(LiftCometActor, Long)], originalRequest: Req): () => Box[LiftResponse] = () => { val f = new LAFuture[List[AnswerRender]] - val cont = new CometContinuationActor(request, session, actors, + val cont = new ContinuationActor(request, session, actors, answers => f.satisfy(answers)) try { From aeca4536a0f29e4cb0e2b2b296a98c969ba0cd62 Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Fri, 31 Aug 2012 14:37:24 -0400 Subject: [PATCH 0174/1949] Revert "Include a version on the end of the Ajax GUID." This reverts commit bbd703dcad3296e211354c9a521707c8f2fc0b31. --- .../net/liftweb/http/js/JSArtifacts.scala | 31 +++++-------------- .../net/liftweb/http/js/ScriptRenderer.scala | 23 +++----------- .../http/js/extcore/ExtCoreArtifacts.scala | 2 +- .../http/js/jquery/JQueryArtifacts.scala | 8 +---- .../liftweb/http/js/yui/YUIArtifacts.scala | 8 +---- 5 files changed, 15 insertions(+), 57 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/js/JSArtifacts.scala b/web/webkit/src/main/scala/net/liftweb/http/js/JSArtifacts.scala index b75de40ac6..d719954026 100755 --- a/web/webkit/src/main/scala/net/liftweb/http/js/JSArtifacts.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/js/JSArtifacts.scala @@ -112,24 +112,24 @@ trait JSArtifacts { } /** - * The companion module for AjaxInfodd that provides + * The companion module for AjaxInfo that provides * different construction schemes */ object AjaxInfo { def apply(data: JsExp, post: Boolean) = - new AjaxInfo(data, if (post) "POST" else "GET", 1000, false, "script", Empty, Empty, true) + new AjaxInfo(data, if (post) "POST" else "GET", 1000, false, "script", Empty, Empty) def apply(data: JsExp, dataType: String, post: Boolean) = - new AjaxInfo(data, if (post) "POST" else "GET", 1000, false, dataType, Empty, Empty, true) + new AjaxInfo(data, if (post) "POST" else "GET", 1000, false, dataType, Empty, Empty) def apply(data: JsExp) = - new AjaxInfo(data, "POST", 1000, false, "script", Empty, Empty, true) + new AjaxInfo(data, "POST", 1000, false, "script", Empty, Empty) def apply(data: JsExp, dataType: String) = - new AjaxInfo(data, "POST", 1000, false, dataType, Empty, Empty, true) + new AjaxInfo(data, "POST", 1000, false, dataType, Empty, Empty) def apply(data: JsExp, post: Boolean, @@ -142,23 +142,7 @@ object AjaxInfo { false, "script", Full(successFunc), - Full(failFunc), - true) - - def apply(data: JsExp, - post: Boolean, - timeout: Long, - successFunc: String, - failFunc: String, - includeVersion: Boolean) = - new AjaxInfo(data, - if (post) "POST" else "GET", - timeout, - false, - "script", - Full(successFunc), - Full(failFunc), - includeVersion) + Full(failFunc)) } /** @@ -166,6 +150,5 @@ object AjaxInfo { */ case class AjaxInfo(data: JsExp, action: String, timeout: Long, cache: Boolean, dataType: String, - successFunc: Box[String], failFunc: Box[String], - includeVersion: Boolean) + successFunc: Box[String], failFunc: Box[String]) diff --git a/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala b/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala index 00bbef286a..4b9d536fdc 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala @@ -107,7 +107,8 @@ object ScriptRenderer { "POST", LiftRules.ajaxPostTimeout, false, "script", - Full("liftAjax.lift_successRegisterGC"), Full("liftAjax.lift_failRegisterGC"), false)) + """ + Full("liftAjax.lift_successRegisterGC"), Full("liftAjax.lift_failRegisterGC"))) + + """ }, @@ -131,7 +132,6 @@ object ScriptRenderer { aboutToSend.onSuccess(data); } liftAjax.lift_doCycleQueueCnt++; - liftAjax.lift_ajaxVersion++; liftAjax.lift_doAjaxCycle(); }; @@ -145,7 +145,6 @@ object ScriptRenderer { queue.push(aboutToSend); liftAjax.lift_ajaxQueueSort(); } else { - liftAjax.lift_ajaxVersion++; if (aboutToSend.onFailure) { aboutToSend.onFailure(); } else { @@ -181,18 +180,6 @@ object ScriptRenderer { setTimeout("liftAjax.lift_doAjaxCycle();", 200); }, - lift_ajaxVersion: 0, - - addPageNameAndVersion: function(url) { - return """ + { - if (LiftRules.enableLiftGC) { - "url.replace('" + LiftRules.ajaxPath + "', '" + LiftRules.ajaxPath + "/'+lift_page+(liftAjax.lift_ajaxVersion % 36).toString(36));" - } else { - "url;" - } - } + """ - }, - addPageName: function(url) { return """ + { if (LiftRules.enableLiftGC) { @@ -209,7 +196,7 @@ object ScriptRenderer { "POST", LiftRules.ajaxPostTimeout, false, "script", - Full("onSuccess"), Full("onFailure"), true)) + + Full("onSuccess"), Full("onFailure"))) + """ }, @@ -219,7 +206,7 @@ object ScriptRenderer { "POST", LiftRules.ajaxPostTimeout, false, "json", - Full("onSuccess"), Full("onFailure"), true)) + + Full("onSuccess"), Full("onFailure"))) + """ } }; @@ -292,7 +279,7 @@ object ScriptRenderer { false, "script", Full("liftComet.lift_handlerSuccessFunc"), - Full("liftComet.lift_handlerFailureFunc"), false)) + + Full("liftComet.lift_handlerFailureFunc"))) + """ } } diff --git a/web/webkit/src/main/scala/net/liftweb/http/js/extcore/ExtCoreArtifacts.scala b/web/webkit/src/main/scala/net/liftweb/http/js/extcore/ExtCoreArtifacts.scala index b1de61371d..ee36f9d709 100755 --- a/web/webkit/src/main/scala/net/liftweb/http/js/extcore/ExtCoreArtifacts.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/js/extcore/ExtCoreArtifacts.scala @@ -148,7 +148,7 @@ object ExtCoreArtifacts extends JSArtifacts { } private def toJson(info: AjaxInfo, server: String, path: String => JsExp): String = - (("url : liftAjax.addPageNameAndVersion(" + path(server).toJsCmd + ")" ) :: + (("url : liftAjax.addPageName(" + path(server).toJsCmd + ")" ) :: "params : " + info.data.toJsCmd :: ("method : " + info.action.encJs) :: ("dataType : " + info.dataType.encJs) :: diff --git a/web/webkit/src/main/scala/net/liftweb/http/js/jquery/JQueryArtifacts.scala b/web/webkit/src/main/scala/net/liftweb/http/js/jquery/JQueryArtifacts.scala index 1b8cc1de81..346c554542 100755 --- a/web/webkit/src/main/scala/net/liftweb/http/js/jquery/JQueryArtifacts.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/js/jquery/JQueryArtifacts.scala @@ -95,15 +95,9 @@ trait JQueryArtifacts extends JSArtifacts { * attributes described by data parameter */ def ajax(data: AjaxInfo): String = { - val versionIncluder = - if (data.includeVersion) - "liftAjax.addPageNameAndVersion" - else - "liftAjax.addPageName" - "jQuery.ajax(" + toJson(data, S.contextPath, prefix => - JsRaw(versionIncluder + "(" + S.encodeURL(prefix + "/" + LiftRules.ajaxPath + "/").encJs + ")")) + ");" + JsRaw("liftAjax.addPageName(" + S.encodeURL(prefix + "/" + LiftRules.ajaxPath + "/").encJs + ")")) + ");" } /** diff --git a/web/webkit/src/main/scala/net/liftweb/http/js/yui/YUIArtifacts.scala b/web/webkit/src/main/scala/net/liftweb/http/js/yui/YUIArtifacts.scala index 2ca7011894..a3ce7214a1 100755 --- a/web/webkit/src/main/scala/net/liftweb/http/js/yui/YUIArtifacts.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/js/yui/YUIArtifacts.scala @@ -126,15 +126,9 @@ object YUIArtifacts extends JSArtifacts { * attributes described by data parameter */ def ajax(data: AjaxInfo): String = { - val versionIncluder = - if (data.includeVersion) - "liftAjax.addPageNameAndVersion" - else - "liftAjax.addPageName" - val url = S.encodeURL(S.contextPath + "/" + LiftRules.ajaxPath + "/") - "url = YAHOO.lift.buildURI(" + versionIncluder + "(" + url.encJs + ") , " + data.data.toJsCmd + ");" + + "url = YAHOO.lift.buildURI(liftAjax.addPageName(" + url.encJs + ") , " + data.data.toJsCmd + ");" + "YAHOO.util.Connect.asyncRequest(" + data.action.encJs + ", url, " + toJson(data) + ");" } From dfb39e041b59712a2203b7f8ace78386eb2b7398 Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Fri, 31 Aug 2012 14:37:36 -0400 Subject: [PATCH 0175/1949] Revert "Add !< to LiftCometActor." This reverts commit 4b61567f2ee9225e770e8e299273f75b73a22ec5. --- web/webkit/src/main/scala/net/liftweb/http/CometActor.scala | 6 ------ 1 file changed, 6 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala b/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala index a382f42ce9..6ad032fd93 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala @@ -399,12 +399,6 @@ trait LiftCometActor extends TypedActor[Any, Any] with ForwardableActor[Any, Any initCometActor(theSession, theType, name, defaultHtml, attributes) } - /** - * Asynchronous message send. Send-and-receive eventually. Returns a Future for the reply message. - */ - def !<(msg: Any): LAFuture[Any] - - /** * Override in sub-class to customise timeout for the render()-method for the specific comet */ From 564c06ff5100d86b6a62dbd562de11f1163093dd Mon Sep 17 00:00:00 2001 From: David Pollak Date: Wed, 5 Sep 2012 11:20:45 -0700 Subject: [PATCH 0176/1949] Resource files are loaded correctly for Ajax requests --- .../scala/net/liftweb/http/DefaultRoutines.scala | 2 +- .../src/main/scala/net/liftweb/http/Req.scala | 15 ++++++++++++++- .../src/main/scala/net/liftweb/http/S.scala | 15 ++++++++++++++- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/DefaultRoutines.scala b/web/webkit/src/main/scala/net/liftweb/http/DefaultRoutines.scala index 96b7ac7b6d..2b9c2bc342 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/DefaultRoutines.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/DefaultRoutines.scala @@ -98,7 +98,7 @@ object DefaultRoutines { val loc = S.locale val cb = for { - req <- S.request + req <- S.originalRequest path = req.path.partPath.dropRight(1) ::: req.path.partPath.takeRight(1).map(s => "_resources_"+s) bundle <- resBundleFor(loc, path) diff --git a/web/webkit/src/main/scala/net/liftweb/http/Req.scala b/web/webkit/src/main/scala/net/liftweb/http/Req.scala index 9caa19d91d..60146cc915 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Req.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Req.scala @@ -856,6 +856,14 @@ class Req(val path: ParsePath, (header.values.exists(_ equalsIgnoreCase "xmlhttprequest")) } + /** + * A request that is neither Ajax or Comet + */ + lazy val standardRequest_? : Boolean = path.partPath match { + case x :: _ if x == LiftRules.ajaxPath || x == LiftRules.cometPath => false + case _ => true + } + /** * Make the servlet session go away */ @@ -865,7 +873,12 @@ class Req(val path: ParsePath, } httpReq.destroyServletSession() } - def snapshot = { + /** + * A snapshot of the request that can be passed off the current thread + * + * @return a copy of the Req + */ + def snapshot: Req = { val paramCalc = paramCalculator() paramCalc.body.map(_.body) // make sure we grab the body new Req(path, diff --git a/web/webkit/src/main/scala/net/liftweb/http/S.scala b/web/webkit/src/main/scala/net/liftweb/http/S.scala index 31d9b250b1..9f89071371 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/S.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/S.scala @@ -332,6 +332,7 @@ trait S extends HasParams with Loggable { private val autoCleanUp = new ThreadGlobal[Boolean] private val _oneShot = new ThreadGlobal[Boolean] private val _disableTestFuncNames = new ThreadGlobal[Boolean] + private object _originalRequest extends RequestVar[Box[Req]](Empty) private object _exceptionThrown extends TransientRequestVar(false) @@ -379,6 +380,15 @@ trait S extends HasParams with Loggable { */ def request: Box[Req] = (Box !! _request.value) or CurrentReq.box + + /** + * If this is an Ajax request, return the original request that created the page. The original + * request is useful because it has the original path which is helpful for localization purposes. + * + * @return the original request or if that's not available, the current request + */ + def originalRequest: Box[Req] = _originalRequest.get or request + private[http] object CurrentLocation extends RequestVar[Box[sitemap.Loc[_]]](request.flatMap(_.location)) def location: Box[sitemap.Loc[_]] = CurrentLocation.is or { @@ -1616,7 +1626,10 @@ for { _attrs.doWith((Null,Nil)) { _resBundle.doWith(Nil) { inS.doWith(true) { - withReq(doStatefulRewrite(request)) { + val statefulRequest = doStatefulRewrite(request) + withReq(statefulRequest) { + _originalRequest.set(Full(statefulRequest)) + _nest2InnerInit(f) } } From 74abc3a3aebff78dbd523158a14e5c48f14120ef Mon Sep 17 00:00:00 2001 From: David Pollak Date: Wed, 5 Sep 2012 11:26:12 -0700 Subject: [PATCH 0177/1949] Resource files are loaded correctly for Ajax requests --- web/webkit/src/main/scala/net/liftweb/http/Req.scala | 4 +++- web/webkit/src/main/scala/net/liftweb/http/S.scala | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/Req.scala b/web/webkit/src/main/scala/net/liftweb/http/Req.scala index 60146cc915..109641ae56 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Req.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Req.scala @@ -522,7 +522,9 @@ object Req { */ def nil = new Req(NilPath, "", GetRequest, Empty, null, System.nanoTime, System.nanoTime, false, - () => ParamCalcInfo(Nil, Map.empty, Nil, Empty), Map()) + () => ParamCalcInfo(Nil, Map.empty, Nil, Empty), Map()) { + override lazy val standardRequest_? = false + } def parsePath(in: String): ParsePath = { val p1 = fixURI((in match {case null => "/"; case s if s.length == 0 => "/"; case s => s}).replaceAll("/+", "/")) diff --git a/web/webkit/src/main/scala/net/liftweb/http/S.scala b/web/webkit/src/main/scala/net/liftweb/http/S.scala index 9f89071371..d737d6a332 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/S.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/S.scala @@ -1628,7 +1628,8 @@ for { inS.doWith(true) { val statefulRequest = doStatefulRewrite(request) withReq(statefulRequest) { - _originalRequest.set(Full(statefulRequest)) + // set the request for standard requests + if (statefulRequest.standardRequest_?) _originalRequest.set(Full(statefulRequest)) _nest2InnerInit(f) } From e175de601bef9be9fb7f0cb84fd82efc3fb31022 Mon Sep 17 00:00:00 2001 From: David Pollak Date: Thu, 6 Sep 2012 10:12:18 -0700 Subject: [PATCH 0178/1949] Fixed a missed implicit conversion issue a NodeSeq is a NodeSeq not an Iterable[NodeSeq] --- .../main/scala/net/liftweb/util/CssSel.scala | 158 ++-- .../net/liftweb/util/BindHelpersSpec.scala | 750 ---------------- .../net/liftweb/util/CssSelectorSpec.scala | 810 +++++++++++++++++- 3 files changed, 878 insertions(+), 840 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/CssSel.scala b/core/util/src/main/scala/net/liftweb/util/CssSel.scala index e3d6ab0fe2..7c30b3fbf4 100644 --- a/core/util/src/main/scala/net/liftweb/util/CssSel.scala +++ b/core/util/src/main/scala/net/liftweb/util/CssSel.scala @@ -62,9 +62,10 @@ object ClearNodes extends Function1[NodeSeq, NodeSeq] { } - private final case class AggregatedCssBindFunc(binds: List[CssBind]) extends CssSel { - private lazy val (good, bad) = binds.partition{_.css.isDefined} + private lazy val (good, bad) = binds.partition { + _.css.isDefined + } private lazy val selectorMap = new SelectorMap(good) def apply(in: NodeSeq): NodeSeq = bad match { @@ -95,7 +96,7 @@ private class SelectorMap(binds: List[CssBind]) extends Function1[NodeSeq, NodeS // The KidsSubNode always has to go last or else we // get into an issue where we're trying to apply the whole // transform to the whole shooting match - private def sortBinds(lst: List[CssBind]): List[CssBind] = { + private def sortBinds(lst: List[CssBind]): List[CssBind] = { lst.sortWith { case (SubNode(me: EmptyBox), SubNode(_)) => true case (SubNode(_), SubNode(them: EmptyBox)) => false @@ -111,7 +112,7 @@ private class SelectorMap(binds: List[CssBind]) extends Function1[NodeSeq, NodeS } private val (idMap, nameMap, clzMap, attrMap, elemMap, - starFunc, selectThis: Box[CssBind]) = { + starFunc, selectThis: Box[CssBind]) = { var idMap: Map[String, List[CssBind]] = Map() var nameMap: Map[String, List[CssBind]] = Map() var clzMap: Map[String, List[CssBind]] = Map() @@ -121,29 +122,29 @@ private class SelectorMap(binds: List[CssBind]) extends Function1[NodeSeq, NodeS val selThis: Box[CssBind] = binds.flatMap { b => - b.css.open_!.subNodes match { + b.css.openOrThrowException("Guarded with test before calling this method").subNodes match { case Full(SelectThisNode(_)) => List(b) case _ => Nil } }.headOption binds.foreach { - case i @ CssBind(IdSelector(id, _)) => + case i@CssBind(IdSelector(id, _)) => idMap += (id -> sortBinds(i :: idMap.getOrElse(id, Nil))) - case i @ CssBind(ElemSelector(id, _)) => + case i@CssBind(ElemSelector(id, _)) => elemMap += (id -> sortBinds(i :: elemMap.getOrElse(id, Nil))) - case i @ CssBind(StarSelector(_)) => starFunc = Full(sortBinds(i :: starFunc.openOr(Nil))) + case i@CssBind(StarSelector(_)) => starFunc = Full(sortBinds(i :: starFunc.openOr(Nil))) - case i @ CssBind(NameSelector(name, _)) => + case i@CssBind(NameSelector(name, _)) => nameMap += (name -> sortBinds(i :: nameMap.getOrElse(name, Nil))) - case i @ CssBind(ClassSelector(clz, _)) => + case i@CssBind(ClassSelector(clz, _)) => clzMap += (clz -> sortBinds(i :: clzMap.getOrElse(clz, Nil))) - case i @ CssBind(AttrSelector(name, value, _)) => { + case i@CssBind(AttrSelector(name, value, _)) => { val oldMap = attrMap.getOrElse(name, Map()) attrMap += (name -> (oldMap + (value -> sortBinds(i :: oldMap.getOrElse(value, Nil))))) } @@ -158,8 +159,9 @@ private class SelectorMap(binds: List[CssBind]) extends Function1[NodeSeq, NodeS case ns => ns } - private abstract class SlurpedAttrs(val id: Box[String],val name: Box[String]) { + private abstract class SlurpedAttrs(val id: Box[String], val name: Box[String]) { def attrs: Map[String, String] + def classes: List[String] def removeId(in: MetaData) = in.filter { @@ -168,7 +170,7 @@ private class SelectorMap(binds: List[CssBind]) extends Function1[NodeSeq, NodeS } private final def isSelThis(bind: CssBind): Boolean = - bind.css.open_!.subNodes match { + bind.css.openOrThrowException("Guarded with test before calling this method").subNodes match { case Full(SelectThisNode(_)) => true case _ => false } @@ -191,11 +193,12 @@ private class SelectorMap(binds: List[CssBind]) extends Function1[NodeSeq, NodeS } final def applyAttributeRules(bindList: List[CssBind], elem: Elem): Elem = { - bindList.map(b => (b, b.css.open_!.subNodes.open_!)). - foldLeft(elem){ + bindList.map(b => (b, b.css.openOrThrowException("Guarded with test before calling this method"). + subNodes.openOrThrowException("Guarded with test before calling this method"))). + foldLeft(elem) { case (elem, (bind, AttrSubNode(attr))) => { val calced = bind.calculate(elem).map(findElemIfThereIsOne _) - val filtered = elem.attributes.filter{ + val filtered = elem.attributes.filter { case up: UnprefixedAttribute => up.key != attr case _ => true } @@ -209,7 +212,7 @@ private class SelectorMap(binds: List[CssBind]) extends Function1[NodeSeq, NodeS new Elem(elem.prefix, elem.label, newAttr, - elem.scope, elem.child :_*) + elem.scope, elem.child: _*) } case (elem, (bind, AttrAppendSubNode(attr))) => { @@ -220,7 +223,7 @@ private class SelectorMap(binds: List[CssBind]) extends Function1[NodeSeq, NodeS if (calced.isEmpty) { elem } else { - val filtered = elem.attributes.filter{ + val filtered = elem.attributes.filter { case up: UnprefixedAttribute => up.key != attr case _ => true } @@ -242,7 +245,7 @@ private class SelectorMap(binds: List[CssBind]) extends Function1[NodeSeq, NodeS new Elem(elem.prefix, elem.label, newAttr, - elem.scope, elem.child :_*) + elem.scope, elem.child: _*) } } @@ -251,16 +254,17 @@ private class SelectorMap(binds: List[CssBind]) extends Function1[NodeSeq, NodeS val org: NodeSeq = elem.attribute(attr).getOrElse(NodeSeq.Empty) val calced = bind.calculate(elem).toList.map(findElemIfThereIsOne _) - if (calced.isEmpty || org.isEmpty) { // if either is empty, then return the Elem unmodified + if (calced.isEmpty || org.isEmpty) { + // if either is empty, then return the Elem unmodified elem } else { - val filtered = elem.attributes.filter{ + val filtered = elem.attributes.filter { case up: UnprefixedAttribute => up.key != attr case _ => true } val flat: Box[NodeSeq] = if (attr == "class") { - val set = Set(calced.map(_.text) :_*) + val set = Set(calced.map(_.text): _*) SuperString(org.text).charSplit(' ').toList. filter(_.length > 0).filter(s => !set.contains(s)) match { case Nil => Empty @@ -277,7 +281,7 @@ private class SelectorMap(binds: List[CssBind]) extends Function1[NodeSeq, NodeS new Elem(elem.prefix, elem.label, newAttr, - elem.scope, elem.child :_*) + elem.scope, elem.child: _*) } } @@ -338,7 +342,7 @@ private class SelectorMap(binds: List[CssBind]) extends Function1[NodeSeq, NodeS } case pa: PrefixedAttribute => { - oldAttrs -= (pa.pre+":"+pa.key) + oldAttrs -= (pa.pre + ":" + pa.key) builtMeta = pa.copy(builtMeta) } case _ => @@ -364,7 +368,7 @@ private class SelectorMap(binds: List[CssBind]) extends Function1[NodeSeq, NodeS // we can do an open_! here because all the CssBind elems // have been vetted - bind.css.open_!.subNodes match { + bind.css.openOrThrowException("Guarded with test before calling this method").subNodes match { case Full(SelectThisNode(kids)) => { throw new RetryWithException(if (kids) realE.child else realE) } @@ -375,11 +379,11 @@ private class SelectorMap(binds: List[CssBind]) extends Function1[NodeSeq, NodeS case 0 => new Elem(realE.prefix, realE.label, realE.attributes, realE.scope) case 1 => new Elem(realE.prefix, realE.label, realE.attributes, realE.scope, - todo.transform(realE.child, calced.head) :_*) + todo.transform(realE.child, calced.head): _*) case _ if id.isEmpty => calced.map(kids => new Elem(realE.prefix, realE.label, realE.attributes, realE.scope, - todo.transform(realE.child, kids) :_*)) + todo.transform(realE.child, kids): _*)) case _ => { val noId = removeId(realE.attributes) @@ -387,11 +391,11 @@ private class SelectorMap(binds: List[CssBind]) extends Function1[NodeSeq, NodeS case (kids, 0) => new Elem(realE.prefix, realE.label, realE.attributes, realE.scope, - todo.transform(realE.child, kids) :_*) + todo.transform(realE.child, kids): _*) case (kids, _) => new Elem(realE.prefix, realE.label, noId, realE.scope, - todo.transform(realE.child, kids) :_*) + todo.transform(realE.child, kids): _*) } } } @@ -407,7 +411,7 @@ private class SelectorMap(binds: List[CssBind]) extends Function1[NodeSeq, NodeS case Group(g) => g case e: Elem => new Elem(e.prefix, e.label, mergeAll(e.attributes, false, x == Full(DontMergeAttributes)), - e.scope, e.child :_*) + e.scope, e.child: _*) case x => x } } @@ -415,20 +419,25 @@ private class SelectorMap(binds: List[CssBind]) extends Function1[NodeSeq, NodeS case n => { val calcedList = calced.toList val availableIds = (attrs.get("id").toList ++ - calcedList.collect({ case e:Elem => e.attribute("id") }).flatten.map(_.toString)).toSet - val merged = calcedList.foldLeft((availableIds, Nil: List[Seq[xml.Node]])) { (idsAndResult, a) => - val (ids, result) = idsAndResult - a match { - case Group(g) => (ids, g :: result) - case e:Elem => { - val targetId = e.attribute("id").map(_.toString) orElse (attrs.get("id")) - val keepId = targetId map { id => ids.contains(id) } getOrElse (false) - val newIds = targetId filter (_ => keepId) map (i => ids - i) getOrElse (ids) - val newElem = new Elem(e.prefix, e.label, mergeAll(e.attributes, ! keepId, x == Full(DontMergeAttributes)), e.scope, e.child: _*) - (newIds, newElem :: result) + calcedList.collect({ + case e: Elem => e.attribute("id") + }).flatten.map(_.toString)).toSet + val merged = calcedList.foldLeft((availableIds, Nil: List[Seq[xml.Node]])) { + (idsAndResult, a) => + val (ids, result) = idsAndResult + a match { + case Group(g) => (ids, g :: result) + case e: Elem => { + val targetId = e.attribute("id").map(_.toString) orElse (attrs.get("id")) + val keepId = targetId map { + id => ids.contains(id) + } getOrElse (false) + val newIds = targetId filter (_ => keepId) map (i => ids - i) getOrElse (ids) + val newElem = new Elem(e.prefix, e.label, mergeAll(e.attributes, !keepId, x == Full(DontMergeAttributes)), e.scope, e.child: _*) + (newIds, newElem :: result) + } + case x => (ids, x :: result) } - case x => (ids, x :: result) - } } merged._2.reverse.flatten } @@ -517,7 +526,7 @@ private class SelectorMap(binds: List[CssBind]) extends Function1[NodeSeq, NodeS } case pa: PrefixedAttribute if (null ne pa.value) => { - theAttrs += ((pa.pre+":"+pa.key) -> pa.value.text) + theAttrs += ((pa.pre + ":" + pa.key) -> pa.value.text) } case _ => @@ -528,6 +537,7 @@ private class SelectorMap(binds: List[CssBind]) extends Function1[NodeSeq, NodeS new SlurpedAttrs(id, name) { def attrs: Map[String, String] = theAttrs + def classes: List[String] = clzs } } @@ -552,20 +562,21 @@ private class SelectorMap(binds: List[CssBind]) extends Function1[NodeSeq, NodeS case csb :: _ => throw new RetryWithException(if (csb.selectThisChildren_?) - e.child else e) + e.child + else e) } } else { - lb.toList.filterNot(_.selectThis_?) match { + lb.toList.filterNot(_.selectThis_?) match { case Nil => new Elem(e.prefix, e.label, - e.attributes, e.scope, run(e.child, onlySel) :_*) + e.attributes, e.scope, run(e.child, onlySel): _*) case csb => // do attributes first, then the body csb.partition(_.attrSel_?) match { - case (Nil, rules) => slurp.applyRule(rules, e, onlySel) + case (Nil, rules) => slurp.applyRule(rules, e, onlySel) case (attrs, Nil) => { val elem = slurp.applyAttributeRules(attrs, e) new Elem(elem.prefix, elem.label, - elem.attributes, elem.scope, run(elem.child, onlySel) :_*) + elem.attributes, elem.scope, run(elem.child, onlySel): _*) } case (attrs, rules) => { @@ -608,16 +619,19 @@ object CssBind { trait CssBind extends CssSel { def stringSelector: Box[String] + def css: Box[CssSelector] - override def toString(): String = "CssBind("+stringSelector+", "+ - css+")" + override def toString(): String = "CssBind(" + stringSelector + ", " + + css + ")" def apply(in: NodeSeq): NodeSeq = css match { case Full(c) => selectorMap(in) case _ => Helpers.errorDiv(
    - Syntax error in CSS selector definition: {stringSelector openOr "N/A"}. + Syntax error in CSS selector definition: + {stringSelector openOr "N/A"} + . The selector will not be applied.
    ) openOr NodeSeq.Empty } @@ -686,8 +700,11 @@ final class CssJBridge { new ToCssBindPromoter(Full(str), CssSelectorParser.parse(str)) def sel(selector: String, value: String): CssSel = selector #> value + def sel(selector: String, value: NodeSeq): CssSel = selector #> value + def sel(selector: String, value: NodeSeq => NodeSeq): CssSel = selector #> value + def sel(selector: String, value: Bindable): CssSel = selector #> value /** @@ -708,6 +725,7 @@ final class CssJBridge { } trait ComputeTransformRules[-T] { + def name: String = "ComputeTransformRule" def computeTransform(it: => T, ns: NodeSeq): Seq[NodeSeq] } @@ -737,7 +755,6 @@ object ComputeTransformRules { } - implicit def jsCmdPairTransform: ComputeTransformRules[(_, ToJsCmd)] = new ComputeTransformRules[(_, ToJsCmd)] { def computeTransform(str: => (_, ToJsCmd), ns: NodeSeq): Seq[NodeSeq] = List(Text(str._2.toJsCmd)) } @@ -763,8 +780,8 @@ object ComputeTransformRules { } - - implicit def nodeSeqTransform[T](implicit f : T => NodeSeq): ComputeTransformRules[T] = new ComputeTransformRules[T] { + implicit def toNodeSeqTransform[T](implicit f: T => NodeSeq): ComputeTransformRules[T] = new ComputeTransformRules[T] { + override def name: String = "def toNodeSeqTransform[T]" def computeTransform(param: => T, ns: NodeSeq): Seq[NodeSeq] = List(f(param)) } @@ -780,9 +797,17 @@ object ComputeTransformRules { def computeTransform(func: => NodeSeq => Node, ns: NodeSeq): Seq[NodeSeq] = List(func(ns)) } + implicit def iterableNodeTransform[NST](implicit f2: NST => NodeSeq): ComputeTransformRules[Iterable[NST]] = new ComputeTransformRules[Iterable[NST]] { - def computeTransform(info: => Iterable[NST], ns: NodeSeq): Seq[NodeSeq] = Helpers.ensureUniqueId(info.toSeq.map(f2)) + def computeTransform(info: => Iterable[NST], ns: NodeSeq): Seq[NodeSeq] = + { + val i = info + i match { + case ns: NodeSeq => List(ns) + case x => Helpers.ensureUniqueId(x.toSeq.map(f2)) + } + } } implicit def boxNodeTransform[NST](implicit f2: NST => NodeSeq): ComputeTransformRules[Box[NST]] = @@ -813,7 +838,7 @@ object ComputeTransformRules { implicit def iterableStringPromotableTransform[T[_], PM](implicit f: T[PM] => Iterable[PM], - prom: PM => StringPromotable ): + prom: PM => StringPromotable): ComputeTransformRules[T[PM]] = new ComputeTransformRules[T[PM]] { def computeTransform(info: => T[PM], ns: NodeSeq): Seq[NodeSeq] = f(info).toSeq.map(a => Text(prom(a).toString)) @@ -824,14 +849,14 @@ object ComputeTransformRules { def computeTransform(info: => T[F], ns: NodeSeq): Seq[NodeSeq] = Helpers.ensureUniqueId(f(info).toSeq.map(_.apply(ns))) } - implicit def funcIterableTransform[T[_], F <: NodeSeq](implicit f: T[F] => Iterable[F]): ComputeTransformRules[ NodeSeq => T[F]] = - new ComputeTransformRules[ NodeSeq => T[F]] { - def computeTransform(info: => NodeSeq => T[F], ns: NodeSeq): Seq[NodeSeq] = Helpers.ensureUniqueId(f(info(ns)).toSeq) + implicit def funcIterableTransform[T[_], F <: NodeSeq](implicit f: T[F] => Iterable[F]): ComputeTransformRules[NodeSeq => T[F]] = + new ComputeTransformRules[NodeSeq => T[F]] { + def computeTransform(info: => NodeSeq => T[F], ns: NodeSeq): Seq[NodeSeq] = Helpers.ensureUniqueId(f(info(ns)).toSeq) } implicit def stringFuncTransform: ComputeTransformRules[NodeSeq => String] = - new ComputeTransformRules[ NodeSeq => String] { - def computeTransform(info: => NodeSeq => String, ns: NodeSeq): Seq[NodeSeq] = List(Text(info(ns))) + new ComputeTransformRules[NodeSeq => String] { + def computeTransform(info: => NodeSeq => String, ns: NodeSeq): Seq[NodeSeq] = List(Text(info(ns))) } implicit def stringIterFuncTransform[T[_]](implicit f: T[String] => Iterable[String]): ComputeTransformRules[NodeSeq => T[String]] = @@ -861,15 +886,18 @@ final case class ToCssBindPromoter(stringSelector: Box[String], css: Box[CssSele * @tparam T the type of it * @return the function that will transform an incoming DOM based on the transform rules */ - def #>[T](it: => T)(implicit computer: ComputeTransformRules[T]): CssSel = css match { + def #>[T](it: => T)(implicit computer: ComputeTransformRules[T]): CssSel = { + css match { case Full(EnclosedSelector(a, b)) => null - (ToCssBindPromoter(stringSelector, Full(a))).#>(nsFunc(ns =>{ - ToCssBindPromoter(stringSelector, Full(b)).#>(it)(computer)(ns)})) // (ComputeTransformRules.nodeSeqFuncTransform) + (ToCssBindPromoter(stringSelector, Full(a))).#>(nsFunc(ns => { + ToCssBindPromoter(stringSelector, Full(b)).#>(it)(computer)(ns) + })) // (ComputeTransformRules.nodeSeqFuncTransform) case _ => new CssBindImpl(stringSelector, css) { def calculate(in: NodeSeq): Seq[NodeSeq] = computer.computeTransform(it, in) } } + } /** * Transform a DOM (NodeSeq) based on rules diff --git a/core/util/src/test/scala/net/liftweb/util/BindHelpersSpec.scala b/core/util/src/test/scala/net/liftweb/util/BindHelpersSpec.scala index 5bdd956f3e..93990d5e85 100644 --- a/core/util/src/test/scala/net/liftweb/util/BindHelpersSpec.scala +++ b/core/util/src/test/scala/net/liftweb/util/BindHelpersSpec.scala @@ -347,753 +347,3 @@ object BindHelpersSpec extends Specification { } -object CssBindHelpersSpec extends Specification { - - "css bind helpers" should { - "clear clearable" in { - ClearClearable() must ==/ () - } - - "substitute a String by id" in { - ("#foo" #> "hello").apply() must ==/ (hello) - } - - - "not duplicate classes" in { - - def anchor(quesType: String, value: String) = { - (value) - } - var page = 1 - var elements = List("1","2","3","4") - - val xml =
    -
    - 1 - 1 - 1 -
    - -
    - - val sel = ".question" #> elements.map(value => { - ".question [id]" #> ("question-" + value) & - ".question [class]" #> ("question-" + value) & - ".L" #> anchor("L", value) & - ".U" #> anchor("U", value) & - ".D" #> anchor("D", value) - }) - - val res = sel(xml) - - ((res \\ "a").head \ "@class").head.text must_== "selected L" - } - - - "Compound selector" in { - val res = - (".foo [href]" #> "http://dog.com" & ".bar [id]" #> "moo").apply( - ) - (res \ "@href").text must_== "http://dog.com" - (res \ "@id").text must_== "moo" - } - - "not stack overflow on Elem" in { - val xf = "* [id]" #> "xx" & - "* [style]" #> "border:thin solid black" & - "* *" #> - success - } - - "not stack overflow on Elem" in { - val xf = "* [id]" #> "xx" & - "* [style]" #> "border:thin solid black" & - "* *+" #> - - xf(
    ) - success - } - - "not stack overflow on Elem" in { - val xf = "* [id]" #> "xx" & - "* [style]" #> "border:thin solid black" & - "* -*" #> - - xf(
    ) - success - } - - "support modifying attributes along with body" in { - val org = foo - val func = "a [href]" #> "dog" & "a *" #> "bar" - val res = func(org) - - res.toString must_== "bar" - } - - "substitute a String by id" in { - ("#foo" replaceWith "hello").apply() must ==/ (hello) - } - - "substitute a String by nested class" in { - ("div .foo" #> "hello").apply(
    ) must ==/ (
    hello
    ) - } - - "substitute a String by deep nested class" in { - ("#baz div .foo" #> "hello").apply( -
    ) must ==/ (
    hello
    ) - } - - "insert a String by deep nested class" in { - ("#baz div .foo *" #> "hello").apply( -
    ) must ==/ (
    hello
    ) - } - - - - "Select a node" in { - ("#foo ^^" #> "hello").apply(
    ) must ==/ () - } - - "Another nested select" in { - val template = -
    - - -

    -
    -
    - - -

    -
    -
    - - val xf = "#get ^^" #> "ignore" & "#file_upload" #> - - val ret = xf(template) - - ret(0).asInstanceOf[Elem].label must_== "div" - ret.length must_== 1 - (ret \ "@id").text must_== "get" - - (ret \\ "input").length must_== 2 - - ((ret \\ "input").toList(0) \ "@type").map(_.text) must_== List("moose") - - } - - "Child nested select" in { - val template = -
    - - -

    -
    -
    - - -

    -
    -
    - - val xf = "#get ^*" #> "ignore" & "#file_upload" #> - - val ret = xf(template) - - (ret \\ "div").length must_== 0 - - (ret \\ "input").length must_== 2 - - ((ret \\ "input").toList(0) \ "@type").map(_.text) must_== List("moose") - - } - - "Select a node and transform stuff" in { - val ret = ("#foo ^^" #> "hello" & - "span [id]" #> "bar")() - - ret(0).asInstanceOf[Elem].label must_== "span" - ret.length must_== 1 - (ret \ "@id").text must_== "bar" - } - - - "Select a node and transform stuff deeply nested" in { - val ret = ("#foo ^^" #> "hello" & - "span [id]" #> "bar")(
    ) - - ret(0).asInstanceOf[Elem].label must_== "span" - ret.length must_== 1 - (ret \ "@id").text must_== "bar" - } - - - "Select a node and transform stuff deeply nested 2" in { - val ret = ("#foo ^^" #> "hello" & - "span [id]" #> "bar")(
    ) - - ret(0).asInstanceOf[Elem].label must_== "span" - ret.length must_== 1 - (ret \ "@id").text must_== "bar" - (ret \ "@dog").text must_== "woof" - } - - - - "substitute multiple Strings by id" in { - ("#foo" #> "hello" & - "#baz" #> "bye" - )(
    Hello
    ) must be_== (NodeSeq fromSeq {Text("bye")}{Text("hello")}) - } - - "bind href and None content" in { - val opt: Option[String] = None - val res = ("top *" #> opt & - "top [href]" #> "frog")(cat) - - res.text must_== "" - (res \ "@href").text.mkString must_== "frog" - } - - "bind href and Some content" in { - val opt: Option[String] = Some("Dog") - val res = ("top *" #> opt & - "top [href]" #> "frog")(cat) - - res.text must_== "Dog" - (res \ "@href").text.mkString must_== "frog" - } - - "bind href and Some content with multiple attrs" in { - val opt: Option[String] = Some("Dog") - val res = ("top *" #> opt & - "top [meow]" #> "woof" & - "top [href]" #> "frog")(cat) - - res.text must_== "Dog" - (res \ "@href").text.mkString must_== "frog" - (res \ "@meow").text.mkString must_== "woof" - } - - "option transform on *" in { - val opt: Option[String] = None - val res = ("* *" #> opt.map(ignore => "Dog")).apply(cat) - res.head must_== - } - - "append attribute to a class with spaces" in { - val stuff = List("a", "b") - val res = ("* [class+]" #> stuff).apply(cat) - (res \ "@class").text must_== "q a b" - } - - "append attribute to an href" in { - val stuff = List("&a=b", "&b=d") - val res = ("* [href+]" #> stuff).apply(cat) - (res \ "@href").text must_== "q?z=r&a=b&b=d" - } - - "remove an attribute from a class" in { - val func = ".foo [class!]" #> "andOther" - - (func() \ "@class").text must_== "foo" - } - - "remove an attribute from a class and the attribute if it's the only one left" in { - val func = ".foo [class!]" #> "foo" - val res = func() - - (res \ "@class").length must_== 0 - } - - - - "Remove a subnode's class attribute" in { - - val func = ".removeme !!" #> ("td [class!]" #> "removeme") - val res = func.apply(Hi) - - ((res \ "td") \ "@class").text must_== "fish" - } - - - "not remove a non-existant class" in { - val func = ".foo [class!]" #> "bar" - val res = func() - - (res \ "@class").text must_== "foo" - } - - - "remove an attribute from an attribute" in { - val func = "span [href!]" #> "foo" - val res = func() - - (res \ "@href").length must_== 0 - } - - - "not remove a non-existant href" in { - val func = "span [href!]" #> "bar" - val res = func() - - (res \ "@href").text must_== "foo bar" - } - - "option transform on *" in { - val opt: Option[Int] = Full(44) - val res = ("* *" #> opt.map(ignore => "Dog")).apply(cat) - res must ==/ (Dog) - } - - - "Java number support" in { - val f = "a *" #> Full(new java.lang.Long(12)) - val xml = Hello - - f(xml) must ==/ (12) - } - - - "Surround kids" in { - val f = "a <*>" #>
    - val xml = Meow Cat woof - - f(xml) must ==/ (Meow
    Cat
    woof
    ) - } - - "Andreas's thing doesn't blow up" in { - def cachedMessageList: Box[Box[String]] = Empty - - def messageListId = "Hello" - - def collapseUnless[A](isEmptyCond: Boolean)(f: => A): Box[A] = { - if (!isEmptyCond) { - Empty - } else { - Full(f) - } - } - - ".noMail" #> collapseUnless(cachedMessageList.map(_.isEmpty).openOr(true)) { - "tbody [id]" #> messageListId & - "*" #> PassThru - } - - true must_== true - } - - "other Andreas test" in { - def renderBlogEntrySummary = { - ".blogEntry" #> ((ns: NodeSeq) => { - ("*" #> "Horse").apply(ns) - }) - } - - - - def render = { - - "*" #> ((ns: NodeSeq) => - renderBlogEntrySummary.apply(ns) ++ hi - ) - } - - render - - true must_== true - } - - - "option transform on *" in { - val opt: Box[String] = Empty - val res = ("* *" #> opt.map(ignore => "Dog")).apply(cat) - res.head must_== - } - - "option transform on *" in { - val opt: Box[Int] = Some(44) - val res = ("* *" #> opt.map(ignore => "Dog")).apply(cat) - res must ==/ (Dog) - } - - "transform on *" in { - val res = ("* *" #> "Dog").apply(cat) - res must ==/ (Dog) - } - - "transform child content on *+" in { - val res = ("* *+" #> "moose").apply(I like ) - res.text must_== "I like moose" - } - - "transform child content on -*" in { - val res = ("* -*" #> "moose").apply( I like) - res.text must_== "moose I like" - } - - "transform on li" in { - val res = ("li *" #> List("Woof", "Bark") & ClearClearable)( -
    • meow
    • a
    • a
    ) - res must ==/ (
    • Woof
    • Bark
    ) - } - - "substitute multiple Strings by id" in { - (("#foo" replaceWith "hello") & - ("#baz" replaceWith "bye") - )( -
    Hello
    - ) must_== (NodeSeq fromSeq {Text("bye")}{Text("hello")}) - } - - "substitute multiple Strings with a List by id" in { - ("#foo" #> "hello" & - "#baz" #> List("bye", "bye"))(
    Hello
    ) must_== (NodeSeq fromSeq {Text("bye")}{Text("bye")}{Text("hello")}) - } - - "substitute multiple Strings with a List by id" in { - (("#foo" replaceWith "hello") & - ("#baz" replaceWith List("bye", "bye")))(
    Hello
    ) must_== (NodeSeq fromSeq {Text("bye")}{Text("bye")}{Text("hello")}) - } - - - "substitute multiple Strings with a List of XML by id" in { - val answer = ("#foo" #> "hello" & - "#baz" #> List[NodeSeq](, Meow))(
    Hello
    ) - - (answer \ "i").length must_== 2 - (answer \ "i")(0) must ==/ () - (answer \ "i")(1) must ==/ (Meow) - } - - "substitute multiple Strings with a List of XML by id" in { - val answer = (("#foo" replaceWith "hello") & - ("#baz" replaceWith List[NodeSeq](, Meow)))(
    Hello
    ) - - (answer \ "i").length must_== 2 - (answer \ "i")(0) must ==/ () - (answer \ "i")(1) must ==/ (Meow) - } - - "substitute by name" in { - val answer = ("name=moose" #> ).apply ( -
    ) - - (answer \ "input")(0) must ==/ () - } - - "substitute by name" in { - val answer = ("name=moose" replaceWith ).apply ( -
    ) - - (answer \ "input")(0) must ==/ () - } - - - "substitute by name with attrs" in { - val answer = ("name=moose" #> ).apply ( -
    ) - - (answer \ "input")(0) must ==/ () - } - - "substitute by name with attrs" in { - val answer = ("name=moose" replaceWith ).apply ( -
    ) - - (answer \ "input")(0) must ==/ () - } - - - "substitute by a selector with attrs" in { - val answer = ("cute=moose" #> ).apply ( -
    ) - - (answer \ "input")(0) must ==/ () - } - - "substitute by a selector with attrs" in { - val answer = ("cute=moose" replaceWith ).apply ( -
    ) - - (answer \ "input")(0) must ==/ () - } - - "Map of funcs" in { - val func: NodeSeq => NodeSeq = "#horse" #> List(1,2,3).map(".item *" #> _) - val answer: NodeSeq = func(
    frogi
    ) - - answer must ==/ (
    frog1
    frog2
    frog3
    ) - - } - - "maintain unique id attributes provided by transform" in { - val func = ".thinglist *" #> - (".thing" #> List("xx1", "xx2", "xx2", "xx2", "xx4").map(t => { - ".thing [id]" #> t - }) - ) - val answer = func(
    ) - - answer must ==/ (
    ) - } - - "merge classes" in { - val answer = ("cute=moose" #> ).apply ( -
    ) - - (answer \ "input")(0) must ==/ () - } - - - "merge classes" in { - val answer = ("cute=moose" replaceWith ).apply ( -
    ) - - (answer \ "input")(0) must ==/ () - } - - - - - "list of strings" in { - val answer = ("#moose *" #> List("a", "b", "c", "woof") & - ClearClearable).apply ( -
      -
    • first
    • -
    • second
    • -
    • Third
    • -
    ) - - val lis = (answer \ "li").toList - - lis.length must_== 4 - - lis(0) must ==/ (
  • a
  • ) - lis(3) must ==/ (
  • woof
  • ) - } - - - "list of Nodes" in { - val answer = ("#moose *" #> List[NodeSeq]("a", Text("b"), Text("c"), woof) & - ClearClearable).apply ( -
      -
    • first
    • -
    • second
    • -
    • Third
    • -
    ) - - val lis = (answer \ "li").toList - - lis.length must_== 4 - - lis(0) must ==/ (
  • "a"
  • ) - lis(3) must ==/ (
  • woof
  • ) - } - - - "set href" in { - val answer = ("#moose [href]" #> "Hi" & - ClearClearable).apply ( - ) - - - (answer \ "a" \ "@href").text must_== "Hi" - (answer \ "li").length must_== 0 - } - - "set href and subnodes" in { - val answer = ("#moose [href]" #> "Hi" & - ClearClearable).apply ( - ) - - - (answer \ "a" \ "@href").text must_== "Hi" - (answer \\ "li").length must_== 0 - } - - - "list of strings" in { - val answer = (("#moose *" replaceWith List("a", "b", "c", "woof")) & - ClearClearable).apply ( -
      -
    • first
    • -
    • second
    • -
    • Third
    • -
    ) - - val lis = (answer \ "li").toList - - lis.length must_== 4 - - lis(0) must ==/ (
  • a
  • ) - lis(3) must ==/ (
  • woof
  • ) - } - - "bind must bind to subnodes" in { - val html =
      -
    • - -
    • -
    - - val lst = List(1,2,3) - - val f = ".users *" #> ("li" #> lst.map(i => ".user [userid]" #> i)) - - (f(html) \\ "ul").length must_== 1 - (f(html) \\ "li").length must_== 3 - } - - "list of Nodes" in { - val answer = (("#moose *" replaceWith List[NodeSeq]("a", Text("b"), Text("c"), woof)) & - ClearClearable).apply ( -
      -
    • first
    • -
    • second
    • -
    • Third
    • -
    ) - - val lis = (answer \ "li").toList - - lis.length must_== 4 - - lis(0) must ==/ (
  • "a"
  • ) - lis(3) must ==/ (
  • woof
  • ) - } - - - "set href" in { - val answer = (("#moose [href]" replaceWith "Hi") & - ClearClearable).apply ( - ) - - - (answer \ "a" \ "@href").text must_== "Hi" - (answer \ "li").length must_== 0 - } - - "set href and subnodes" in { - val answer = (("#moose [href]" replaceWith "Hi") & - ClearClearable).apply ( - ) - - - (answer \ "a" \ "@href").text must_== "Hi" - (answer \\ "li").length must_== 0 - } - - - } -} - - -/** - * This class doesn't actually perform any tests, but insures that - * the implicit conversions work correctly - */ -object CheckTheImplicitConversionsForToCssBindPromoter { - val bog = new ToCssBindPromoter(Empty, Empty) - - import BindHelpers._ - - "foo" #> "baz" - - bog #> "Hello" - bog #> - bog #> 1 - bog #> 'foo - bog #> 44L - bog #> false - - bog #> List() - bog #> Full() - bog #> Some() - - - bog #> List("Hello") - bog #> Full("Dog") - bog #> Some("Moo") - - - bog #> List((null: Bindable)) - bog #> Full((null: Bindable)) - bog #> Some((null: Bindable)) - - bog #> nsToNs _ - bog #> nsToOptNs _ - bog #> nsToBoxNs _ - bog #> nsToSeqNs _ - - bog #> nsToString _ - bog #> nsToOptString _ - bog #> nsToBoxString _ - bog #> nsToSeqString _ - - val nsf: NodeSeq => NodeSeq = bog #> "Hello" & - bog #> & - bog #> 1 & - bog #> 'foo & - bog #> 44L & - bog #> false - - "foo" #> "Hello" - "foo" #> - "foo" #> 1 - "foo" #> 'foo - "foo" #> 44L - "foo" #> false - - "foo" #> List() - "foo" #> Full() - "foo" #> Some() - - - "foo" #> List("Hello") - "foo" #> Full("Dog") - "foo" #> Some("Moo") - - - "foo" #> List((null: Bindable)) - "foo" #> Full((null: Bindable)) - "foo" #> Some((null: Bindable)) - - "foo" #> nsToNs _ - "foo" #> nsToOptNs _ - "foo" #> nsToBoxNs _ - "foo" #> nsToSeqNs _ - - "foo" #> nsToString _ - "foo" #> nsToOptString _ - "foo" #> nsToBoxString _ - "foo" #> nsToSeqString _ - - "#foo" #> Set("a", "b", "c") - - val nsf2: NodeSeq => NodeSeq = "foo" #> "Hello" & - "foo" #> & - "foo" #> 1 & - "foo" #> 'foo & - "foo" #> 44L & - "foo" #> false - - "bar" #> List("1","2","3").map(s => "baz" #> s) - - "bar" #> Full(1).map(s => ("baz" #> s): CssBindFunc) - "bar" #> Some(1).map(s => ("baz" #> s): CssBindFunc) - - - - def nsToNs(in: NodeSeq): NodeSeq = in - def nsToOptNs(in: NodeSeq): Option[NodeSeq] = Some(in) - def nsToBoxNs(in: NodeSeq): Box[NodeSeq] = Full(in) - def nsToSeqNs(in: NodeSeq): Seq[NodeSeq] = List(in) - - def nsToString(in: NodeSeq): String = in.text - def nsToOptString(in: NodeSeq): Option[String] = Some(in.text) - def nsToBoxString(in: NodeSeq): Box[String] = Full(in.text) - def nsToSeqString(in: NodeSeq): Seq[String] = List(in.text) -} diff --git a/core/util/src/test/scala/net/liftweb/util/CssSelectorSpec.scala b/core/util/src/test/scala/net/liftweb/util/CssSelectorSpec.scala index c149680d69..a21e2e616b 100644 --- a/core/util/src/test/scala/net/liftweb/util/CssSelectorSpec.scala +++ b/core/util/src/test/scala/net/liftweb/util/CssSelectorSpec.scala @@ -20,7 +20,7 @@ package util import org.specs2.mutable.Specification import common._ - +import scala.xml._ /** * Systems under specification for CSS Selector. @@ -34,7 +34,8 @@ object CssSelectorSpec extends Specification { } "select an id" in { - CssSelectorParser.parse("#foo").open_! must_== IdSelector("foo", Empty) + CssSelectorParser.parse("#foo").openOrThrowException("If the box is empty, we want a failure") must_== + IdSelector("foo", Empty) } "a selector with cruft at the end must fail" in { @@ -46,73 +47,73 @@ object CssSelectorSpec extends Specification { } ":button must parse" in { - CssSelectorParser.parse(":button").open_! must_== + CssSelectorParser.parse(":button").openOrThrowException("If the box is empty, we want a failure") must_== AttrSelector("type", "button", Empty) } ":checkbox must parse" in { - CssSelectorParser.parse(":checkbox").open_! must_== + CssSelectorParser.parse(":checkbox").openOrThrowException("If the box is empty, we want a failure") must_== AttrSelector("type", "checkbox", Empty) } ":file must parse" in { - CssSelectorParser.parse(":file").open_! must_== + CssSelectorParser.parse(":file").openOrThrowException("If the box is empty, we want a failure") must_== AttrSelector("type", "file", Empty) } ":password must parse" in { - CssSelectorParser.parse(":password").open_! must_== + CssSelectorParser.parse(":password").openOrThrowException("If the box is empty, we want a failure") must_== AttrSelector("type", "password", Empty) } ":radio must parse" in { - CssSelectorParser.parse(":radio").open_! must_== + CssSelectorParser.parse(":radio").openOrThrowException("If the box is empty, we want a failure") must_== AttrSelector("type", "radio", Empty) } ":reset must parse" in { - CssSelectorParser.parse(":reset").open_! must_== + CssSelectorParser.parse(":reset").openOrThrowException("If the box is empty, we want a failure") must_== AttrSelector("type", "reset", Empty) } ":submit must parse" in { - CssSelectorParser.parse(":submit").open_! must_== + CssSelectorParser.parse(":submit").openOrThrowException("If the box is empty, we want a failure") must_== AttrSelector("type", "submit", Empty) } ":text must parse" in { - CssSelectorParser.parse(":text").open_! must_== + CssSelectorParser.parse(":text").openOrThrowException("If the box is empty, we want a failure") must_== AttrSelector("type", "text", Empty) } "select an id with attr subnodes" in { - CssSelectorParser.parse("#foo *[dog] ").open_! must_== + CssSelectorParser.parse("#foo *[dog] ").openOrThrowException("If the box is empty, we want a failure") must_== IdSelector("foo", Full(AttrSubNode("dog"))) } "select an id with no star attr subnodes" in { - CssSelectorParser.parse("#foo [woof] ").open_! must_== + CssSelectorParser.parse("#foo [woof] ").openOrThrowException("If the box is empty, we want a failure") must_== IdSelector("foo", Full(AttrSubNode("woof"))) } "select an id with attr append subnodes" in { - CssSelectorParser.parse("#foo *[dog+] ").open_! must_== + CssSelectorParser.parse("#foo *[dog+] ").openOrThrowException("If the box is empty, we want a failure") must_== IdSelector("foo", Full(AttrAppendSubNode("dog"))) } "select an id with no star attr append subnodes" in { - CssSelectorParser.parse("#foo [woof+] ").open_! must_== + CssSelectorParser.parse("#foo [woof+] ").openOrThrowException("If the box is empty, we want a failure") must_== IdSelector("foo", Full(AttrAppendSubNode("woof"))) } "select an id with attr append subnodes" in { - CssSelectorParser.parse("#foo *[dog!] ").open_! must_== + CssSelectorParser.parse("#foo *[dog!] ").openOrThrowException("If the box is empty, we want a failure") must_== IdSelector("foo", Full(AttrRemoveSubNode("dog"))) } "select an id with no star attr append subnodes" in { - CssSelectorParser.parse("#foo [woof!] ").open_! must_== + CssSelectorParser.parse("#foo [woof!] ").openOrThrowException("If the box is empty, we want a failure") must_== IdSelector("foo", Full(AttrRemoveSubNode("woof"))) } @@ -176,46 +177,46 @@ object CssSelectorSpec extends Specification { } "select a class" in { - CssSelectorParser.parse(".foo").open_! must_== ClassSelector("foo", Empty) + CssSelectorParser.parse(".foo").openOrThrowException("If the box is empty, we want a failure") must_== ClassSelector("foo", Empty) } "select a class with subnodes" in { - CssSelectorParser.parse(".foo * ").open_! must_== + CssSelectorParser.parse(".foo * ").openOrThrowException("If the box is empty, we want a failure") must_== ClassSelector("foo", Full(KidsSubNode())) } "Support selecting this node" in { - CssSelectorParser.parse(".foo ^^ ").open_! must_== + CssSelectorParser.parse(".foo ^^ ").openOrThrowException("If the box is empty, we want a failure") must_== ClassSelector("foo", Full(SelectThisNode(false))) } "Support selecting this node" in { - CssSelectorParser.parse(".foo ^* ").open_! must_== + CssSelectorParser.parse(".foo ^* ").openOrThrowException("If the box is empty, we want a failure") must_== ClassSelector("foo", Full(SelectThisNode(true))) } "select a class with attr subnodes" in { - CssSelectorParser.parse(".foo *[dog] ").open_! must_== + CssSelectorParser.parse(".foo *[dog] ").openOrThrowException("If the box is empty, we want a failure") must_== ClassSelector("foo", Full(AttrSubNode("dog"))) } "select an id with no star attr subnodes" in { - CssSelectorParser.parse(".foo [woof] ").open_! must_== + CssSelectorParser.parse(".foo [woof] ").openOrThrowException("If the box is empty, we want a failure") must_== ClassSelector("foo", Full(AttrSubNode("woof"))) } "select multiple depth" in { - CssSelectorParser.parse("div .foo [woof] ").open_! must_== + CssSelectorParser.parse("div .foo [woof] ").openOrThrowException("If the box is empty, we want a failure") must_== EnclosedSelector(ElemSelector("div", Empty), ClassSelector("foo", Full(AttrSubNode("woof")))) } "select multiple depth with star" in { - CssSelectorParser.parse("div .foo * ").open_! must_== + CssSelectorParser.parse("div .foo * ").openOrThrowException("If the box is empty, we want a failure") must_== EnclosedSelector(ElemSelector("div", Empty), ClassSelector("foo", Full(KidsSubNode()))) } "select multiple super depth with star" in { - CssSelectorParser.parse("span div .foo * ").open_! must_== + CssSelectorParser.parse("span div .foo * ").openOrThrowException("If the box is empty, we want a failure") must_== EnclosedSelector(ElemSelector("span", Empty), EnclosedSelector(ElemSelector("div", Empty), ClassSelector("foo", Full(KidsSubNode())))) } @@ -224,3 +225,762 @@ object CssSelectorSpec extends Specification { } +object CssBindHelpersSpec extends Specification { + + "css bind helpers" should { + "clear clearable" in { + ClearClearable() must ==/ () + } + + "substitute a String by id" in { + ("#foo" #> "hello").apply() must ==/ (hello) + } + + + "not duplicate classes" in { + + def anchor(quesType: String, value: String) = { + (value) + } + var page = 1 + var elements = List("1","2","3","4") + + val xml =
    +
    + 1 + 1 + 1 +
    + +
    + + val sel = ".question" #> elements.map(value => { + ".question [id]" #> ("question-" + value) & + ".question [class]" #> ("question-" + value) & + ".L" #> anchor("L", value) & + ".U" #> anchor("U", value) & + ".D" #> anchor("D", value) + }) + + val res = sel(xml) + + ((res \\ "a").head \ "@class").head.text must_== "selected L" + } + + + "Compound selector" in { + val res = + (".foo [href]" #> "http://dog.com" & ".bar [id]" #> "moo").apply( + ) + (res \ "@href").text must_== "http://dog.com" + (res \ "@id").text must_== "moo" + } + + "not stack overflow on Elem" in { + val xf = "* [id]" #> "xx" & + "* [style]" #> "border:thin solid black" & + "* *" #> + success + } + + "not stack overflow on Elem" in { + val xf = "* [id]" #> "xx" & + "* [style]" #> "border:thin solid black" & + "* *+" #> + + xf(
    ) + success + } + + "not stack overflow on Elem" in { + val xf = "* [id]" #> "xx" & + "* [style]" #> "border:thin solid black" & + "* -*" #> + + xf(
    ) + success + } + + "support modifying attributes along with body" in { + val org = foo + val func = "a [href]" #> "dog" & "a *" #> "bar" + val res = func(org) + + res.toString must_== "bar" + } + + "substitute a String by id" in { + ("#foo" replaceWith "hello").apply() must ==/ (hello) + } + + "substitute a String by nested class" in { + ("div .foo" #> "hello").apply(
    ) must ==/ (
    hello
    ) + } + + "substitute a String by deep nested class" in { + ("#baz div .foo" #> "hello").apply( +
    ) must ==/ (
    hello
    ) + } + + "insert a String by deep nested class" in { + ("#baz div .foo *" #> "hello").apply( +
    ) must ==/ (
    hello
    ) + } + + + + "Select a node" in { + ("#foo ^^" #> "hello").apply(
    ) must ==/ () + } + + "Another nested select" in { + val template = +
    + + +

    +
    +
    + + +

    +
    +
    + + val xf = "#get ^^" #> "ignore" & "#file_upload" #> + + val ret = xf(template) + + ret(0).asInstanceOf[Elem].label must_== "div" + ret.length must_== 1 + (ret \ "@id").text must_== "get" + + (ret \\ "input").length must_== 2 + + ((ret \\ "input").toList(0) \ "@type").map(_.text) must_== List("moose") + + } + + "Child nested select" in { + val template = +
    + + +

    +
    +
    + + +

    +
    +
    + + val xf = "#get ^*" #> "ignore" & "#file_upload" #> + + val ret = xf(template) + + (ret \\ "div").length must_== 0 + + (ret \\ "input").length must_== 2 + + ((ret \\ "input").toList(0) \ "@type").map(_.text) must_== List("moose") + + } + + "Select a node and transform stuff" in { + val ret = ("#foo ^^" #> "hello" & + "span [id]" #> "bar")() + + ret(0).asInstanceOf[Elem].label must_== "span" + ret.length must_== 1 + (ret \ "@id").text must_== "bar" + } + + + "Select a node and transform stuff deeply nested" in { + val ret = ("#foo ^^" #> "hello" & + "span [id]" #> "bar")(
    ) + + ret(0).asInstanceOf[Elem].label must_== "span" + ret.length must_== 1 + (ret \ "@id").text must_== "bar" + } + + + "Select a node and transform stuff deeply nested 2" in { + val ret = ("#foo ^^" #> "hello" & + "span [id]" #> "bar")(
    ) + + ret(0).asInstanceOf[Elem].label must_== "span" + ret.length must_== 1 + (ret \ "@id").text must_== "bar" + (ret \ "@dog").text must_== "woof" + } + + + + "substitute multiple Strings by id" in { + ("#foo" #> "hello" & + "#baz" #> "bye" + )(
    Hello
    ) must be_== (NodeSeq fromSeq {Text("bye")}{Text("hello")}) + } + + "bind href and None content" in { + val opt: Option[String] = None + val res = ("top *" #> opt & + "top [href]" #> "frog")(cat) + + res.text must_== "" + (res \ "@href").text.mkString must_== "frog" + } + + "bind href and Some content" in { + val opt: Option[String] = Some("Dog") + val res = ("top *" #> opt & + "top [href]" #> "frog")(cat) + + res.text must_== "Dog" + (res \ "@href").text.mkString must_== "frog" + } + + "bind href and Some content with multiple attrs" in { + val opt: Option[String] = Some("Dog") + val res = ("top *" #> opt & + "top [meow]" #> "woof" & + "top [href]" #> "frog")(cat) + + res.text must_== "Dog" + (res \ "@href").text.mkString must_== "frog" + (res \ "@meow").text.mkString must_== "woof" + } + + "option transform on *" in { + val opt: Option[String] = None + val res = ("* *" #> opt.map(ignore => "Dog")).apply(cat) + res.head must_== + } + + "append attribute to a class with spaces" in { + val stuff = List("a", "b") + val res = ("* [class+]" #> stuff).apply(cat) + (res \ "@class").text must_== "q a b" + } + + "append attribute to an href" in { + val stuff = List("&a=b", "&b=d") + val res = ("* [href+]" #> stuff).apply(cat) + (res \ "@href").text must_== "q?z=r&a=b&b=d" + } + + "remove an attribute from a class" in { + val func = ".foo [class!]" #> "andOther" + + (func() \ "@class").text must_== "foo" + } + + "remove an attribute from a class and the attribute if it's the only one left" in { + val func = ".foo [class!]" #> "foo" + val res = func() + + (res \ "@class").length must_== 0 + } + + + + "Remove a subnode's class attribute" in { + + val func = ".removeme !!" #> ("td [class!]" #> "removeme") + val res = func.apply(Hi) + + ((res \ "td") \ "@class").text must_== "fish" + } + + + "not remove a non-existant class" in { + val func = ".foo [class!]" #> "bar" + val res = func() + + (res \ "@class").text must_== "foo" + } + + + "remove an attribute from an attribute" in { + val func = "span [href!]" #> "foo" + val res = func() + + (res \ "@href").length must_== 0 + } + + + "not remove a non-existant href" in { + val func = "span [href!]" #> "bar" + val res = func() + + (res \ "@href").text must_== "foo bar" + } + + "option transform on *" in { + val opt: Option[Int] = Full(44) + val res = ("* *" #> opt.map(ignore => "Dog")).apply(cat) + res must ==/ (Dog) + } + + + "Java number support" in { + val f = "a *" #> Full(new java.lang.Long(12)) + val xml = Hello + + f(xml) must ==/ (12) + } + + + "Surround kids" in { + val f = "a <*>" #>
    + val xml = Meow Cat woof + + f(xml) must ==/ (Meow
    Cat
    woof
    ) + } + + "Andreas's thing doesn't blow up" in { + def cachedMessageList: Box[Box[String]] = Empty + + def messageListId = "Hello" + + def collapseUnless[A](isEmptyCond: Boolean)(f: => A): Box[A] = { + if (!isEmptyCond) { + Empty + } else { + Full(f) + } + } + + ".noMail" #> collapseUnless(cachedMessageList.map(_.isEmpty).openOr(true)) { + "tbody [id]" #> messageListId & + "*" #> PassThru + } + + true must_== true + } + + "other Andreas test" in { + def renderBlogEntrySummary = { + ".blogEntry" #> ((ns: NodeSeq) => { + ("*" #> "Horse").apply(ns) + }) + } + + + + def render = { + + "*" #> ((ns: NodeSeq) => + renderBlogEntrySummary.apply(ns) ++ hi + ) + } + + render + + true must_== true + } + + + "option transform on *" in { + val opt: Box[String] = Empty + val res = ("* *" #> opt.map(ignore => "Dog")).apply(cat) + res.head must_== + } + + "option transform on *" in { + val opt: Box[Int] = Some(44) + val res = ("* *" #> opt.map(ignore => "Dog")).apply(cat) + res must ==/ (Dog) + } + + "transform on *" in { + val res = ("* *" #> "Dog").apply(cat) + res must ==/ (Dog) + } + + "transform child content on *+" in { + val res = ("* *+" #> "moose").apply(I like ) + res.text must_== "I like moose" + } + + "transform child content on -*" in { + val res = ("* -*" #> "moose").apply( I like) + res.text must_== "moose I like" + } + + "transform on li" in { + val res = ("li *" #> List("Woof", "Bark") & ClearClearable)( +
    • meow
    • a
    • a
    ) + res must ==/ (
    • Woof
    • Bark
    ) + } + + "substitute multiple Strings by id" in { + (("#foo" replaceWith "hello") & + ("#baz" replaceWith "bye") + )( +
    Hello
    + ) must_== (NodeSeq fromSeq {Text("bye")}{Text("hello")}) + } + + "substitute multiple Strings with a List by id" in { + ("#foo" #> "hello" & + "#baz" #> List("bye", "bye"))(
    Hello
    ) must_== (NodeSeq fromSeq {Text("bye")}{Text("bye")}{Text("hello")}) + } + + "substitute multiple Strings with a List by id" in { + (("#foo" replaceWith "hello") & + ("#baz" replaceWith List("bye", "bye")))(
    Hello
    ) must_== (NodeSeq fromSeq {Text("bye")}{Text("bye")}{Text("hello")}) + } + + + "substitute multiple Strings with a List of XML by id" in { + val answer = ("#foo" #> "hello" & + "#baz" #> List[NodeSeq](, Meow))(
    Hello
    ) + + (answer \ "i").length must_== 2 + (answer \ "i")(0) must ==/ () + (answer \ "i")(1) must ==/ (Meow) + } + + "substitute multiple Strings with a List of XML by id" in { + val answer = (("#foo" replaceWith "hello") & + ("#baz" replaceWith List[NodeSeq](, Meow)))(
    Hello
    ) + + (answer \ "i").length must_== 2 + (answer \ "i")(0) must ==/ () + (answer \ "i")(1) must ==/ (Meow) + } + + "substitute by name" in { + val answer = ("name=moose" #> ).apply ( +
    ) + + (answer \ "input")(0) must ==/ () + } + + + "Deal with NodeSeq as a NodeSeq" in { + val f = "h6 *" #> ((Text("Some awesome ") ++ text ++ Text(" here.")): NodeSeq) + val xml =
    Dude, where's my car?
    + + val res = f(xml) + res must ==/ (
    Some awesome text here.
    ) + } + + "substitute by name" in { + val answer = ("name=moose" replaceWith ).apply ( +
    ) + + (answer \ "input")(0) must ==/ () + } + + + "substitute by name with attrs" in { + val answer = ("name=moose" #> ).apply ( +
    ) + + (answer \ "input")(0) must ==/ () + } + + "substitute by name with attrs" in { + val answer = ("name=moose" replaceWith ).apply ( +
    ) + + (answer \ "input")(0) must ==/ () + } + + + "substitute by a selector with attrs" in { + val answer = ("cute=moose" #> ).apply ( +
    ) + + (answer \ "input")(0) must ==/ () + } + + "substitute by a selector with attrs" in { + val answer = ("cute=moose" replaceWith ).apply ( +
    ) + + (answer \ "input")(0) must ==/ () + } + + "Map of funcs" in { + val func: NodeSeq => NodeSeq = "#horse" #> List(1,2,3).map(".item *" #> _) + val answer: NodeSeq = func(
    frogi
    ) + + answer must ==/ (
    frog1
    frog2
    frog3
    ) + + } + + "maintain unique id attributes provided by transform" in { + val func = ".thinglist *" #> + (".thing" #> List("xx1", "xx2", "xx2", "xx2", "xx4").map(t => { + ".thing [id]" #> t + }) + ) + val answer = func(
    ) + + answer must ==/ (
    ) + } + + "merge classes" in { + val answer = ("cute=moose" #> ).apply ( +
    ) + + (answer \ "input")(0) must ==/ () + } + + + "merge classes" in { + val answer = ("cute=moose" replaceWith ).apply ( +
    ) + + (answer \ "input")(0) must ==/ () + } + + + + + "list of strings" in { + val answer = ("#moose *" #> List("a", "b", "c", "woof") & + ClearClearable).apply ( +
      +
    • first
    • +
    • second
    • +
    • Third
    • +
    ) + + val lis = (answer \ "li").toList + + lis.length must_== 4 + + lis(0) must ==/ (
  • a
  • ) + lis(3) must ==/ (
  • woof
  • ) + } + + + "list of Nodes" in { + val answer = ("#moose *" #> List[NodeSeq]("a", Text("b"), Text("c"), woof) & + ClearClearable).apply ( +
      +
    • first
    • +
    • second
    • +
    • Third
    • +
    ) + + val lis = (answer \ "li").toList + + lis.length must_== 4 + + lis(0) must ==/ (
  • "a"
  • ) + lis(3) must ==/ (
  • woof
  • ) + } + + + "set href" in { + val answer = ("#moose [href]" #> "Hi" & + ClearClearable).apply ( + ) + + + (answer \ "a" \ "@href").text must_== "Hi" + (answer \ "li").length must_== 0 + } + + "set href and subnodes" in { + val answer = ("#moose [href]" #> "Hi" & + ClearClearable).apply ( + ) + + + (answer \ "a" \ "@href").text must_== "Hi" + (answer \\ "li").length must_== 0 + } + + + "list of strings" in { + val answer = (("#moose *" replaceWith List("a", "b", "c", "woof")) & + ClearClearable).apply ( +
      +
    • first
    • +
    • second
    • +
    • Third
    • +
    ) + + val lis = (answer \ "li").toList + + lis.length must_== 4 + + lis(0) must ==/ (
  • a
  • ) + lis(3) must ==/ (
  • woof
  • ) + } + + "bind must bind to subnodes" in { + val html =
      +
    • + +
    • +
    + + val lst = List(1,2,3) + + val f = ".users *" #> ("li" #> lst.map(i => ".user [userid]" #> i)) + + (f(html) \\ "ul").length must_== 1 + (f(html) \\ "li").length must_== 3 + } + + "list of Nodes" in { + val answer = (("#moose *" replaceWith List[NodeSeq]("a", Text("b"), Text("c"), woof)) & + ClearClearable).apply ( +
      +
    • first
    • +
    • second
    • +
    • Third
    • +
    ) + + val lis = (answer \ "li").toList + + lis.length must_== 4 + + lis(0) must ==/ (
  • "a"
  • ) + lis(3) must ==/ (
  • woof
  • ) + } + + + "set href" in { + val answer = (("#moose [href]" replaceWith "Hi") & + ClearClearable).apply ( + ) + + + (answer \ "a" \ "@href").text must_== "Hi" + (answer \ "li").length must_== 0 + } + + "set href and subnodes" in { + val answer = (("#moose [href]" replaceWith "Hi") & + ClearClearable).apply ( + ) + + + (answer \ "a" \ "@href").text must_== "Hi" + (answer \\ "li").length must_== 0 + } + + + } +} + + +/** + * This class doesn't actually perform any tests, but insures that + * the implicit conversions work correctly + */ +object CheckTheImplicitConversionsForToCssBindPromoter { + val bog = new ToCssBindPromoter(Empty, Empty) + + import BindHelpers._ + + "foo" #> "baz" + + bog #> "Hello" + bog #> + bog #> 1 + bog #> 'foo + bog #> 44L + bog #> false + + bog #> List() + bog #> Full() + bog #> Some() + + + bog #> List("Hello") + bog #> Full("Dog") + bog #> Some("Moo") + + + bog #> List((null: Bindable)) + bog #> Full((null: Bindable)) + bog #> Some((null: Bindable)) + + bog #> nsToNs _ + bog #> nsToOptNs _ + bog #> nsToBoxNs _ + bog #> nsToSeqNs _ + + bog #> nsToString _ + bog #> nsToOptString _ + bog #> nsToBoxString _ + bog #> nsToSeqString _ + + val nsf: NodeSeq => NodeSeq = bog #> "Hello" & + bog #> & + bog #> 1 & + bog #> 'foo & + bog #> 44L & + bog #> false + + "foo" #> "Hello" + "foo" #> + "foo" #> 1 + "foo" #> 'foo + "foo" #> 44L + "foo" #> false + + "foo" #> List() + "foo" #> Full() + "foo" #> Some() + + + "foo" #> List("Hello") + "foo" #> Full("Dog") + "foo" #> Some("Moo") + + + "foo" #> List((null: Bindable)) + "foo" #> Full((null: Bindable)) + "foo" #> Some((null: Bindable)) + + "foo" #> nsToNs _ + "foo" #> nsToOptNs _ + "foo" #> nsToBoxNs _ + "foo" #> nsToSeqNs _ + + "foo" #> nsToString _ + "foo" #> nsToOptString _ + "foo" #> nsToBoxString _ + "foo" #> nsToSeqString _ + + "#foo" #> Set("a", "b", "c") + + val nsf2: NodeSeq => NodeSeq = "foo" #> "Hello" & + "foo" #> & + "foo" #> 1 & + "foo" #> 'foo & + "foo" #> 44L & + "foo" #> false + + "bar" #> List("1","2","3").map(s => "baz" #> s) + + "bar" #> Full(1).map(s => ("baz" #> s): CssBindFunc) + "bar" #> Some(1).map(s => ("baz" #> s): CssBindFunc) + + + + def nsToNs(in: NodeSeq): NodeSeq = in + def nsToOptNs(in: NodeSeq): Option[NodeSeq] = Some(in) + def nsToBoxNs(in: NodeSeq): Box[NodeSeq] = Full(in) + def nsToSeqNs(in: NodeSeq): Seq[NodeSeq] = List(in) + + def nsToString(in: NodeSeq): String = in.text + def nsToOptString(in: NodeSeq): Option[String] = Some(in.text) + def nsToBoxString(in: NodeSeq): Box[String] = Full(in.text) + def nsToSeqString(in: NodeSeq): Seq[String] = List(in.text) +} From b2a5635533e533264edc66e2ad6e708d088fcb4a Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Mon, 10 Sep 2012 13:53:10 -0400 Subject: [PATCH 0179/1949] * modified unsafePublishLift to use publish instead of publish-local * made unsafePublishLift executable * updated scala versions we build against --- build.sbt | 2 +- unsafePublishLift.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) mode change 100644 => 100755 unsafePublishLift.sh diff --git a/build.sbt b/build.sbt index 9b82164410..a2df893f30 100644 --- a/build.sbt +++ b/build.sbt @@ -12,7 +12,7 @@ startYear in ThisBuild := Some(2006) organizationName in ThisBuild := "WorldWide Conferencing, LLC" -crossScalaVersions in ThisBuild := Seq("2.9.2", "2.9.1-1", "2.9.1", "2.9.0-1", "2.9.0") +crossScalaVersions in ThisBuild := Seq("2.9.2", "2.9.1-1", "2.9.1") libraryDependencies in ThisBuild ++= Seq(specs2, scalacheck) diff --git a/unsafePublishLift.sh b/unsafePublishLift.sh old mode 100644 new mode 100755 index 1660038e2d..803e56dcc2 --- a/unsafePublishLift.sh +++ b/unsafePublishLift.sh @@ -157,7 +157,7 @@ for MODULE in framework ; do # Do a separate build for each configured Scala version so we don't blow the heap for SCALA_VERSION in $(grep crossScalaVersions build.sbt | cut -d '(' -f 2 | sed s/[,\)\"]//g ); do echo -n " Building against Scala ${SCALA_VERSION}..." - if ! ./liftsh ++${SCALA_VERSION} clean update test publish-local >> ${BUILDLOG} ; then + if ! ./liftsh ++${SCALA_VERSION} clean update test publish >> ${BUILDLOG} ; then echo "failed! See build log for details" exit fi From 97140740e489e9071f92f3f3211570d2f11be8b7 Mon Sep 17 00:00:00 2001 From: David Pollak Date: Thu, 13 Sep 2012 12:02:07 -0700 Subject: [PATCH 0180/1949] Changed *Var backing store to concurrent hashmap --- .../src/main/scala/net/liftweb/http/Vars.scala | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/Vars.scala b/web/webkit/src/main/scala/net/liftweb/http/Vars.scala index 2725a5077d..ea0623e0b3 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Vars.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Vars.scala @@ -19,9 +19,9 @@ package http import net.liftweb.common._ import net.liftweb.util._ -import Helpers._ -import scala.collection.mutable.{HashMap, HashSet, ListBuffer} -import java.util.concurrent.Callable +import scala.collection.mutable.ListBuffer +import java.util.concurrent.{ConcurrentHashMap, Callable} +import scala.collection.JavaConversions._ /** * The bridge between Scala *Vars implementations and @@ -534,7 +534,7 @@ private[http] trait CoreRequestVarHandler { private val logger = Logger(classOf[CoreRequestVarHandler]) // This maps from the RV name to (RV instance, value, set-but-not-read flag) - private val vals: ThreadGlobal[HashMap[String, (MyType, Any, Boolean)]] = new ThreadGlobal + private val vals: ThreadGlobal[ConcurrentHashMap[String, (MyType, Any, Boolean)]] = new ThreadGlobal private val cleanup: ThreadGlobal[ListBuffer[Box[LiftSession] => Unit]] = new ThreadGlobal private val isIn: ThreadGlobal[String] = new ThreadGlobal private val sessionThing: ThreadGlobal[Box[LiftSession]] = new ThreadGlobal @@ -562,7 +562,7 @@ private[http] trait CoreRequestVarHandler { ) } - protected def backingStore: Box[HashMap[String, (MyType, Any, Boolean)]] = + protected def backingStore: Box[ConcurrentHashMap[String, (MyType, Any, Boolean)]] = vals.value match { case null => if (LiftRules.throwOnOutOfScopeVarAccess) { @@ -575,7 +575,7 @@ private[http] trait CoreRequestVarHandler { private[http] def get[T](name: String): Box[T] = for { ht <- backingStore - (rvInstance, value, unread) <- ht.get(name) + (rvInstance, value, unread) <- Box !! ht.get(name) } yield { if (unread) { // Flag the variable as no longer being set-but-unread @@ -613,7 +613,7 @@ private[http] trait CoreRequestVarHandler { f } else { isIn.doWith("in")( - vals.doWith(new HashMap)( + vals.doWith(new ConcurrentHashMap)( cleanup.doWith(new ListBuffer) { sessionThing.doWith(session) { val ret: T = f From 9dc635255f15bca027db5735e94ddfbbd800aa56 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Fri, 14 Sep 2012 08:20:28 -0400 Subject: [PATCH 0181/1949] Fix deadlock in SoftReferenceCache apply function. When we find a stale SoftReference, release the read lock, then acquire the write lock, double-check that the reference is still stale, and then finally remove the entry from the cache. --- .../net/liftweb/util/SoftReferenceCache.scala | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/SoftReferenceCache.scala b/core/util/src/main/scala/net/liftweb/util/SoftReferenceCache.scala index 96cc504ad2..cba1535269 100644 --- a/core/util/src/main/scala/net/liftweb/util/SoftReferenceCache.scala +++ b/core/util/src/main/scala/net/liftweb/util/SoftReferenceCache.scala @@ -118,15 +118,28 @@ class SoftReferenceCache[K, V](cacheSize: Int) { * @param key * @return Box[V] */ - def apply(key: K): Box[V] = lock(readLock) { - Box.!!(cache.get(key)) match { - case Full(value) => - Box.!!(value.get) or { - remove(key); - Empty + def apply(key: K): Box[V] = { + val (doRemove:Boolean, retval:Box[V]) = + lock(readLock) { + Box.!!(cache.get(key)) match { + case Full(value) => + Box.!!(value.get).map((false, _)) openOr { + (true, Empty) + } + case _ => (false, Empty) } - case _ => Empty + } + + if (doRemove) { + lock(writeLock) { + val value = cache.get(key) + + if (value != null && value.get == null) + remove(key) + } } + + retval } /** From c9279a48512dc83c14be655e40928624bbab04fe Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 24 Sep 2012 18:39:36 -0400 Subject: [PATCH 0182/1949] Move uriSuffix extraction into lift_ajaxHandler. By having it in doAjaxCycle, there were situations where the uri suffix could get lost. The most obvious one was when a long-running ajax request was occurring, and two AJAX requests were queued during that time frame. This would result in the first request getting the second request's uriSuffix, and the second request getting no suffix at all. We now immediately put the uriSuffix into the sending data when lift_ajaxHandler is called. --- .../scala/net/liftweb/http/js/ScriptRenderer.scala | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala b/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala index 4b9d536fdc..efd6684e1d 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala @@ -47,6 +47,12 @@ object ScriptRenderer { toSend.onFailure = theFailure; toSend.responseType = responseType; + if (liftAjax.lift_uriSuffix) { + theData += '&' + liftAjax.lift_uriSuffix; + toSend.theData = theData; + liftAjax.lift_uriSuffix = undefined; + } + liftAjax.lift_ajaxQueue.push(toSend); liftAjax.lift_ajaxQueueSort(); liftAjax.lift_doCycleQueueCnt++; @@ -161,11 +167,6 @@ object ScriptRenderer { liftAjax.lift_actualJSONCall(aboutToSend.theData, successFunc, failureFunc); } else { var theData = aboutToSend.theData; - if (liftAjax.lift_uriSuffix) { - theData += '&' + liftAjax.lift_uriSuffix; - aboutToSend.theData = theData; - liftAjax.lift_uriSuffix = undefined; - } liftAjax.lift_actualAjaxCall(theData, successFunc, failureFunc); } } From 98780652f0259e9dbd48ee02082e7bb549fe1e96 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 24 Sep 2012 18:46:46 -0400 Subject: [PATCH 0183/1949] Encode an AJAX request version in the request URI. The AJAX request version has two components: the actual version number and a count of queued requests. This is presented as two base-36 values after a dash. So a request now looks like: /ajax_request/F-v8 Where v indicates request number 31 and 8 indicates that there are 8 queued requests that have not been handled yet. --- .../net/liftweb/http/js/ScriptRenderer.scala | 28 +++++++++++++------ .../http/js/jquery/JQueryArtifacts.scala | 2 +- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala b/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala index efd6684e1d..1921da5e07 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala @@ -46,6 +46,7 @@ object ScriptRenderer { toSend.onSuccess = theSuccess; toSend.onFailure = theFailure; toSend.responseType = responseType; + toSend.version = liftAjax.lift_ajaxVersion++; if (liftAjax.lift_uriSuffix) { theData += '&' + liftAjax.lift_uriSuffix; @@ -108,7 +109,8 @@ object ScriptRenderer { }, lift_registerGC: function() { - var data = "__lift__GC=_" + var data = "__lift__GC=_", + version = null; """ + LiftRules.jsArtifacts.ajax(AjaxInfo(JE.JsRaw("data"), "POST", LiftRules.ajaxPostTimeout, @@ -166,8 +168,10 @@ object ScriptRenderer { aboutToSend.responseType.toLowerCase() === "json") { liftAjax.lift_actualJSONCall(aboutToSend.theData, successFunc, failureFunc); } else { - var theData = aboutToSend.theData; - liftAjax.lift_actualAjaxCall(theData, successFunc, failureFunc); + var theData = aboutToSend.theData, + version = aboutToSend.version; + + liftAjax.lift_actualAjaxCall(theData, version, successFunc, failureFunc); } } } @@ -181,17 +185,22 @@ object ScriptRenderer { setTimeout("liftAjax.lift_doAjaxCycle();", 200); }, - addPageName: function(url) { - return """ + { - if (LiftRules.enableLiftGC) { - "url.replace('" + LiftRules.ajaxPath + "', '" + LiftRules.ajaxPath + "/'+lift_page);" + lift_ajaxVersion: 0, + + addPageNameAndVersion: function(url, version) { + """ + { + if (LiftRules.enableLiftGC) { """ + var replacement = '""" + LiftRules.ajaxPath + """/'+lift_page; + if (version) + replacement += ('-'+(version%36).toString(36)) + (liftAjax.lift_ajaxQueue.length > 35 ? 35 : liftAjax.lift_ajaxQueue.length).toString(36); + return url.replace('""" + LiftRules.ajaxPath + """', replacement);""" } else { - "url;" + "return url;" } } + """ }, - lift_actualAjaxCall: function(data, onSuccess, onFailure) { + lift_actualAjaxCall: function(data, version, onSuccess, onFailure) { """ + LiftRules.jsArtifacts.ajax(AjaxInfo(JE.JsRaw("data"), "POST", @@ -202,6 +211,7 @@ object ScriptRenderer { }, lift_actualJSONCall: function(data, onSuccess, onFailure) { + var version = null; """ + LiftRules.jsArtifacts.ajax(AjaxInfo(JE.JsRaw("data"), "POST", diff --git a/web/webkit/src/main/scala/net/liftweb/http/js/jquery/JQueryArtifacts.scala b/web/webkit/src/main/scala/net/liftweb/http/js/jquery/JQueryArtifacts.scala index 346c554542..76f4a12197 100755 --- a/web/webkit/src/main/scala/net/liftweb/http/js/jquery/JQueryArtifacts.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/js/jquery/JQueryArtifacts.scala @@ -97,7 +97,7 @@ trait JQueryArtifacts extends JSArtifacts { def ajax(data: AjaxInfo): String = { "jQuery.ajax(" + toJson(data, S.contextPath, prefix => - JsRaw("liftAjax.addPageName(" + S.encodeURL(prefix + "/" + LiftRules.ajaxPath + "/").encJs + ")")) + ");" + JsRaw("liftAjax.addPageNameAndVersion(" + S.encodeURL(prefix + "/" + LiftRules.ajaxPath + "/").encJs + ", version)")) + ");" } /** From d2ec29f36a5a43d86c928d2b6f80eed913182fae Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 24 Sep 2012 19:02:02 -0400 Subject: [PATCH 0184/1949] Drop the timeout on comet-related AJAX requests. While this will tie up a request thread for longer, it means we can reliably say that when the AJAX request thread completes, it will actually have the correct response to the original request. --- web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index dc206fd6c2..cbdb27632a 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -709,10 +709,8 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, w match { // if it's going to a CometActor, batch up the commands case Full(id) if asyncById.contains(id) => asyncById.get(id).toList.flatMap(a => - a.!?(a.cometProcessingTimeout, ActionMessageSet(f.map(i => buildFunc(i)), state)) match { - case Full(li: List[_]) => li + a.!?(ActionMessageSet(f.map(i => buildFunc(i)), state)) match { case li: List[_] => li - case Empty => Full(a.cometProcessingTimeoutHandler()) case other => Nil }) case _ => f.map(i => buildFunc(i).apply()) From 26bf7a01267d1621886d049bed112e2d7a29b75a Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 24 Sep 2012 19:04:15 -0400 Subject: [PATCH 0185/1949] Add tracking for AJAX requests in LiftSession. Request info consists of three things: - The request version. - A future for the response to the request, satisfied by the first request for this version when the response is ready. - A lastSeen timestamp, used to expire the entry after the usual function lifespan. LiftSession.withAjaxRequests exposes the request list, which is a Map mapping a page version to the list of AjaxRequestInfos currently being tracked for that page. AjaxRequestInfos are cleaned up according to their lastSeen timestamp, which is updated the same way as those of functions on the page. --- .../scala/net/liftweb/http/LiftSession.scala | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index cbdb27632a..081901398a 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -499,6 +499,15 @@ private final case class PostPageFunctions(renderVersion: String, } +/** + * The responseFuture will be satisfied by the original request handling + * thread when the response has been calculated. Retries will wait for the + * future to be satisfied in order to return the proper response. + */ +private[http] final case class AjaxRequestInfo(requestVersion:Int, + responseFuture:LAFuture[Box[LiftResponse]], + lastSeen: Long) + /** * The LiftSession class containg the session state information */ @@ -557,6 +566,20 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, */ private var postPageFunctions: Map[String, PostPageFunctions] = Map() + /** + * A list of AJAX requests that may or may not be pending for this + * session. There is an entry for every AJAX request we don't *know* + * has completed successfully or been discarded by the client. + * + * See LiftServlet.handleAjax for how we determine we no longer need + * to hold a reference to an AJAX request. + */ + private var ajaxRequests = scala.collection.mutable.Map[String,List[AjaxRequestInfo]]() + + private[http] def withAjaxRequests[T](fn: (scala.collection.mutable.Map[String, List[AjaxRequestInfo]]) => T) = { + ajaxRequests.synchronized { fn(ajaxRequests) } + } + /** * The synchronization lock for the postPageFunctions */ @@ -834,6 +857,22 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, } } + withAjaxRequests { currentAjaxRequests => + for { + (version, requestInfos) <- currentAjaxRequests + } { + val remaining = + requestInfos.filter { info => + (now - info.lastSeen) <= LiftRules.unusedFunctionsLifeTime + } + + if (remaining.length > 0) + currentAjaxRequests += (version -> remaining) + else + currentAjaxRequests -= version + } + } + synchronized { messageCallback.foreach { case (k, f) => @@ -961,6 +1000,14 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, } postPageFunctions += (ownerName -> funcInfo.updateLastSeen) } + withAjaxRequests { currentAjaxRequests => + currentAjaxRequests.get(ownerName).foreach { requestInfos => + val updated = requestInfos.map(_.copy(lastSeen = time)) + + currentAjaxRequests += (ownerName -> updated) + } + } + synchronized { (0 /: messageCallback)((l, v) => l + (v._2.owner match { case Full(owner) if (owner == ownerName) => From 82824bf4ed80027ac7129a5ad3a4cf9d68fab5e7 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 24 Sep 2012 19:09:17 -0400 Subject: [PATCH 0186/1949] Implement the meat of AJAX deduplication. AJAX requests now come in with a two-part version number, one a sequence number for the ajax request's count in the overall list of requests sent by the client, and one a number indicating how many other requests are queued up on the client. If this is the first request seen with its sequence number, we record the request in the session and run regular AJAX request handling. When that request handling is complete, it satisfies the future that is recorded in the session. Subsequent requests for a given sequence number wait on the future from the first request up to the ajax post timeout, then fail. When we get a request coming in with a 0 count for pending requests on the client, we clear out all other pending requests in the session's list, since that means none of them will be getting a chance to report their responses again. --- .../scala/net/liftweb/http/LiftServlet.scala | 250 ++++++++++++++---- 1 file changed, 199 insertions(+), 51 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala index 8bafb9ce59..15632a5a91 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala @@ -423,75 +423,223 @@ class LiftServlet extends Loggable { } } + /** + * Tracks the two aspects of an AJAX version: the sequence number, + * whose sole purpose is to identify requests that are retries for the + * same resource, and pending requests, which indicates how many + * requests are still queued for this particular page version on the + * client. The latter is used to expire result data for sequence + * numbers that are no longer needed. + */ + private case class AjaxVersionInfo(renderVersion:String, sequenceNumber:Int, pendingRequests:Int) + private object AjaxVersions { + def unapply(ajaxPathPart: String) : Option[AjaxVersionInfo] = { + val dash = ajaxPathPart.indexOf("-") + if (dash > -1 && ajaxPathPart.length > dash + 2) + Some( + AjaxVersionInfo(ajaxPathPart.substring(0, dash), + ajaxPathPart.charAt(dash + 1), + Integer.parseInt(ajaxPathPart.substring(dash + 2, dash + 3), 36)) + ) + else + None + } + } + /** + * Extracts two versions from a given AJAX path: + * - The RenderVersion, which is used for GC purposes. + * - The requestVersions, which let us determine if this is + * a request we've already dealt with or are currently dealing + * with (so we don't rerun the associated handler). See + * handleVersionedAjax for more. + * + * The requestVersion is passed to the function that is passed in. + */ + private def extractVersions[T](path: List[String])(f: (Box[AjaxVersionInfo]) => T): T = { + path match { + case first :: AjaxVersions(versionInfo @ AjaxVersionInfo(renderVersion, _, _)) :: _ => + RenderVersion.doWith(renderVersion)(f(Full(versionInfo))) + case _ => f(Empty) + } + } + + /** + * Runs the actual AJAX processing. This includes handling __lift__GC, + * or running the parameters in the session. onComplete is run when the + * AJAX request has completed with a response that is meant for the + * user. In cases where the request is taking too long to respond, + * an LAFuture may be used to delay the real response (and thus the + * invocation of onComplete) while this function returns an empty + * response. + */ + private def runAjax(liftSession: LiftSession, + requestState: Req): Box[LiftResponse] = { + try { + requestState.param("__lift__GC") match { + case Full(_) => + liftSession.updateFuncByOwner(RenderVersion.get, millis) + Full(JavaScriptResponse(js.JsCmds.Noop)) + + case _ => + try { + val what = flatten(try { + liftSession.runParams(requestState) + } catch { + case ResponseShortcutException(_, Full(to), _) => + import js.JsCmds._ + List(RedirectTo(to)) + }) + + val what2 = what.flatMap { + case js: JsCmd => List(js) + case jv: JValue => List(jv) + case n: NodeSeq => List(n) + case js: JsCommands => List(js) + case r: LiftResponse => List(r) + case s => Nil + } + + val ret: LiftResponse = what2 match { + case (json: JsObj) :: Nil => JsonResponse(json) + case (jv: JValue) :: Nil => JsonResponse(jv) + case (js: JsCmd) :: xs => { + (JsCommands(S.noticesToJsCmd :: Nil) & + ((js :: xs).flatMap { + case js: JsCmd => List(js) + case _ => Nil + }.reverse) & + S.jsToAppend).toResponse + } + + case (n: Node) :: _ => XmlResponse(n) + case (ns: NodeSeq) :: _ => XmlResponse(Group(ns)) + case (r: LiftResponse) :: _ => r + case _ => JsCommands(S.noticesToJsCmd :: JsCmds.Noop :: S.jsToAppend).toResponse + } + + LiftRules.cometLogger.debug("AJAX Response: " + liftSession.uniqueId + " " + ret) + + Full(ret) + } finally { + if (S.functionMap.size > 0) { + liftSession.updateFunctionMap(S.functionMap, RenderVersion.get, millis) + S.clearFunctionMap + } + } + } + } catch { + case foc: LiftFlowOfControlException => throw foc + case e => S.assertExceptionThrown() ; NamedPF.applyBox((Props.mode, requestState, e), LiftRules.exceptionHandler.toList); + } + } + + // Retry requests will stop trying to wait for the original request to + // complete 500ms after the client's timeout. This is because, while + // we want the original thread to complete so that it can provide an + // answer for future retries, we don't want retries tying up resources + // when the client won't receive the response anyway. + private lazy val ajaxPostTimeout: Long = LiftRules.ajaxPostTimeout * 1000L + 500L + /** + * Kick off AJAX handling. Extracts relevant versions and handles the + * begin/end servicing requests. Then checks whether to wait on an + * existing request for this same version to complete or whether to + * do the actual processing. + */ private def handleAjax(liftSession: LiftSession, requestState: Req): Box[LiftResponse] = { - extractVersion(requestState.path.partPath) { - + extractVersions(requestState.path.partPath) { versionInfo => LiftRules.cometLogger.debug("AJAX Request: " + liftSession.uniqueId + " " + requestState.params) tryo { LiftSession.onBeginServicing.foreach(_(liftSession, requestState)) } - val ret = try { - requestState.param("__lift__GC") match { - case Full(_) => - liftSession.updateFuncByOwner(RenderVersion.get, millis) - Full(JavaScriptResponse(js.JsCmds.Noop)) - - case _ => - try { - val what = flatten(try { - liftSession.runParams(requestState) - } catch { - case ResponseShortcutException(_, Full(to), _) => - import js.JsCmds._ - List(RedirectTo(to)) - }) - - val what2 = what.flatMap { - case js: JsCmd => List(js) - case jv: JValue => List(jv) - case n: NodeSeq => List(n) - case js: JsCommands => List(js) - case r: LiftResponse => List(r) - case s => Nil + // Here, a Left[LAFuture] indicates a future that needs to be + // *satisfied*, meaning we will run the request processing. + // A Right[LAFuture] indicates a future we need to *wait* on, + // meaning we will return the result of whatever satisfies the + // future. + val nextAction:Either[LAFuture[Box[LiftResponse]], LAFuture[Box[LiftResponse]]] = + versionInfo match { + case Full(AjaxVersionInfo(_, handlerVersion, pendingRequests)) => + val renderVersion = RenderVersion.get + + liftSession.withAjaxRequests { currentAjaxRequests => + // Create a new future, put it in the request list, and return + // the associated info with the future that needs to be + // satisfied by the current request handler. + def newRequestInfo = { + val info = AjaxRequestInfo(handlerVersion, new LAFuture[Box[LiftResponse]], millis) + + val existing = currentAjaxRequests.getOrElseUpdate(renderVersion, Nil) + currentAjaxRequests += (renderVersion -> (info :: existing)) + + info } - val ret: LiftResponse = what2 match { - case (json: JsObj) :: Nil => JsonResponse(json) - case (jv: JValue) :: Nil => JsonResponse(jv) - case (js: JsCmd) :: xs => { - (JsCommands(S.noticesToJsCmd :: Nil) & - ((js :: xs).flatMap { - case js: JsCmd => List(js) - case _ => Nil - }.reverse) & - S.jsToAppend).toResponse - } + val infoList = currentAjaxRequests.get(renderVersion) + val (requestInfo, result) = + infoList + .flatMap { entries => + entries + .find(_.requestVersion == handlerVersion) + .map { entry => + (entry, Right(entry.responseFuture)) + } + } + .getOrElse { + val entry = newRequestInfo - case (n: Node) :: _ => XmlResponse(n) - case (ns: NodeSeq) :: _ => XmlResponse(Group(ns)) - case (r: LiftResponse) :: _ => r - case _ => JsCommands(S.noticesToJsCmd :: JsCmds.Noop :: S.jsToAppend).toResponse - } + (entry, Left(entry.responseFuture)) + } - LiftRules.cometLogger.debug("AJAX Response: " + liftSession.uniqueId + " " + ret) + // If there are no other pending requests, we can + // invalidate all the render version's AJAX entries except + // for the current one, as the client is no longer looking + // to retry any of them. + if (pendingRequests == 0) { + // Satisfy anyone waiting on futures for invalid + // requests with a failure. + for { + list <- infoList + entry <- list if entry.requestVersion != handlerVersion + } { + entry.responseFuture.satisfy(Failure("Request no longer pending.")) + } - Full(ret) - } finally { - if (S.functionMap.size > 0) { - liftSession.updateFunctionMap(S.functionMap, RenderVersion.get, millis) - S.clearFunctionMap + currentAjaxRequests += (renderVersion -> List(requestInfo)) } + + result } + + case _ => + // Create a future that processes the ajax response + // immediately. This runs if we don't have a handler + // version, which happens in cases like AJAX requests for + // Lift GC that don't go through the de-duping pipeline. + // Because we always return a Left here, the ajax processing + // always runs for this type of request. + Left(new LAFuture[Box[LiftResponse]]) } - } catch { - case foc: LiftFlowOfControlException => throw foc - case e => S.assertExceptionThrown() ; NamedPF.applyBox((Props.mode, requestState, e), LiftRules.exceptionHandler.toList); - } + + val ret:Box[LiftResponse] = + nextAction match { + case Left(future) => + val result = runAjax(liftSession, requestState) + future.satisfy(result) + + result + + case Right(future) => + val ret = future.get(ajaxPostTimeout) openOr Failure("AJAX retry timeout.") + + ret + } + tryo { LiftSession.onEndServicing.foreach(_(liftSession, requestState, ret)) } + ret } } From fd2bbba0fe96cdc29439516f68bc8623515a8571 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Fri, 28 Sep 2012 16:35:35 -0400 Subject: [PATCH 0187/1949] Track sequence numbers of arbitrary length. We track sequence numbers as Longs now, and the client side encodes the sequence number as an arbitrarily-long base-36 number. --- .../main/scala/net/liftweb/http/LiftServlet.scala | 12 ++++++------ .../scala/net/liftweb/http/js/ScriptRenderer.scala | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala index 15632a5a91..1fc3ab2d46 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala @@ -431,15 +431,15 @@ class LiftServlet extends Loggable { * client. The latter is used to expire result data for sequence * numbers that are no longer needed. */ - private case class AjaxVersionInfo(renderVersion:String, sequenceNumber:Int, pendingRequests:Int) + private case class AjaxVersionInfo(renderVersion:String, sequenceNumber:Long, pendingRequests:Int) private object AjaxVersions { def unapply(ajaxPathPart: String) : Option[AjaxVersionInfo] = { - val dash = ajaxPathPart.indexOf("-") - if (dash > -1 && ajaxPathPart.length > dash + 2) + val separator = ajaxPathPart.indexOf("-") + if (separator > -1 && ajaxPathPart.length > separator + 2) Some( - AjaxVersionInfo(ajaxPathPart.substring(0, dash), - ajaxPathPart.charAt(dash + 1), - Integer.parseInt(ajaxPathPart.substring(dash + 2, dash + 3), 36)) + AjaxVersionInfo(ajaxPathPart.substring(0, separator), + java.lang.Long.parseLong(ajaxPathPart.substring(separator + 1, ajaxPathPart.length - 1), 36), + Integer.parseInt(ajaxPathPart.substring(ajaxPathPart.length - 1), 36)) ) else None diff --git a/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala b/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala index 1921da5e07..a7a783e7b3 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala @@ -192,7 +192,7 @@ object ScriptRenderer { if (LiftRules.enableLiftGC) { """ var replacement = '""" + LiftRules.ajaxPath + """/'+lift_page; if (version) - replacement += ('-'+(version%36).toString(36)) + (liftAjax.lift_ajaxQueue.length > 35 ? 35 : liftAjax.lift_ajaxQueue.length).toString(36); + replacement += ('-'+version.toString(36)) + (liftAjax.lift_ajaxQueue.length > 35 ? 35 : liftAjax.lift_ajaxQueue.length).toString(36); return url.replace('""" + LiftRules.ajaxPath + """', replacement);""" } else { "return url;" From 3c3a404b19d7eb0f62a9102f8428d724a377181c Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Fri, 28 Sep 2012 16:50:06 -0400 Subject: [PATCH 0188/1949] Guard for JS number size overflows. If you hit the maximum of a JS integer, math starts getting wonky. We make sure at that point we wrap to 0. --- .../src/main/scala/net/liftweb/http/js/ScriptRenderer.scala | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala b/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala index a7a783e7b3..be342d70d8 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala @@ -48,6 +48,11 @@ object ScriptRenderer { toSend.responseType = responseType; toSend.version = liftAjax.lift_ajaxVersion++; + // Make sure we wrap when we hit JS max int. + var version = liftAjax.lift_ajaxVersion + if ((version - (version + 1) != -1) || (version - (version - 1) != 1)) + liftAjax.lift_ajaxVersion = 0; + if (liftAjax.lift_uriSuffix) { theData += '&' + liftAjax.lift_uriSuffix; toSend.theData = theData; From f785ddb6539c7f310c37401837c0bb3fdb243b74 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Tue, 2 Oct 2012 15:03:08 -0400 Subject: [PATCH 0189/1949] Make AjaxRequestInfo track a Long version id. Not sure how I got this to compile locally without this change... --- web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index 081901398a..315bb6d124 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -504,8 +504,8 @@ private final case class PostPageFunctions(renderVersion: String, * thread when the response has been calculated. Retries will wait for the * future to be satisfied in order to return the proper response. */ -private[http] final case class AjaxRequestInfo(requestVersion:Int, - responseFuture:LAFuture[Box[LiftResponse]], +private[http] final case class AjaxRequestInfo(requestVersion: Long, + responseFuture: LAFuture[Box[LiftResponse]], lastSeen: Long) /** From fdce4ea1f8f4dce97931edb45224f22da41186a8 Mon Sep 17 00:00:00 2001 From: Naftoli Gugenheim Date: Wed, 19 Sep 2012 13:08:55 -0400 Subject: [PATCH 0190/1949] Remove ToCssBindPromoter implicits in package object --- .../src/main/scala/net/liftweb/util/package.scala | 12 ------------ .../scala/net/liftweb/util/CssSelectorSpec.scala | 3 +++ 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/package.scala b/core/util/src/main/scala/net/liftweb/util/package.scala index 10d796fdc9..48d4d85e2b 100644 --- a/core/util/src/main/scala/net/liftweb/util/package.scala +++ b/core/util/src/main/scala/net/liftweb/util/package.scala @@ -32,18 +32,6 @@ package object util { @deprecated("Use Schedule", "2.3") val ActorPing = Schedule - /** - * promote a String to a ToCssBindPromotor - */ - implicit def strToCssBindPromoter(str: String): ToCssBindPromoter = - new ToCssBindPromoter(Full(str), CssSelectorParser.parse(str)) - - /** - * promote a String to a ToCssBindPromotor - */ - implicit def cssSelectorToCssBindPromoter(sel: CssSelector): ToCssBindPromoter = - new ToCssBindPromoter(Empty, Full(sel)) - /** * Wrap a function and make sure it's a NodeSeq => NodeSeq. Much easier * than explicitly casting the first parameter diff --git a/core/util/src/test/scala/net/liftweb/util/CssSelectorSpec.scala b/core/util/src/test/scala/net/liftweb/util/CssSelectorSpec.scala index a21e2e616b..a6a7bab767 100644 --- a/core/util/src/test/scala/net/liftweb/util/CssSelectorSpec.scala +++ b/core/util/src/test/scala/net/liftweb/util/CssSelectorSpec.scala @@ -22,6 +22,9 @@ import org.specs2.mutable.Specification import common._ import scala.xml._ +import BindHelpers._ + + /** * Systems under specification for CSS Selector. */ From 5ee8231a944818fe9d7a2e884fcc4a06816c9583 Mon Sep 17 00:00:00 2001 From: Naftoli Gugenheim Date: Wed, 19 Sep 2012 13:12:57 -0400 Subject: [PATCH 0191/1949] Allow, once again, to bind anything that is convertable to NS=>NS --- core/util/src/main/scala/net/liftweb/util/CssSel.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/CssSel.scala b/core/util/src/main/scala/net/liftweb/util/CssSel.scala index 7c30b3fbf4..1fc7f76cf1 100644 --- a/core/util/src/main/scala/net/liftweb/util/CssSel.scala +++ b/core/util/src/main/scala/net/liftweb/util/CssSel.scala @@ -785,8 +785,8 @@ object ComputeTransformRules { def computeTransform(param: => T, ns: NodeSeq): Seq[NodeSeq] = List(f(param)) } - implicit def nodeSeqFuncTransform: ComputeTransformRules[NodeSeq => NodeSeq] = new ComputeTransformRules[NodeSeq => NodeSeq] { - def computeTransform(func: => NodeSeq => NodeSeq, ns: NodeSeq): Seq[NodeSeq] = List(func(ns)) + implicit def nodeSeqFuncTransform[A](implicit view: A => NodeSeq => NodeSeq): ComputeTransformRules[A] = new ComputeTransformRules[A] { + def computeTransform(func: =>A, ns: NodeSeq): Seq[NodeSeq] = List(view(func)(ns)) } implicit def nodeSeqSeqFuncTransform: ComputeTransformRules[NodeSeq => Seq[Node]] = new ComputeTransformRules[NodeSeq => Seq[Node]] { @@ -908,4 +908,4 @@ final case class ToCssBindPromoter(stringSelector: Box[String], css: Box[CssSele * @return the function that will transform an incoming DOM based on the transform rules */ def replaceWith[T](it: => T)(implicit computer: ComputeTransformRules[T]): CssSel = this.#>(it)(computer) -} \ No newline at end of file +} From ed623a2feaa52111b615c791223a1b95df11c98d Mon Sep 17 00:00:00 2001 From: David Pollak Date: Sat, 6 Oct 2012 07:56:55 -0700 Subject: [PATCH 0192/1949] Added a method to release all the long polling connections --- .../main/scala/net/liftweb/http/LiftSession.scala | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index dc206fd6c2..8f9dcff838 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -87,6 +87,8 @@ object LiftSession { */ var onEndServicing: List[(LiftSession, Req, Box[LiftResponse]) => Unit] = Nil + + @volatile private var constructorCache: Map[(Class[_], Box[Class[_]]), Box[ConstructorType]] = Map() private[http] def constructFrom[T](session: LiftSession, pp: Box[ParamPair], clz: Class[T]): Box[T] = { @@ -211,6 +213,18 @@ object SessionMaster extends LiftActor with Loggable { } } + + /** + * End comet long polling for all sessions. This allows a clean reload of Nginx + * because Nginx children stick around for long polling. + */ + def breakOutAllComet() { + val ses = lockRead(sessions) + ses.valuesIterator.foreach { + _.session.breakOutComet() + } + } + def getSession(id: String, otherId: Box[String]): Box[LiftSession] = lockAndBump { otherId.flatMap(sessions.get) or Box(sessions.get(id)) } From 51d3bebbfd0a9720247d5209f801ab5f3e78eb23 Mon Sep 17 00:00:00 2001 From: Joni Freeman Date: Mon, 8 Oct 2012 10:39:24 +0300 Subject: [PATCH 0193/1949] Fix off-by one when escaping control char in JSON --- core/json/src/main/scala/net/liftweb/json/JsonAST.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/json/src/main/scala/net/liftweb/json/JsonAST.scala b/core/json/src/main/scala/net/liftweb/json/JsonAST.scala index 69767e5097..1810e01a55 100644 --- a/core/json/src/main/scala/net/liftweb/json/JsonAST.scala +++ b/core/json/src/main/scala/net/liftweb/json/JsonAST.scala @@ -427,7 +427,7 @@ object JsonAST { case '\n' => "\\n" case '\r' => "\\r" case '\t' => "\\t" - case c if ((c >= '\u0000' && c < '\u001f') || (c >= '\u0080' && c < '\u00a0') || (c >= '\u2000' && c < '\u2100')) => "\\u%04x".format(c: Int) + case c if ((c >= '\u0000' && c < '\u0020') || (c >= '\u0080' && c < '\u00a0') || (c >= '\u2000' && c < '\u2100')) => "\\u%04x".format(c: Int) case c => c }) } From 6a8d75d908e58c555b1e087c202541dfa9a391e4 Mon Sep 17 00:00:00 2001 From: Joni Freeman Date: Wed, 10 Oct 2012 08:30:13 +0300 Subject: [PATCH 0194/1949] Do not escape unnecessarily ranges which JSON spec does not require. Fix memoization --- .../json/src/main/scala/net/liftweb/json/JsonAST.scala | 2 +- core/json/src/main/scala/net/liftweb/json/Meta.scala | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/core/json/src/main/scala/net/liftweb/json/JsonAST.scala b/core/json/src/main/scala/net/liftweb/json/JsonAST.scala index 1810e01a55..cef3aa34d5 100644 --- a/core/json/src/main/scala/net/liftweb/json/JsonAST.scala +++ b/core/json/src/main/scala/net/liftweb/json/JsonAST.scala @@ -427,7 +427,7 @@ object JsonAST { case '\n' => "\\n" case '\r' => "\\r" case '\t' => "\\t" - case c if ((c >= '\u0000' && c < '\u0020') || (c >= '\u0080' && c < '\u00a0') || (c >= '\u2000' && c < '\u2100')) => "\\u%04x".format(c: Int) + case c if ((c >= '\u0000' && c < '\u0020')) => "\\u%04x".format(c: Int) case c => c }) } diff --git a/core/json/src/main/scala/net/liftweb/json/Meta.scala b/core/json/src/main/scala/net/liftweb/json/Meta.scala index e9fa49218d..668a801fd4 100644 --- a/core/json/src/main/scala/net/liftweb/json/Meta.scala +++ b/core/json/src/main/scala/net/liftweb/json/Meta.scala @@ -191,14 +191,16 @@ private[json] object Meta { private[json] def fail(msg: String, cause: Exception = null) = throw new MappingException(msg, cause) private class Memo[A, R] { - @volatile private var cache = Map[A, R]() + private val cache = new java.util.concurrent.atomic.AtomicReference(Map[A, R]()) - def memoize(x: A, f: A => R): R = - if (cache contains x) cache(x) else { + def memoize(x: A, f: A => R): R = { + val c = cache.get + if (c contains x) c(x) else { val ret = f(x) - cache += (x -> ret) + cache.set(c + (x -> ret)) ret } + } } object Reflection { From 8b527fc2fd8687d270758e379d3c41bc02610695 Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Thu, 11 Oct 2012 09:33:01 -0400 Subject: [PATCH 0195/1949] Fixed #1330 - HTTP basic authentication not working in 2.5 we lost an important "else" in LiftServlet - (Thanks to Aditya Vishwakarma for pointing it out) --- web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala index 8bafb9ce59..747c3d90e9 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala @@ -261,9 +261,8 @@ class LiftServlet extends Loggable { Full(new JsCommands(cmd :: Nil).toResponse) } - } - // if the request is matched is defined in the stateless table, dispatch - if (S.statelessInit(req) { + } else if (S.statelessInit(req) { + // if the request is matched is defined in the stateless table, dispatch tmpStatelessHolder = NamedPF.applyBox(req, LiftRules.statelessDispatch.toList).map(_.apply() match { case Full(a) => Full(LiftRules.convertResponse((a, Nil, S.responseCookies, req))) From f2de993c8c568c994d4b2a04daf22b21f459b7bf Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Thu, 11 Oct 2012 16:36:08 -0400 Subject: [PATCH 0196/1949] Use ThreadLocalRandom in StringHelpers.randomString on Java 7. On Java <7, we use the default SecureRandom, which locks on access. ThreadLocalRandom will not lock as it is thread-local (!). --- .../net/liftweb/util/StringHelpers.scala | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/StringHelpers.scala b/core/util/src/main/scala/net/liftweb/util/StringHelpers.scala index 1b3715976a..b223fee77d 100644 --- a/core/util/src/main/scala/net/liftweb/util/StringHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/StringHelpers.scala @@ -19,6 +19,7 @@ package util import java.security.SecureRandom import java.util.regex._ +import java.util.Random import java.lang.Character._ import java.lang.{StringBuilder => GoodSB} import scala.xml.NodeSeq @@ -32,7 +33,23 @@ object StringHelpers extends StringHelpers trait StringHelpers { /** random numbers generator */ - private val _random = new SecureRandom + private lazy val _slowRandom = new SecureRandom + private lazy val _currentTlrMethod = { + try { + val tlr = Class.forName("java.util.concurrent.ThreadLocalRandom") + Full(tlr.getMethod("current")) + } catch { + case e => + Failure("ThreadLocalRandom is not available.", Full(e), Empty) + } + } + private def withRng[T](block: (Random)=>T) = { + _currentTlrMethod.map { meth => + block(meth.invoke(null).asInstanceOf[Random]) + } openOr { + _slowRandom.synchronized(block(_slowRandom)) + } + } /** * If str is surrounded by quotes it return the content between the quotes @@ -176,7 +193,7 @@ trait StringHelpers { if (pos >= size) sb else { val randNum = if ((pos % 6) == 0) { - _random.synchronized(_random.nextInt) + withRng(_.nextInt) } else { lastRand } From f35e4e0c1b4ff5ea7f95814b58a1997f0698b533 Mon Sep 17 00:00:00 2001 From: Naftoli Gugenheim Date: Wed, 19 Sep 2012 13:00:09 -0400 Subject: [PATCH 0197/1949] Only catch Exception --- core/json/src/main/scala/net/liftweb/json/JsonParser.scala | 2 +- core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala | 2 +- .../mapper/src/main/scala/net/liftweb/mapper/FieldFinder.scala | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala index b98038c445..2a39238b65 100644 --- a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala +++ b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala @@ -237,7 +237,7 @@ object JsonParser { unquote(buf) } catch { case p: ParseException => throw p - case _ => fail("unexpected string end") + case _: Exception => fail("unexpected string end") } def parseValue(first: Char) = { diff --git a/core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala b/core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala index 5706d6f710..15d1f7c491 100644 --- a/core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala @@ -432,7 +432,7 @@ trait TimeHelpers { self: ControlHelpers => case o => toDate(o.toString) } } catch { - case e => logger.debug("Error parsing date "+in, e); Failure("Bad date: "+in, Full(e), Empty) + case e: Exception => logger.debug("Error parsing date "+in, e); Failure("Bad date: "+in, Full(e), Empty) } } } diff --git a/persistence/mapper/src/main/scala/net/liftweb/mapper/FieldFinder.scala b/persistence/mapper/src/main/scala/net/liftweb/mapper/FieldFinder.scala index d881cf507c..4f191862c0 100644 --- a/persistence/mapper/src/main/scala/net/liftweb/mapper/FieldFinder.scala +++ b/persistence/mapper/src/main/scala/net/liftweb/mapper/FieldFinder.scala @@ -83,7 +83,7 @@ class FieldFinder[T: ClassManifest](metaMapper: AnyRef, logger: net.liftweb.comm } } catch { - case e => + case e: Exception => logger.debug("Not a valid mapped field: %s, got exception: %s".format(meth.getName, e)) false } From e5e3f7e7e9bf309ae3a8c3438af012dbf12fd4cf Mon Sep 17 00:00:00 2001 From: Naftoli Gugenheim Date: Wed, 19 Sep 2012 12:51:41 -0400 Subject: [PATCH 0198/1949] Css: Parse failures return ParamFailure, not Empty --- core/util/src/main/scala/net/liftweb/util/CssSelector.scala | 2 +- core/util/src/test/scala/net/liftweb/util/CssSelectorSpec.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/CssSelector.scala b/core/util/src/main/scala/net/liftweb/util/CssSelector.scala index f7851d2283..72e7f8eb40 100644 --- a/core/util/src/main/scala/net/liftweb/util/CssSelector.scala +++ b/core/util/src/main/scala/net/liftweb/util/CssSelector.scala @@ -151,7 +151,7 @@ object CssSelectorParser extends PackratParsers with ImplicitConversions { val reader: Input = new CharSequenceReader(toParse, 0) topParser(reader) match { case Success(v, _) => Full(v) - case x => Empty + case x: NoSuccess => ParamFailure(x.msg, Empty, Empty, x) } } diff --git a/core/util/src/test/scala/net/liftweb/util/CssSelectorSpec.scala b/core/util/src/test/scala/net/liftweb/util/CssSelectorSpec.scala index a6a7bab767..2dec8bcbd0 100644 --- a/core/util/src/test/scala/net/liftweb/util/CssSelectorSpec.scala +++ b/core/util/src/test/scala/net/liftweb/util/CssSelectorSpec.scala @@ -33,7 +33,7 @@ object CssSelectorSpec extends Specification { "CssSelector" should { "fail for garbage input" in { - CssSelectorParser.parse(" 49234e23") must_== Empty + CssSelectorParser.parse(" 49234e23").isDefined must_== false } "select an id" in { From 55811dbaaace90f2ceb630e6e851729f54683714 Mon Sep 17 00:00:00 2001 From: Dave Briccetti Date: Tue, 23 Oct 2012 23:07:47 -0700 Subject: [PATCH 0199/1949] =?UTF-8?q?Make=20minor=20improvements=20to=20co?= =?UTF-8?q?mments=20in=20ProtoUser.scala,=20primarily=20to=20fix=20a=20"th?= =?UTF-8?q?e=20the.=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/scala/net/liftweb/record/ProtoUser.scala | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/persistence/record/src/main/scala/net/liftweb/record/ProtoUser.scala b/persistence/record/src/main/scala/net/liftweb/record/ProtoUser.scala index 488bf68e2e..c583b49d57 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/ProtoUser.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/ProtoUser.scala @@ -33,7 +33,7 @@ import net.liftweb.record.field._ import net.liftweb.proto.{ProtoUser => GenProtoUser} /** - * ProtoUser is a base class that gives you a "User" that has a first name, + * ProtoUser is a base class that gives you a "User" with a first name, * last name, email, etc. */ trait ProtoUser[T <: ProtoUser[T]] extends Record[T] { @@ -177,7 +177,7 @@ trait ProtoUser[T <: ProtoUser[T]] extends Record[T] { } /** - * Mix this trait into the the Mapper singleton for User and you + * Mix this trait into the Mapper singleton for User and you * get a bunch of user functionality including password reset, etc. */ trait MetaMegaProtoUser[ModelType <: MegaProtoUser[ModelType]] extends MetaRecord[ModelType] with GenProtoUser { @@ -203,7 +203,7 @@ trait MetaMegaProtoUser[ModelType <: MegaProtoUser[ModelType]] extends MetaRecor def displayHtml: NodeSeq = from.displayHtml /** - * Does this represent a pointer to a Password field + * Does this represent a pointer to a Password field? */ def isPasswordField_? : Boolean = from match { case a: PasswordField[_] => true @@ -366,7 +366,7 @@ trait MegaProtoUser[T <: MegaProtoUser[T]] extends ProtoUser[T] { } /** - * The has the user been validated. + * Whether the user has been validated. * You can override the behavior * of this field: *
    @@ -427,4 +427,3 @@ trait MegaProtoUser[T <: MegaProtoUser[T]] extends ProtoUser[T] {
       def localeDisplayName = ??("locale")
     
     }
    -
    
    From 562d4b1fe106f7f10732f4230ef15140645a4441 Mon Sep 17 00:00:00 2001
    From: Dave Briccetti 
    Date: Tue, 23 Oct 2012 23:30:42 -0700
    Subject: [PATCH 0200/1949] Improve another comment in ProtoUser
    
    ---
     .../record/src/main/scala/net/liftweb/record/ProtoUser.scala   | 3 +--
     1 file changed, 1 insertion(+), 2 deletions(-)
    
    diff --git a/persistence/record/src/main/scala/net/liftweb/record/ProtoUser.scala b/persistence/record/src/main/scala/net/liftweb/record/ProtoUser.scala
    index 45c94127b8..8fd74bce25 100644
    --- a/persistence/record/src/main/scala/net/liftweb/record/ProtoUser.scala
    +++ b/persistence/record/src/main/scala/net/liftweb/record/ProtoUser.scala
    @@ -33,8 +33,7 @@ import net.liftweb.record.field._
     import net.liftweb.proto.{ProtoUser => GenProtoUser}
     
     /**
    - * ProtoUser is a base class that gives you a "User" with a first name,
    - * last name, email, etc.
    + * ProtoUser provides a "User" with a first name, last name, email, etc.
      */
     trait ProtoUser[T <: ProtoUser[T]] extends Record[T] {
       self: T =>
    
    From 00812dd94450a984cf2577db1e503f20df93881b Mon Sep 17 00:00:00 2001
    From: Tim Nelson 
    Date: Sat, 20 Oct 2012 14:28:08 -0500
    Subject: [PATCH 0201/1949] Deprecated MongoId
    
    ---
     .../src/main/scala/net/liftweb/mongodb/record/MongoRecord.scala  | 1 +
     1 file changed, 1 insertion(+)
    
    diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/MongoRecord.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/MongoRecord.scala
    index 122d027cab..2e803ef0c7 100644
    --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/MongoRecord.scala
    +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/MongoRecord.scala
    @@ -96,6 +96,7 @@ trait MongoRecord[MyType <: MongoRecord[MyType]] extends BsonRecord[MyType] {
     /**
     * Mix this into a Record to add an ObjectIdField
     */
    +@deprecated("Use one of the MongoPK traits instead", "2.5")
     trait MongoId[OwnerType <: MongoRecord[OwnerType]] {
       self: OwnerType =>
     
    
    From 8e6cb4b59c349b250a3874b9a154410c67331d37 Mon Sep 17 00:00:00 2001
    From: Tim Nelson 
    Date: Sat, 20 Oct 2012 16:40:56 -0500
    Subject: [PATCH 0202/1949] Issue 1294 - Add formats to DateTimeField
    
    ---
     .../scala/net/liftweb/util/TimeHelpers.scala  | 10 ++++----
     .../liftweb/record/field/DateTimeField.scala  | 14 +++++++----
     .../scala/net/liftweb/record/FieldSpec.scala  | 15 ++++++++++-
     .../scala/net/liftweb/record/Fixtures.scala   | 25 +++++++++++++++----
     4 files changed, 48 insertions(+), 16 deletions(-)
    
    diff --git a/core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala b/core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala
    index 15d1f7c491..c8d1cf0217 100644
    --- a/core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala
    +++ b/core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala
    @@ -89,7 +89,7 @@ trait TimeHelpers { self: ControlHelpers =>
       class TimeSpan(private val dt: Either[DateTime, Period]) extends ConvertableToDate {
         /** @return a Date as the amount of time represented by the TimeSpan after the Epoch date */
     
    -    def this(ms: Long) = 
    +    def this(ms: Long) =
           this(if (ms < 52L * 7L * 24L * 60L * 60L * 1000L) Right(new Period(ms))
                else Left(new DateTime(ms)))
     
    @@ -102,7 +102,7 @@ trait TimeHelpers { self: ControlHelpers =>
          * Convert to a Date
          */
         def toDate: Date = date
    -    
    +
         /**
          * Convert to a JodaTime DateTime
          */
    @@ -117,7 +117,7 @@ trait TimeHelpers { self: ControlHelpers =>
           case Left(datetime) => datetime.getMillis()
           case Right(duration) => duration.toStandardDuration.getMillis()
         }
    -    
    +
     
         /** @return a Date as the amount of time represented by the TimeSpan after now */
         def later: TimeSpan = dt match {
    @@ -397,7 +397,7 @@ trait TimeHelpers { self: ControlHelpers =>
         ret
       }
     
    -  /** @return a date from a string using the internet format. Return the Epoch date if the parse is unsuccesfull */
    +  /** @return a Box[date] from a string using the internet format. */
       def boxParseInternetDate(dateString: String): Box[Date] = tryo {
         internetDateFormatter.parse(dateString)
       }
    @@ -447,6 +447,6 @@ object ConvertableToDate {
       implicit def toDate(in: ConvertableToDate): Date = in.toDate
       implicit def toDateTime(in: ConvertableToDate): DateTime = in.toDateTime
       implicit def toMillis(in: ConvertableToDate): Long = in.millis
    -  
    +
     }
     
    diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/DateTimeField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/DateTimeField.scala
    index 05f1d1421f..a8e218e22a 100644
    --- a/persistence/record/src/main/scala/net/liftweb/record/field/DateTimeField.scala
    +++ b/persistence/record/src/main/scala/net/liftweb/record/field/DateTimeField.scala
    @@ -1,5 +1,5 @@
     /*
    - * Copyright 2007-2011 WorldWide Conferencing, LLC
    + * 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.
    @@ -22,7 +22,7 @@ import scala.xml._
     import net.liftweb.common._
     import net.liftweb.http.{S}
     import net.liftweb.http.js._
    -import net.liftweb.json.JsonAST.JValue
    +import net.liftweb.json._
     import net.liftweb.util._
     import java.util.{Calendar, Date}
     import Helpers._
    @@ -36,6 +36,10 @@ trait DateTimeTypedField extends TypedField[Calendar] {
         cal
       }
     
    +  val formats = new DefaultFormats {
    +    override def dateFormatter = Helpers.internetDateFormatter
    +  }
    +
       def setFromAny(in : Any): Box[Calendar] = toDate(in).flatMap(d => setBox(Full(dateToCal(d)))) or genericSetFromAny(in)
     
       def setFromString(s: String): Box[Calendar] = s match {
    @@ -57,11 +61,11 @@ trait DateTimeTypedField extends TypedField[Calendar] {
           case _        => Full(elem)
         }
     
    -  def asJs = valueBox.map(v => Str(toInternetDate(v.getTime))) openOr JsNull
    +  def asJs = valueBox.map(v => Str(formats.dateFormat.format(v.getTime))) openOr JsNull
     
    -  def asJValue = asJString(v => toInternetDate(v.getTime))
    +  def asJValue = asJString(v => formats.dateFormat.format(v.getTime))
       def setFromJValue(jvalue: JValue) = setFromJString(jvalue) {
    -    v => boxParseInternetDate(v).map(d => {
    +    v => formats.dateFormat.parse(v).map(d => {
           val cal = Calendar.getInstance
           cal.setTime(d)
           cal
    diff --git a/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala b/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala
    index 7988b69354..e3ae81f182 100644
    --- a/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala
    +++ b/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala
    @@ -49,7 +49,7 @@ object FieldSpec extends Specification {
           !mandatory.defaultValue.isInstanceOf[Calendar] // don't try to use the default value of date/time typed fields, because it changes from moment to moment!
     
         def commonBehaviorsForMandatory(in: MandatoryTypedField[A]): Unit = {
    -      
    +
     		if (canCheckDefaultValues) {
         			"which have the correct initial value" in S.initIfUninitted(session) {
     				in.get must_== in.defaultValue
    @@ -326,6 +326,19 @@ object FieldSpec extends Specification {
         )
       }
     
    +  "DateTimeField with custom format" should {
    +    val rec = CustomFormatDateTimeRecord.createRecord
    +    val dt = Calendar.getInstance
    +    val dtStr = rec.customFormatDateTimeField.formats.dateFormat.format(dt.getTime)
    +    passConversionTests(
    +      dt,
    +      rec.customFormatDateTimeField,
    +      Str(dtStr),
    +      JString(dtStr),
    +      Full()
    +    )
    +  }
    +
       "DecimalField" should {
         val rec = FieldTypeTestRecord.createRecord
         val bd = BigDecimal("12.34")
    diff --git a/persistence/record/src/test/scala/net/liftweb/record/Fixtures.scala b/persistence/record/src/test/scala/net/liftweb/record/Fixtures.scala
    index 54fea1faf5..7582143341 100644
    --- a/persistence/record/src/test/scala/net/liftweb/record/Fixtures.scala
    +++ b/persistence/record/src/test/scala/net/liftweb/record/Fixtures.scala
    @@ -14,13 +14,14 @@
      * limitations under the License.
      */
     
    -package net.liftweb 
    -package record 
    -package fixtures 
    +package net.liftweb
    +package record
    +package fixtures
     
     import java.math.MathContext
     import scala.xml.Text
     import common.{Box, Empty, Full}
    +import json._
     import util.{FieldError, Helpers}
     import org.specs2.mutable._
     
    @@ -45,7 +46,7 @@ class PasswordTestRecord private () extends Record[PasswordTestRecord] {
         override def validations = validateNonEmptyPassword _ ::
         super.validations
     
    -    def validateNonEmptyPassword(v: String): List[FieldError] = 
    +    def validateNonEmptyPassword(v: String): List[FieldError] =
           v match {
             case "testvalue" => Text("no way!")
             case _ => Nil
    @@ -238,7 +239,7 @@ object FieldTypeTestRecord extends FieldTypeTestRecord with MetaRecord[FieldType
     trait SyntheticTestTrait{
     
       val genericField: StringField[_]
    -  
    +
     }
     
     class SyntheticTestRecord extends Record[SyntheticTestRecord] with SyntheticTestTrait{
    @@ -251,3 +252,17 @@ class SyntheticTestRecord extends Record[SyntheticTestRecord] with SyntheticTest
     
     object SyntheticTestRecord extends SyntheticTestRecord with MetaRecord[SyntheticTestRecord]
     
    +class CustomFormatDateTimeRecord private () extends Record[CustomFormatDateTimeRecord] {
    +  import java.text.SimpleDateFormat
    +
    +  def meta = CustomFormatDateTimeRecord
    +
    +  object customFormatDateTimeField extends DateTimeField(this) {
    +    override val formats = new DefaultFormats {
    +      override def dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
    +    }
    +  }
    +
    +}
    +
    +object CustomFormatDateTimeRecord extends CustomFormatDateTimeRecord with MetaRecord[CustomFormatDateTimeRecord]
    
    From e578df9e39df4c9941cc0a1451e9f0a26a2a14ce Mon Sep 17 00:00:00 2001
    From: Tim Nelson 
    Date: Sat, 20 Oct 2012 17:32:18 -0500
    Subject: [PATCH 0203/1949] Issue 1314 - Implement setFromJValue in
     JObjectField
    
    ---
     .../mongodb/record/field/JObjectField.scala   | 27 +++++++++++--------
     .../net/liftweb/mongodb/record/Fixtures.scala |  9 +++++++
     .../mongodb/record/MongoFieldSpec.scala       | 22 +++++++++++++++
     .../scala/net/liftweb/record/Fixtures.scala   |  2 +-
     4 files changed, 48 insertions(+), 12 deletions(-)
    
    diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala
    index 1e81d6f7da..d7f9797aa4 100644
    --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala
    +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala
    @@ -19,23 +19,28 @@ package mongodb
     package record
     package field
     
    -import common.{Box, Empty, Failure, Full}
    -import http.js.JE.Str
    -import json.JsonAST.{JNothing, JObject, JValue}
    -import json.JsonParser
    -import net.liftweb.record.{Field, MandatoryTypedField, Record}
    +import common._
    +import http.js.JE._
    +import json._
    +import util.Helpers.tryo
    +import net.liftweb.record.{Field, FieldHelpers, MandatoryTypedField, Record}
     
     import scala.xml.NodeSeq
     
     class JObjectField[OwnerType <: Record[OwnerType]](rec: OwnerType) extends Field[JObject, OwnerType] with MandatoryTypedField[JObject] {
     
    -  def asJs = Str(toString)
    -
    -  def asJValue = (JNothing: JValue) // not implemented
    +  def asJs = asJValue match {
    +    case JNothing => JsNull
    +    case jv => JsRaw(compact(render(jv)))
    +  }
     
    -  def setFromJValue(jvalue: JValue) = Empty // not implemented
    +  def asJValue = valueBox openOr (JNothing: JValue)
     
    -  def asXHtml = 
    + def setFromJValue(jvalue: JValue): Box[JObject] = jvalue match { + case JNothing|JNull if optional_? => setBox(Empty) + case jo: JObject => setBox(Full(jo)) + case other => setBox(FieldHelpers.expectedA("JObject", other)) + } def defaultValue = JObject(List()) @@ -54,7 +59,7 @@ class JObjectField[OwnerType <: Record[OwnerType]](rec: OwnerType) extends Field // assume string is json def setFromString(in: String): Box[JObject] = { // use lift-json to parse string into a JObject - Full(set(JsonParser.parse(in).asInstanceOf[JObject])) + setBox(tryo(JsonParser.parse(in).asInstanceOf[JObject])) } def toForm: Box[NodeSeq] = Empty diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala index ec88b17892..519e5eb3b8 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala @@ -485,3 +485,12 @@ class RefFieldTestRecord private () extends MongoRecord[RefFieldTestRecord] with object RefFieldTestRecord extends RefFieldTestRecord with MongoMetaRecord[RefFieldTestRecord] { override def formats = allFormats } + + +class JObjectFieldTestRecord private () extends Record[JObjectFieldTestRecord] { + def meta = JObjectFieldTestRecord + + object mandatoryJObjectField extends JObjectField(this) +} + +object JObjectFieldTestRecord extends JObjectFieldTestRecord with MetaRecord[JObjectFieldTestRecord] diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala index 72913b67e7..d968d26ae0 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala @@ -473,5 +473,27 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundExample ) } } + + "JObjectField" should { + val jo: JValue = ("minutes" -> 59) + val json: JObject = ("mandatoryJObjectField" -> jo) + + "convert to JValue" in { + val rec = JObjectFieldTestRecord.createRecord + .mandatoryJObjectField(json) + + rec.mandatoryJObjectField.asJValue must_== json + + } + "get set from JValue" in { + val fromJson = JObjectFieldTestRecord.fromJValue(json) + + fromJson.isDefined must_== true + fromJson foreach { r => + r.asJValue must_== json + } + success + } + } } diff --git a/persistence/record/src/test/scala/net/liftweb/record/Fixtures.scala b/persistence/record/src/test/scala/net/liftweb/record/Fixtures.scala index 7582143341..87e0a20d13 100644 --- a/persistence/record/src/test/scala/net/liftweb/record/Fixtures.scala +++ b/persistence/record/src/test/scala/net/liftweb/record/Fixtures.scala @@ -1,5 +1,5 @@ /* - * Copyright 2010-2011 WorldWide Conferencing, LLC + * 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. From 840459df5a2331812687a52d5da1634a44a207d7 Mon Sep 17 00:00:00 2001 From: Tim Nelson Date: Sat, 20 Oct 2012 19:14:12 -0500 Subject: [PATCH 0204/1949] Issue 1336 - Move validations in OptionalEmailField and OptionalPostalCodeField from TypedField --- .../net/liftweb/record/field/EmailField.scala | 6 ++- .../record/field/PostalCodeField.scala | 21 +++++--- .../scala/net/liftweb/record/FieldSpec.scala | 52 ++++++++++++++++++- 3 files changed, 68 insertions(+), 11 deletions(-) diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/EmailField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/EmailField.scala index dc82a23cfd..275889346d 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/EmailField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/EmailField.scala @@ -34,14 +34,16 @@ object EmailField { } trait EmailTypedField extends TypedField[String] { - private def validateEmail(emailValue: ValueType): List[FieldError] = + private def validateEmail(emailValue: ValueType): List[FieldError] = { toBoxMyType(emailValue) match { + case Full(email) if (optional_? && email.isEmpty) => Nil case Full(email) if EmailField.validEmailAddr_?(email) => Nil case _ => Text(S.?("invalid.email.address")) } + } override def validations = validateEmail _ :: Nil -} +} class EmailField[OwnerType <: Record[OwnerType]](rec: OwnerType, maxLength: Int) extends StringField[OwnerType](rec, maxLength) with EmailTypedField diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/PostalCodeField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/PostalCodeField.scala index 0efd68a5ac..1c4d82609d 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/PostalCodeField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/PostalCodeField.scala @@ -29,21 +29,26 @@ import S._ trait PostalCodeTypedField extends StringTypedField { - + protected val country: CountryField[_] override def setFilter = toUpper _ :: trim _ :: super.setFilter override def validations = validatePostalCode _ :: Nil - def validatePostalCode(in: ValueType): List[FieldError] = country.value match { - case Countries.USA => valRegex(RegexPattern.compile("[0-9]{5}(\\-[0-9]{4})?"), S.?("invalid.zip.code"))(in) - case Countries.Sweden => valRegex(RegexPattern.compile("[0-9]{3}[ ]?[0-9]{2}"), S.?("invalid.postal.code"))(in) - case Countries.Australia => valRegex(RegexPattern.compile("(0?|[1-9])[0-9]{3}"), S.?("invalid.postal.code"))(in) - case Countries.Canada => valRegex(RegexPattern.compile("[A-Z][0-9][A-Z][ ][0-9][A-Z][0-9]"), S.?("invalid.postal.code"))(in) - case _ => genericCheck(in) + def validatePostalCode(in: ValueType): List[FieldError] = { + toBoxMyType(in) match { + case Full(zip) if (optional_? && zip.isEmpty) => Nil + case _ => + country.value match { + case Countries.USA => valRegex(RegexPattern.compile("[0-9]{5}(\\-[0-9]{4})?"), S.?("invalid.zip.code"))(in) + case Countries.Sweden => valRegex(RegexPattern.compile("[0-9]{3}[ ]?[0-9]{2}"), S.?("invalid.postal.code"))(in) + case Countries.Australia => valRegex(RegexPattern.compile("(0?|[1-9])[0-9]{3}"), S.?("invalid.postal.code"))(in) + case Countries.Canada => valRegex(RegexPattern.compile("[A-Z][0-9][A-Z][ ][0-9][A-Z][0-9]"), S.?("invalid.postal.code"))(in) + case _ => genericCheck(in) + } + } } - private def genericCheck(zip: ValueType): List[FieldError] = { toBoxMyType(zip) flatMap { case null => Full(Text(S.?("invalid.postal.code"))) diff --git a/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala b/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala index e3ae81f182..77cfb54cf1 100644 --- a/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala +++ b/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala @@ -51,7 +51,7 @@ object FieldSpec extends Specification { def commonBehaviorsForMandatory(in: MandatoryTypedField[A]): Unit = { if (canCheckDefaultValues) { - "which have the correct initial value" in S.initIfUninitted(session) { + "which have the correct initial value" in S.initIfUninitted(session) { in.get must_== in.defaultValue } } @@ -366,6 +366,7 @@ object FieldSpec extends Specification { } "EmailField" should { + val session = new LiftSession("", randomString(20), Empty) val rec = FieldTypeTestRecord.createRecord val email = "foo@bar.baz" passBasicTests(email, rec.mandatoryEmailField, rec.legacyOptionalEmailField, rec.optionalEmailField) @@ -376,6 +377,30 @@ object FieldSpec extends Specification { JString(email), Full() ) + "pass validation if field is optional and value is Empty" in { + S.initIfUninitted(session) { + rec.legacyOptionalEmailField(Empty) + rec.legacyOptionalEmailField.validate must have length(0) + + rec.optionalEmailField(Empty) + rec.optionalEmailField.validate must have length(0) + } + } + "pass validation if field is optional and value is an empty string" in { + S.initIfUninitted(session) { + rec.legacyOptionalEmailField("") + rec.legacyOptionalEmailField.validate must have length(0) + + rec.optionalEmailField("") + rec.optionalEmailField.validate must have length(0) + } + } + "fail validation if value is invalid" in { + S.initIfUninitted(session) { + rec.mandatoryEmailField("invalid email") + rec.mandatoryEmailField.validate must have length(1) + } + } } "EnumField" should { @@ -457,6 +482,7 @@ object FieldSpec extends Specification { } "PostalCodeField" should { + val session = new LiftSession("", randomString(20), Empty) val rec = FieldTypeTestRecord.createRecord val zip = "02452" rec.mandatoryCountryField.set(Countries.USA) @@ -468,6 +494,30 @@ object FieldSpec extends Specification { JString(zip), Full() ) + "pass validation if field is optional and value is Empty" in { + S.initIfUninitted(session) { + rec.legacyOptionalPostalCodeField(Empty) + rec.legacyOptionalPostalCodeField.validate must have length(0) + + rec.optionalPostalCodeField(Empty) + rec.optionalPostalCodeField.validate must have length(0) + } + } + "pass validation if field is optional and value is an empty string" in { + S.initIfUninitted(session) { + rec.legacyOptionalPostalCodeField("") + rec.legacyOptionalPostalCodeField.validate must have length(0) + + rec.optionalPostalCodeField("") + rec.optionalPostalCodeField.validate must have length(0) + } + } + "fail validation if value is invalid" in { + S.initIfUninitted(session) { + rec.mandatoryPostalCodeField("invalid zip") + rec.mandatoryPostalCodeField.validate must have length(1) + } + } } "StringField" should { From 1b7b125b5e29497909f7c2ffdba0aff32942ec7c Mon Sep 17 00:00:00 2001 From: Dave Briccetti Date: Fri, 26 Oct 2012 10:35:14 -0700 Subject: [PATCH 0205/1949] Fix spelling in comment --- core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala b/core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala index c8d1cf0217..39fbfdd8ed 100644 --- a/core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala @@ -402,7 +402,7 @@ trait TimeHelpers { self: ControlHelpers => internetDateFormatter.parse(dateString) } - /** @return a date from a string using the internet format. Return the Epoch date if the parse is unsuccesfull */ + /** @return a date from a string using the internet format. Return the Epoch date if the parse is unsuccesful */ def parseInternetDate(dateString: String): Date = tryo { internetDateFormatter.parse(dateString) } openOr new Date(0L) From dc005ad57422e0f0cbded37bf9c3b5eec99ee46c Mon Sep 17 00:00:00 2001 From: Naftoli Gugenheim Date: Wed, 24 Oct 2012 21:36:26 -0400 Subject: [PATCH 0206/1949] Make LiftRules.htmlProperties use its function --- .../scala/net/liftweb/http/LiftSession.scala | 9 +-- .../net/liftweb/http/HtmlPropertiesSpec.scala | 66 +++++++++++++++++++ 2 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 web/webkit/src/test/scala/net/liftweb/http/HtmlPropertiesSpec.scala diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index cc458b81c4..940753bdf2 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -527,13 +527,10 @@ private[http] final case class AjaxRequestInfo(requestVersion: Long, */ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, val httpSession: Box[HTTPSession]) extends LiftMerge with Loggable with HowStateful { - val sessionHtmlProperties: SessionVar[HtmlProperties] = - new SessionVar[HtmlProperties](LiftRules.htmlProperties.vend( - S.request openOr Req.nil - )) {} + def sessionHtmlProperties = LiftRules.htmlProperties.session.is.make openOr LiftRules.htmlProperties.default.is.vend val requestHtmlProperties: TransientRequestVar[HtmlProperties] = - new TransientRequestVar[HtmlProperties](sessionHtmlProperties.is) {} + new TransientRequestVar[HtmlProperties](sessionHtmlProperties(S.request openOr Req.nil)) {} @volatile private[http] var markedForTermination = false @@ -635,7 +632,7 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, lastServiceTime = millis LiftSession.onSetupSession.foreach(_(this)) - sessionHtmlProperties.is // cause the properties to be calculated + sessionHtmlProperties // cause the properties to be calculated } def running_? = _running_? diff --git a/web/webkit/src/test/scala/net/liftweb/http/HtmlPropertiesSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/HtmlPropertiesSpec.scala new file mode 100644 index 0000000000..282fb5a65c --- /dev/null +++ b/web/webkit/src/test/scala/net/liftweb/http/HtmlPropertiesSpec.scala @@ -0,0 +1,66 @@ +/* + * Copyright 2011 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 mockweb._ + + +import scala.xml.NodeSeq + +import common.{ Box, Empty, Full } + +/** +* This only exists to keep the WebSpecSpec clean. Normally, +* you could just use "() => bootstrap.Boot.boot". +*/ +object HtmlPropertiesSpecBoot { + def boot() { + LiftRules.htmlProperties.default.set((_: Req) match { + case r @ Req("html5" :: _, _, _) => + println("Html5 request: " + r) + Html5Properties(r.userAgent) + case r => + println("other request: " + r) + OldHtmlProperties(r.userAgent) + }) + } +} + +class HtmlPropertiesSpec extends WebSpec(HtmlPropertiesSpecBoot.boot _) { + sequential + + "LiftRules.htmlProperties.default function" should { + val testUrl1 = "http://example.com/html5/something" + val testUrl2 = "http://example.com/anotherurl" + + val session1 = MockWeb.testS(testUrl1)(S.session) + val session2 = MockWeb.testS(testUrl2)(S.session) + + "set S.htmlProperties to html5 when that is the first request" withSFor(testUrl1, session1) in { + S.htmlProperties must haveClass[Html5Properties] + } + "set S.htmlProperties to xhtml when that is not the first request" withSFor(testUrl2, session1) in { + S.htmlProperties must haveClass[OldHtmlProperties] + } + "set S.htmlProperties to xhtml when that is the first request" withSFor(testUrl2, session2) in { + S.htmlProperties must haveClass[OldHtmlProperties] + } + "set S.htmlProperties to html5 when that is not the first request" withSFor(testUrl1, session2) in { + S.htmlProperties must haveClass[Html5Properties] + } + } +} From 908fea7da94abbd785ddfc7d730d17de7b8bc2ca Mon Sep 17 00:00:00 2001 From: Naftoli Gugenheim Date: Wed, 24 Oct 2012 21:39:07 -0400 Subject: [PATCH 0207/1949] Space changes --- .../src/main/scala/net/liftweb/http/LiftSession.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index 940753bdf2..ba48f0cb55 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -433,7 +433,7 @@ private[http] object RenderVersion { } /** - * A trait defining how stateful the session is + * A trait defining how stateful the session is */ trait HowStateful { private val howStateful = new ThreadGlobal[Boolean] @@ -1667,7 +1667,7 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, val ret = findSnippetInstance(nameToTry) // Update the snippetMap so that we reuse the same instance in this request (unless the snippet is transient) ret.filter(TransientSnippet.notTransient(_)).foreach(s => snippetMap.set(snippetMap.is.updated(tagName, s))) - + ret } } @@ -1693,7 +1693,7 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, runWhitelist(snippet, cls, method, kids){(S.locateMappedSnippet(snippet).map(_(kids)) or locSnippet(snippet)).openOr( S.locateSnippet(snippet).map(_(kids)) openOr { - + (locateAndCacheSnippet(cls)) match { // deal with a stateless request when a snippet has // different behavior in stateless mode @@ -1835,7 +1835,7 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, e.snippetFailure, e.buildStackTrace, wholeTag) - + case e: SnippetFailureException => reportSnippetError(page, snippetName, e.snippetFailure, From 51466b12be555027a56acb6601c835d3b7fc51f3 Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Sat, 13 Oct 2012 00:09:07 -0400 Subject: [PATCH 0208/1949] Fixed #1232 - Cache resource lookup in production mode The only mode that the resources are not cached is DevMode --- web/webkit/src/main/scala/net/liftweb/http/S.scala | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/S.scala b/web/webkit/src/main/scala/net/liftweb/http/S.scala index d737d6a332..57bb59cdbd 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/S.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/S.scala @@ -933,10 +933,18 @@ trait S extends HasParams with Loggable { * If you do not define an entry for a particular key, we fall back to using * Lift's core entries. * + * We cache the values in modes other than DevMode + * * @see LiftRules.resourceNames * @see LiftRules.resourceBundleFactories */ - def resourceBundles: List[ResourceBundle] = resourceBundles(locale) ++ liftCoreResourceBundle.toList + def _resourceBundles: List[ResourceBundle] = resourceBundles(locale) ++ liftCoreResourceBundle.toList + + private lazy val cachedResourceBundles = _resourceBundles + + private def resourceBundles: List[ResourceBundle] = + if (Props.devMode) _resourceBundles else cachedResourceBundles + def resourceBundles(loc: Locale): List[ResourceBundle] = { _resBundle.box match { From 688db0048f2af1e744ecd538f431cd51e9f9506c Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 24 Oct 2012 19:53:51 -0700 Subject: [PATCH 0209/1949] Rewrite SoftReferenceCache.apply for type safety. We were using a tuple assignment, which converts into a Scala pattern match that doesn't carry particularly nice type safety guarantees. We now instead assign to a typed tuple and then unpack the tuple with our own match statement, with a clarifying comment on the original assignment to indicate what each part of the tuple means. --- .../net/liftweb/util/SoftReferenceCache.scala | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/SoftReferenceCache.scala b/core/util/src/main/scala/net/liftweb/util/SoftReferenceCache.scala index cba1535269..ccba530f72 100644 --- a/core/util/src/main/scala/net/liftweb/util/SoftReferenceCache.scala +++ b/core/util/src/main/scala/net/liftweb/util/SoftReferenceCache.scala @@ -119,27 +119,30 @@ class SoftReferenceCache[K, V](cacheSize: Int) { * @return Box[V] */ def apply(key: K): Box[V] = { - val (doRemove:Boolean, retval:Box[V]) = + val result:(Boolean,Box[V]) /* (doRemove, retval) */ = lock(readLock) { Box.!!(cache.get(key)) match { case Full(value) => - Box.!!(value.get).map((false, _)) openOr { + Box.!!(value.get).map(value => (false, Full(value))) openOr { (true, Empty) } case _ => (false, Empty) } } - if (doRemove) { - lock(writeLock) { - val value = cache.get(key) + result match { + case (doRemove, retval) if doRemove => + lock(writeLock) { + val value = cache.get(key) - if (value != null && value.get == null) - remove(key) - } - } + if (value != null && value.get == null) + remove(key) + } - retval + retval + case (_, retval) => + retval + } } /** From fb413764afc48458f6d2eb330caec5ee33b267f4 Mon Sep 17 00:00:00 2001 From: Dave Whittaker Date: Thu, 25 Oct 2012 10:23:03 -0400 Subject: [PATCH 0210/1949] Added a Spec for SoftReferenceCache testing basic functionality. --- .../liftweb/util/SoftReferenceCacheSpec.scala | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 core/util/src/test/scala/net/liftweb/util/SoftReferenceCacheSpec.scala diff --git a/core/util/src/test/scala/net/liftweb/util/SoftReferenceCacheSpec.scala b/core/util/src/test/scala/net/liftweb/util/SoftReferenceCacheSpec.scala new file mode 100644 index 0000000000..4bdd811e19 --- /dev/null +++ b/core/util/src/test/scala/net/liftweb/util/SoftReferenceCacheSpec.scala @@ -0,0 +1,28 @@ +package net.liftweb.util + +import org.specs2.mutable._ +import net.liftweb.common._ +import org.specs2.specification.AroundExample + +object SoftReferenceCacheSpec extends Specification { + + sequential + + object cache extends SoftReferenceCache[String, String](1) + + "SoftReferenceCache " should { + "Accept additions" in { + cache += ("test" -> "test") + cache.keys.size() must_== 1 + } + "Allow objects to be retrieved" in { + val cached = cache("test") + cached must beLike { case Full("test") => ok } + } + "Properly age out entries" in { + cache += ("test2" -> "test2") + cache("test") must_== Empty + } + } + +} \ No newline at end of file From 6084c9f9683a5bea6876803a7e91e3e2cbda526f Mon Sep 17 00:00:00 2001 From: Dave Whittaker Date: Sun, 28 Oct 2012 20:37:40 -0400 Subject: [PATCH 0211/1949] Updated to latest version of squeryl, 0.9.5-4 --- project/Dependencies.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 6fb5be5f2d..62d737b074 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -47,7 +47,7 @@ object Dependencies { lazy val scala_compiler: ModuleMap = "org.scala-lang" % "scala-compiler" % _ lazy val scalaz_core: ModuleMap = sv => scalazGroup(sv) % "scalaz-core" % scalazVersion(sv) cross CVMappingAll lazy val slf4j_api = "org.slf4j" % "slf4j-api" % slf4jVersion - lazy val squeryl = "org.squeryl" % "squeryl" % "0.9.5-1" cross crossMapped("2.9.1-1" -> "2.9.1", "2.8.2" -> "2.8.1") + lazy val squeryl = "org.squeryl" % "squeryl" % "0.9.5-4" cross crossMapped("2.9.1-1" -> "2.9.1", "2.8.2" -> "2.8.1") // Aliases lazy val mongo_driver = mongo_java_driver From e048048285ede0cdf7248cd050444024a3b69dcc Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Tue, 30 Oct 2012 01:07:01 -0400 Subject: [PATCH 0212/1949] Properly set the render version on AJAX requests without AJAX version info. Requests of the form /ajax_request/F, without the - on the end, should now properly restore the RenderVersion, resulting in proper state management for these requests. --- .../src/main/scala/net/liftweb/http/LiftServlet.scala | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala index 9713d259dd..e83c767a6c 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala @@ -415,13 +415,6 @@ class LiftServlet extends Loggable { toReturn } - private def extractVersion[T](path: List[String])(f: => T): T = { - path match { - case first :: second :: _ => RenderVersion.doWith(second)(f) - case _ => f - } - } - /** * Tracks the two aspects of an AJAX version: the sequence number, * whose sole purpose is to identify requests that are retries for the @@ -456,8 +449,10 @@ class LiftServlet extends Loggable { */ private def extractVersions[T](path: List[String])(f: (Box[AjaxVersionInfo]) => T): T = { path match { - case first :: AjaxVersions(versionInfo @ AjaxVersionInfo(renderVersion, _, _)) :: _ => + case ajaxPath :: AjaxVersions(versionInfo @ AjaxVersionInfo(renderVersion, _, _)) :: _ => RenderVersion.doWith(renderVersion)(f(Full(versionInfo))) + case ajaxPath :: renderVersion :: _ => + RenderVersion.doWith(renderVersion)(f(Empty)) case _ => f(Empty) } } From a1fa08b5acb3be43822d64305e0ccf03004b91f8 Mon Sep 17 00:00:00 2001 From: Jeppe Nejsum Madsen Date: Tue, 30 Oct 2012 10:51:44 +0100 Subject: [PATCH 0213/1949] Added jsonCall with JValue=>JValue func. Closes #1317 --- .../main/scala/net/liftweb/http/SHtml.scala | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala b/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala index ad65732460..cf89836740 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala @@ -198,6 +198,21 @@ trait SHtml { jsonCall_*(jsCalcValue, jsContext, SFuncHolder(s => parseOpt(s).map(func) getOrElse Noop)) + /** + * Build a JavaScript function that will perform a JSON call based on a value calculated in JavaScript. + * + * The JSON generated by func will be returned to the client and passed as argument to the javascript function specified in + * jsonContext.success + * + * @param jsCalcValue the JavaScript to calculate the value to be sent to the server + * @param jsonContext the context instance that defines JavaScript to be executed on call success or failure + * @param func the function to call when the JSON data is sent. The returned JSON is sent back to the client + * + * @return the function ID and JavaScript that makes the call + */ + def jsonCall(jsCalcValue: JsExp, jsonContext: JsonContext, func: JsonAST.JValue => JsonAST.JValue): GUIDJsExp = + jsonCall_*(jsCalcValue, jsonContext, S.SFuncHolder(s => parseOpt(s).map(func) getOrElse JsonAST.JNothing)) + /** * Build a JavaScript function that will perform a JSON call based on a value calculated in JavaScript * @@ -254,9 +269,9 @@ trait SHtml { f(name, js) } - def jsonCall(jsCalcValue: JsExp, - jsonContext: JsonContext, - func: String => JsObj): GUIDJsExp = ajaxCall_*(jsCalcValue, jsonContext, SFuncHolder(func)) + @deprecated("Use jsonCall with a function that takes JValue => JValue", "2.5") + def jsonCall(jsCalcValue: JsExp, jsonContext: JsonContext, func: String => JsObj)(implicit d: AvoidTypeErasureIssues1): GUIDJsExp = + ajaxCall_*(jsCalcValue, jsonContext, SFuncHolder(func)) def fjsonCall[T](jsCalcValue: JsExp, jsonContext: JsonContext, func: String => JsObj)(f: (String, JsExp) => T): T = { val (name, js) = jsonCall(jsCalcValue, jsonContext, func).product From 731c4dc3734580c3519802a76d4ca717d635b85d Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Thu, 1 Nov 2012 20:33:41 -0400 Subject: [PATCH 0214/1949] Check for nulls when looking for an ajax version. Before we were checking for falseys (doing if (version)), which meant that when the initial version value of 0 came up, we assumed there was no version and sent a non-versioned request. This then restricts that request from being retried properly using the new retry flows. --- .../src/main/scala/net/liftweb/http/js/ScriptRenderer.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala b/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala index be342d70d8..ff9fd0b970 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala @@ -196,7 +196,7 @@ object ScriptRenderer { """ + { if (LiftRules.enableLiftGC) { """ var replacement = '""" + LiftRules.ajaxPath + """/'+lift_page; - if (version) + if (version!=null) replacement += ('-'+version.toString(36)) + (liftAjax.lift_ajaxQueue.length > 35 ? 35 : liftAjax.lift_ajaxQueue.length).toString(36); return url.replace('""" + LiftRules.ajaxPath + """', replacement);""" } else { From 3e9596041185f851c87a8a1d4e349eb5cf73594e Mon Sep 17 00:00:00 2001 From: karma4u101 Date: Sat, 3 Nov 2012 15:02:44 +0100 Subject: [PATCH 0215/1949] Issue #1350 --- web/webkit/src/main/scala/net/liftweb/sitemap/Menu.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/sitemap/Menu.scala b/web/webkit/src/main/scala/net/liftweb/sitemap/Menu.scala index 53ada77b25..83f9493627 100644 --- a/web/webkit/src/main/scala/net/liftweb/sitemap/Menu.scala +++ b/web/webkit/src/main/scala/net/liftweb/sitemap/Menu.scala @@ -555,9 +555,9 @@ sealed trait MenuSingleton { /** * A convenient way to define a Menu items that's got the same name as it does it's localized LinkText. - *
    Menu.i("Home") / "index"
    is short-hand for
    Menu("Home", S ? "Home") / "index"
    + *
    Menu.i("Home") / "index"
    is short-hand for
    Menu("Home", S.loc("Home", Text("Home")) / "index"
    */ - def i(nameAndLink: String): PreMenu = Menu.apply(nameAndLink, S ? nameAndLink) + def i(nameAndLink: String): PreMenu = Menu.apply(nameAndLink, S.loc(nameAndLink, scala.xml.Text(nameAndLink))) def param[T<:AnyRef](name: String, linkText: Loc.LinkText[T], parser: String => Box[T], encoder: T => String): PreParamMenu[T] = From 003782f56757aa6edb663c4e640bfa8f9cb23001 Mon Sep 17 00:00:00 2001 From: Dave Whittaker Date: Fri, 2 Nov 2012 17:40:46 -0400 Subject: [PATCH 0216/1949] All Fields now handle setFromString(null|"") by setting valueBox to Empty if the field is optional, or Failure if it is not --- .../liftweb/record/field/BinaryField.scala | 6 +++-- .../liftweb/record/field/BooleanField.scala | 11 +++++++- .../liftweb/record/field/DateTimeField.scala | 5 ++-- .../liftweb/record/field/DecimalField.scala | 10 ++++++- .../liftweb/record/field/DoubleField.scala | 12 ++++++++- .../net/liftweb/record/field/EnumField.scala | 10 ++++++- .../liftweb/record/field/EnumNameField.scala | 5 ++-- .../net/liftweb/record/field/IntField.scala | 7 +++-- .../net/liftweb/record/field/LongField.scala | 11 +++++++- .../liftweb/record/field/PasswordField.scala | 5 ++-- .../liftweb/record/field/StringField.scala | 5 ++-- .../scala/net/liftweb/record/FieldSpec.scala | 27 +++++++++++++++++++ 12 files changed, 97 insertions(+), 17 deletions(-) diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/BinaryField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/BinaryField.scala index de20e56b70..7c1f319249 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/BinaryField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/BinaryField.scala @@ -30,11 +30,13 @@ import JE._ trait BinaryTypedField extends TypedField[Array[Byte]] { + def setFromAny(in: Any): Box[Array[Byte]] = genericSetFromAny(in) def setFromString(s: String): Box[Array[Byte]] = s match { - case "" if optional_? => setBox(Empty) - case _ => setBox(tryo(s.getBytes("UTF-8"))) + case null|"" if optional_? => setBox(Empty) + case null|"" => setBox(Failure(notOptionalErrorMessage)) + case _ => setBox(tryo(s.getBytes("UTF-8"))) } def toForm: Box[NodeSeq] = Empty diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/BooleanField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/BooleanField.scala index 101f172463..eb055a0096 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/BooleanField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/BooleanField.scala @@ -29,6 +29,7 @@ import S._ import JE._ trait BooleanTypedField extends TypedField[Boolean] { + def setFromAny(in: Any): Box[Boolean] = in match{ case b: java.lang.Boolean => setBox(Full(b.booleanValue)) case Full(b: java.lang.Boolean) => setBox(Full(b.booleanValue)) @@ -37,7 +38,15 @@ trait BooleanTypedField extends TypedField[Boolean] { case _ => genericSetFromAny(in) } - def setFromString(s: String): Box[Boolean] = setBox(tryo(toBoolean(s))) + def setFromString(s: String): Box[Boolean] = + if(s == null || s.isEmpty) { + if(optional_?) + setBox(Empty) + else + setBox(Failure(notOptionalErrorMessage)) + } else { + setBox(tryo(toBoolean(s))) + } private def elem(attrs: SHtml.ElemAttr*) = SHtml.checkbox(valueBox openOr false, (b: Boolean) => this.setBox(Full(b)), (("tabindex" -> tabIndex.toString): SHtml.ElemAttr) :: attrs.toList: _*) diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/DateTimeField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/DateTimeField.scala index a8e218e22a..3e4ca30672 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/DateTimeField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/DateTimeField.scala @@ -43,8 +43,9 @@ trait DateTimeTypedField extends TypedField[Calendar] { def setFromAny(in : Any): Box[Calendar] = toDate(in).flatMap(d => setBox(Full(dateToCal(d)))) or genericSetFromAny(in) def setFromString(s: String): Box[Calendar] = s match { - case "" if optional_? => setBox(Empty) - case other => setBox(tryo(dateToCal(parseInternetDate(s)))) + case null|"" if optional_? => setBox(Empty) + case null|"" => setBox(Failure(notOptionalErrorMessage)) + case other => setBox(tryo(dateToCal(parseInternetDate(s)))) } private def elem = diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/DecimalField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/DecimalField.scala index 03c77ebbb7..f46a1a697b 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/DecimalField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/DecimalField.scala @@ -37,7 +37,15 @@ trait DecimalTypedField extends NumericTypedField[BigDecimal] { def setFromAny(in : Any): Box[BigDecimal] = setNumericFromAny(in, n => BigDecimal(n.toString)) - def setFromString (s : String) : Box[BigDecimal] = setBox(tryo(BigDecimal(s))) + def setFromString (s : String) : Box[BigDecimal] = + if(s == null || s.isEmpty) { + if(optional_?) + setBox(Empty) + else + setBox(Failure(notOptionalErrorMessage)) + } else { + setBox(tryo(BigDecimal(s))) + } def set_!(in: BigDecimal): BigDecimal = new BigDecimal(in.bigDecimal.setScale(scale, context.getRoundingMode)) diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/DoubleField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/DoubleField.scala index 153139a4cd..745347141b 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/DoubleField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/DoubleField.scala @@ -27,13 +27,23 @@ import Helpers._ import S._ trait DoubleTypedField extends NumericTypedField[Double] { + def setFromAny(in: Any): Box[Double] = setNumericFromAny(in, _.doubleValue) - def setFromString(s: String): Box[Double] = setBox(tryo(java.lang.Double.parseDouble(s))) + def setFromString(s: String): Box[Double] = + if(s == null || s.isEmpty) { + if(optional_?) + setBox(Empty) + else + setBox(Failure(notOptionalErrorMessage)) + } else { + setBox(tryo(java.lang.Double.parseDouble(s))) + } def defaultValue = 0.0 def asJValue = valueBox.map(JDouble) openOr (JNothing: JValue) + def setFromJValue(jvalue: JValue) = jvalue match { case JNothing|JNull if optional_? => setBox(Empty) case JDouble(d) => setBox(Full(d)) diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/EnumField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/EnumField.scala index d37c260ec8..02a009e924 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/EnumField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/EnumField.scala @@ -52,7 +52,15 @@ trait EnumTypedField[EnumType <: Enumeration] extends TypedField[EnumType#Value] case _ => genericSetFromAny(in)(valueManifest) } - def setFromString(s: String): Box[EnumType#Value] = setBox(asInt(s).flatMap(fromInt)) + def setFromString(s: String): Box[EnumType#Value] = + if(s == null || s.isEmpty) { + if(optional_?) + setBox(Empty) + else + setBox(Failure(notOptionalErrorMessage)) + } else { + setBox(asInt(s).flatMap(fromInt)) + } /** Label for the selection item representing Empty, show when this field is optional. Defaults to the empty string. */ def emptyOptionLabel: String = "" diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/EnumNameField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/EnumNameField.scala index 7b869358c1..4b6b4f9a90 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/EnumNameField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/EnumNameField.scala @@ -39,8 +39,9 @@ trait EnumNameTypedField[EnumType <: Enumeration] extends TypedField[EnumType#Va def setFromAny(in: Any): Box[EnumType#Value] = genericSetFromAny(in)(valueManifest) def setFromString(s: String): Box[EnumType#Value] = s match { - case "" if optional_? => setBox(Empty) - case _ => setBox(enum.values.find(_.toString == s)) + case null|"" if optional_? => setBox(Empty) + case null|"" => setBox(Failure(notOptionalErrorMessage)) + case _ => setBox(enum.values.find(_.toString == s)) } /** Label for the selection item representing Empty, show when this field is optional. Defaults to the empty string. */ diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/IntField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/IntField.scala index b3482f3634..3cd6b16351 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/IntField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/IntField.scala @@ -27,16 +27,19 @@ import Helpers._ import S._ trait IntTypedField extends NumericTypedField[Int] { + def setFromAny(in: Any): Box[Int] = setNumericFromAny(in, _.intValue) def setFromString(s: String): Box[Int] = s match { - case "" if optional_? => setBox(Empty) - case _ => setBox(tryo(java.lang.Integer.parseInt(s))) + case null|"" if optional_? => setBox(Empty) + case null|"" => setBox(Failure(notOptionalErrorMessage)) + case _ => setBox(tryo(java.lang.Integer.parseInt(s))) } def defaultValue = 0 def asJValue: JValue = valueBox.map(i => JInt(BigInt(i))) openOr (JNothing: JValue) + def setFromJValue(jvalue: JValue): Box[Int] = jvalue match { case JNothing|JNull if optional_? => setBox(Empty) case JInt(i) => setBox(Full(i.intValue)) diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/LongField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/LongField.scala index da7f1e3952..7bdbb1b696 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/LongField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/LongField.scala @@ -27,9 +27,18 @@ import Helpers._ import S._ trait LongTypedField extends NumericTypedField[Long] { + def setFromAny(in: Any): Box[Long] = setNumericFromAny(in, _.longValue) - def setFromString(s: String): Box[Long] = setBox(asLong(s)) + def setFromString(s: String): Box[Long] = + if(s == null || s.isEmpty) { + if(optional_?) + setBox(Empty) + else + setBox(Failure(notOptionalErrorMessage)) + } else { + setBox(asLong(s)) + } def defaultValue = 0L diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/PasswordField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/PasswordField.scala index 8bfb8efb67..cd74b9e658 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/PasswordField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/PasswordField.scala @@ -76,8 +76,9 @@ trait PasswordTypedField extends TypedField[String] { } def setFromString(s: String): Box[String] = s match { - case "" if optional_? => setBoxPlain(Empty) - case _ => setBoxPlain(Full(s)) + case null|"" if optional_? => setBoxPlain(Empty) + case null|"" => setBoxPlain(Failure(notOptionalErrorMessage)) + case _ => setBoxPlain(Full(s)) } override def validate: List[FieldError] = { diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/StringField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/StringField.scala index 034ee11ccd..324eef2a0f 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/StringField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/StringField.scala @@ -41,8 +41,9 @@ trait StringTypedField extends TypedField[String] with StringValidators { } def setFromString(s: String): Box[String] = s match { - case "" if optional_? => setBox(Empty) - case _ => setBox(Full(s)) + case null|"" if optional_? => setBox(Empty) + case null|"" => setBox(Failure(notOptionalErrorMessage)) + case _ => setBox(Full(s)) } private def elem = S.fmapFunc(SFuncHolder(this.setFromAny(_))) { diff --git a/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala b/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala index 77cfb54cf1..a8d8f3661a 100644 --- a/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala +++ b/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala @@ -45,6 +45,7 @@ object FieldSpec extends Specification { lazy val session = new LiftSession("", randomString(20), Empty) def passBasicTests[A](example: A, mandatory: MandatoryTypedField[A], legacyOptional: MandatoryTypedField[A], optional: OptionalTypedField[A])(implicit m: scala.reflect.Manifest[A]): Fragment = { + val canCheckDefaultValues = !mandatory.defaultValue.isInstanceOf[Calendar] // don't try to use the default value of date/time typed fields, because it changes from moment to moment! @@ -74,6 +75,22 @@ object FieldSpec extends Specification { in.get must_== in.defaultValue } } + + if(!in.optional_?) { + "which fail when set with an empty string when not optional" in { + in.setFromString(null) + in.valueBox must beLike { case f: Failure => ok } + in.setFromString("") + in.valueBox must beLike { case f: Failure => ok } + } + } else { + "which don't fail when set with an empty string when optional" in { + in.setFromString(null) + in.valueBox must_== Empty + in.setFromString("") + in.valueBox must_== Empty + } + } } def commonBehaviorsForAllFlavors(in: TypedField[A]): Unit = { @@ -145,6 +162,7 @@ object FieldSpec extends Specification { } "which initialize to some value" in { + mandatory.clear mandatory.valueBox.isDefined must_== true } @@ -198,6 +216,15 @@ object FieldSpec extends Specification { "which initialize to Empty" in { optional.valueBox must_== Empty } + + "which don't fail when set with an empty string" in { + optional.setFromString(null) + optional.value must_== None + optional.valueBox must_== Empty + optional.setFromString("") + optional.value must_== None + optional.valueBox must_== Empty + } "which do not fail when set to Empty" in { optional.set(Some(example)) From c10470b875c44ef39e9183ee5c7bfff464536573 Mon Sep 17 00:00:00 2001 From: Dave Whittaker Date: Fri, 2 Nov 2012 14:40:06 -0400 Subject: [PATCH 0217/1949] Add an inTransaction around the CRUDify delete --- .../src/main/scala/net/liftweb/squerylrecord/CRUDify.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/persistence/squeryl-record/src/main/scala/net/liftweb/squerylrecord/CRUDify.scala b/persistence/squeryl-record/src/main/scala/net/liftweb/squerylrecord/CRUDify.scala index 57a64f2cbe..39fc3a5cc1 100644 --- a/persistence/squeryl-record/src/main/scala/net/liftweb/squerylrecord/CRUDify.scala +++ b/persistence/squeryl-record/src/main/scala/net/liftweb/squerylrecord/CRUDify.scala @@ -58,7 +58,9 @@ trait CRUDify[K, T <: Record[T] with KeyedEntity[K]] extends Crudify { protected class SquerylBridge(in: TheCrudType) extends CrudBridge { - def delete_! = table.delete(in.id) + def delete_! = inTransaction { + table.delete(in.id) + } def save = { if (in.isPersisted) { From 36d59def9204480e5700dce4f1d8ecc9a8ca7f45 Mon Sep 17 00:00:00 2001 From: Dave Whittaker Date: Fri, 2 Nov 2012 14:22:38 -0400 Subject: [PATCH 0218/1949] Changes to how the dirty status of a field is calculated: - We now use `data` instead of the valueBox method so that valueBox can be overridenwith an implementation that uses dirty_? itself - When a field is set, the dirty flag is only re-calculated if the value was false. Once a field is dirty it remains dirty until it is explicitly set otherwise (usually when it is persisted) . --- .../src/main/scala/net/liftweb/record/Field.scala | 12 +++++++----- .../test/scala/net/liftweb/record/FieldSpec.scala | 3 +++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/persistence/record/src/main/scala/net/liftweb/record/Field.scala b/persistence/record/src/main/scala/net/liftweb/record/Field.scala index 859bc4fd75..b4a65cbd91 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/Field.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/Field.scala @@ -217,7 +217,7 @@ trait TypedField[ThisType] extends BaseField { def setBox(in: Box[MyType]): Box[MyType] = synchronized { needsDefault = false - val oldValue = valueBox + val oldValue = data data = in match { case _ if !canWrite_? => Failure(noValueErrorMessage) case Full(_) => set_!(in) @@ -225,11 +225,13 @@ trait TypedField[ThisType] extends BaseField { case (f: Failure) => set_!(f) // preserve failures set in case _ => Failure(notOptionalErrorMessage) } - val same = (oldValue, valueBox) match { - case (Full(ov), Full(nv)) => ov == nv - case (a, b) => a == b + if(!dirty_?) { + val same = (oldValue, data) match { + case (Full(ov), Full(nv)) => ov == nv + case (a, b) => a == b + } + dirty_?(!same) } - dirty_?(!same) data } diff --git a/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala b/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala index a8d8f3661a..a73498b208 100644 --- a/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala +++ b/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala @@ -147,6 +147,9 @@ object FieldSpec extends Specification { (valueBox === exampleBox) must_== false in.setBox(exampleBox) in.dirty_? must_== true + //dirty value should remain true, even if the same value is set twice before persisting + in.setBox(exampleBox) + in.dirty_? must_== true in.setBox(valueBox) success } From f030513b9228ca1a0478180e4575762b98596c75 Mon Sep 17 00:00:00 2001 From: Tim Nelson Date: Sat, 3 Nov 2012 11:28:01 -0500 Subject: [PATCH 0219/1949] Issue 1343 - Ensure equals method works properly with all Fields --- .../liftweb/mongodb/record/BsonRecord.scala | 59 +++---- .../mongodb/record/MongoMetaRecord.scala | 6 +- .../record/field/JsonObjectField.scala | 4 +- .../mongodb/record/field/PatternField.scala | 8 +- .../mongodb/record/BsonRecordSpec.scala | 46 +++++ .../net/liftweb/mongodb/record/Fixtures.scala | 84 ++++----- .../mongodb/record/MongoFieldSpec.scala | 162 ++++++++++-------- .../mongodb/record/MongoRecordSpec.scala | 77 +++++++-- .../main/scala/net/liftweb/record/Field.scala | 38 ++-- .../scala/net/liftweb/record/MetaRecord.scala | 21 +++ .../scala/net/liftweb/record/Record.scala | 32 +++- .../scala/net/liftweb/record/FieldSpec.scala | 104 ++++++----- .../scala/net/liftweb/record/Fixtures.scala | 28 ++- .../scala/net/liftweb/record/RecordSpec.scala | 3 +- 14 files changed, 413 insertions(+), 259 deletions(-) create mode 100644 persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/BsonRecordSpec.scala diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/BsonRecord.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/BsonRecord.scala index ad7a7b38af..2cf2045eaa 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/BsonRecord.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/BsonRecord.scala @@ -1,5 +1,5 @@ /* - * Copyright 2011 WorldWide Conferencing, LLC + * Copyright 2011-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. @@ -36,31 +36,18 @@ trait BsonRecord[MyType <: BsonRecord[MyType]] extends Record[MyType] { def meta: BsonMetaRecord[MyType] /** - * Encode a record instance into a DBObject - */ + * Encode a record instance into a DBObject + */ def asDBObject: DBObject = meta.asDBObject(this) /** - * Set the fields of this record from the given DBObject - */ + * Set the fields of this record from the given DBObject + */ def setFieldsFromDBObject(dbo: DBObject): Unit = meta.setFieldsFromDBObject(this, dbo) - override def toString = { - val fieldList = this.fields.map(f => "%s=%s" format (f.name, - f.valueBox match { - case Full(c: java.util.Calendar) => c.getTime().toString() - case Full(null) => "" - case Full(v) => v.toString - case _ => "" - })) - - "%s={%s}" format (this.getClass.toString, fieldList.mkString(", ")) - } - - /** - * Save the instance and return the instance - */ + * Save the instance and return the instance + */ override def saveTheRecord(): Box[MyType] = throw new BackingStoreException("BSON Records don't save themselves") } @@ -69,10 +56,10 @@ trait BsonMetaRecord[BaseRecord <: BsonRecord[BaseRecord]] extends MetaRecord[Ba self: BaseRecord => /** - * Create a BasicDBObject from the field names and values. - * - MongoFieldFlavor types (List) are converted to DBObjects - * using asDBObject - */ + * Create a BasicDBObject from the field names and values. + * - MongoFieldFlavor types (List) are converted to DBObjects + * using asDBObject + */ def asDBObject(inst: BaseRecord): DBObject = { val dbo = BasicDBObjectBuilder.start // use this so regex patterns can be stored. @@ -116,11 +103,11 @@ trait BsonMetaRecord[BaseRecord <: BsonRecord[BaseRecord]] extends MetaRecord[Ba } /** - * Creates a new record, then sets the fields with the given DBObject. - * - * @param dbo - the DBObject - * @return Box[BaseRecord] - */ + * Creates a new record, then sets the fields with the given DBObject. + * + * @param dbo - the DBObject + * @return Box[BaseRecord] + */ def fromDBObject(dbo: DBObject): BaseRecord = { val inst: BaseRecord = createRecord setFieldsFromDBObject(inst, dbo) @@ -128,13 +115,13 @@ trait BsonMetaRecord[BaseRecord <: BsonRecord[BaseRecord]] extends MetaRecord[Ba } /** - * Populate the inst's fields with the values from a DBObject. Values are set - * using setFromAny passing it the DBObject returned from Mongo. - * - * @param inst - the record that will be populated - * @param dbo - The DBObject - * @return Unit - */ + * Populate the inst's fields with the values from a DBObject. Values are set + * using setFromAny passing it the DBObject returned from Mongo. + * + * @param inst - the record that will be populated + * @param dbo - The DBObject + * @return Unit + */ def setFieldsFromDBObject(inst: BaseRecord, dbo: DBObject): Unit = { for (k <- dbo.keySet; field <- inst.fieldByName(k.toString)) { field.setFromAny(dbo.get(k.toString)) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/MongoMetaRecord.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/MongoMetaRecord.scala index d521b63866..a064fe51dd 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/MongoMetaRecord.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/MongoMetaRecord.scala @@ -1,5 +1,5 @@ /* - * Copyright 2010-2011 WorldWide Conferencing, LLC + * 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. @@ -317,7 +317,9 @@ trait MongoMetaRecord[BaseRecord <: MongoRecord[BaseRecord]] } /** - * Update only the dirty fields + * Update only the dirty fields. + * + * Note: PatternField will always set the dirty flag when set. */ def update(inst: BaseRecord): Unit = { val dirtyFields = fields(inst).filter(_.dirty_?) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JsonObjectField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JsonObjectField.scala index 55ad6136dd..d20eb25cd0 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JsonObjectField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JsonObjectField.scala @@ -1,5 +1,5 @@ /* -* Copyright 2010-2011 WorldWide Conferencing, LLC +* 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. @@ -42,7 +42,7 @@ abstract class JsonObjectField[OwnerType <: BsonRecord[OwnerType], JObjectType < override def toForm: Box[NodeSeq] = Empty // FIXME /** Encode the field value into a JValue */ - def asJValue: JValue = value.asJObject + def asJValue: JValue = valueBox.map(_.asJObject) openOr (JNothing: JValue) /* * Decode the JValue and set the field to the decoded value. diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/PatternField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/PatternField.scala index da6f1bb99c..d356769c81 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/PatternField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/PatternField.scala @@ -11,10 +11,10 @@ * limitations under the License. */ -package net.liftweb -package mongodb -package record -package field +package net.liftweb +package mongodb +package record +package field import java.util.regex.Pattern import scala.xml.NodeSeq diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/BsonRecordSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/BsonRecordSpec.scala new file mode 100644 index 0000000000..88e67f8ecc --- /dev/null +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/BsonRecordSpec.scala @@ -0,0 +1,46 @@ +/* + * 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 mongodb +package record + +import org.specs2.mutable.Specification + +class BsonRecordSpec extends Specification with MongoTestKit { + "BsonRecordSpec Specification".title + import fixtures._ + + override def before = { + super.before + checkMongoIsRunning + } + + "BsonRecord" should { + "compare properly" in { + + val subRec = SubSubRecord.createRecord.name("subrecord") + val subRec2 = SubSubRecord.createRecord.name("subrecord") + + (subRec == subRec2) must_== true + + subRec2.name("subrecord2") + + (subRec == subRec2) must_== false + + } + } +} diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala index 519e5eb3b8..bc15fdf0c8 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala @@ -1,5 +1,5 @@ /* - * Copyright 2010-2011 WorldWide Conferencing, LLC + * 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. @@ -132,25 +132,6 @@ class FieldTypeTestRecord private () extends MongoRecord[FieldTypeTestRecord] wi object legacyOptionalTimeZoneField extends TimeZoneField(this) { override def optional_? = true } object optionalTimeZoneField extends OptionalTimeZoneField(this) - override def equals(other: Any): Boolean = other match { - case that: FieldTypeTestRecord => - this.id.value == that.id.value && - this.mandatoryBooleanField.value == that.mandatoryBooleanField.value && - this.mandatoryCountryField.value == that.mandatoryCountryField.value && - this.mandatoryDecimalField.value == that.mandatoryDecimalField.value && - this.mandatoryDoubleField.value == that.mandatoryDoubleField.value && - this.mandatoryEmailField.value == that.mandatoryEmailField.value && - this.mandatoryEnumField.value == that.mandatoryEnumField.value && - this.mandatoryIntField.value == that.mandatoryIntField.value && - this.mandatoryLocaleField.value == that.mandatoryLocaleField.value && - this.mandatoryLongField.value == that.mandatoryLongField.value && - this.mandatoryPostalCodeField.value == that.mandatoryPostalCodeField.value && - this.mandatoryStringField.value == that.mandatoryStringField.value && - this.mandatoryTextareaField.value == that.mandatoryTextareaField.value && - this.mandatoryTimeZoneField.value == that.mandatoryTimeZoneField.value - case _ => false - } - def dirtyFields = this.allFields.filter(_.dirty_?) } @@ -238,9 +219,6 @@ class MongoFieldTypeTestRecord private () extends MongoRecord[MongoFieldTypeTest object mandatoryObjectIdField extends ObjectIdField(this) object legacyOptionalObjectIdField extends ObjectIdField(this) { override def optional_? = true } - object mandatoryPatternField extends PatternField(this) - object legacyOptionalPatternField extends PatternField(this) { override def optional_? = true } - object mandatoryUUIDField extends UUIDField(this) object legacyOptionalUUIDField extends UUIDField(this) { override def optional_? = true } @@ -248,19 +226,6 @@ class MongoFieldTypeTestRecord private () extends MongoRecord[MongoFieldTypeTest override def formats = owner.meta.formats } - override def equals(other: Any): Boolean = other match { - case that: MongoFieldTypeTestRecord => - this.id.value == that.id.value && - this.mandatoryDateField.value == that.mandatoryDateField.value && - this.mandatoryJsonObjectField.value == that.mandatoryJsonObjectField.value && - this.mandatoryObjectIdField.value == that.mandatoryObjectIdField.value && - this.mandatoryPatternField.value.pattern == that.mandatoryPatternField.value.pattern && - this.mandatoryPatternField.value.flags == that.mandatoryPatternField.value.flags && - this.mandatoryUUIDField.value == that.mandatoryUUIDField.value && - this.mandatoryMongoCaseClassField.value == that.mandatoryMongoCaseClassField.value - case _ => false - } - def dirtyFields = this.allFields.filter(_.dirty_?) } @@ -268,6 +233,38 @@ object MongoFieldTypeTestRecord extends MongoFieldTypeTestRecord with MongoMetaR override def formats = allFormats + new EnumSerializer(MyTestEnum) } +class PatternFieldTestRecord private () extends MongoRecord[PatternFieldTestRecord] with ObjectIdPk[PatternFieldTestRecord] { + def meta = PatternFieldTestRecord + + import java.util.regex.Pattern + + object mandatoryPatternField extends PatternField(this) + object legacyOptionalPatternField extends PatternField(this) { override def optional_? = true } + + /** + * Pattern.equals doesn't work properly, so a custom equals is required with PatternField + */ + override def equals(other: Any): Boolean = { + other match { + case that: PatternFieldTestRecord => + that.fields.corresponds(this.fields) { (a,b) => + (a.name == b.name) && ((a.valueBox, b.valueBox) match { + case (Full(ap: Pattern), Full(bp: Pattern)) => + ap.pattern == bp.pattern && ap.flags == bp.flags + case _ => a.valueBox == b.valueBox + }) + } + case _ => false + } + } + + def dirtyFields = this.allFields.filter(_.dirty_?) +} + +object PatternFieldTestRecord extends PatternFieldTestRecord with MongoMetaRecord[PatternFieldTestRecord] { + override def formats = allFormats +} + class PasswordTestRecord private () extends MongoRecord[PasswordTestRecord] with ObjectIdPk[PasswordTestRecord] { def meta = PasswordTestRecord @@ -379,12 +376,6 @@ class SubSubRecord private () extends BsonRecord[SubSubRecord] { def meta = SubSubRecord object name extends StringField(this, 12) - - override def equals(other: Any): Boolean = other match { - case that: SubSubRecord => - this.name.value == that.name.value - case _ => false - } } object SubSubRecord extends SubSubRecord with BsonMetaRecord[SubSubRecord] { override def formats = allFormats @@ -431,11 +422,6 @@ class NullTestRecord private () extends MongoRecord[NullTestRecord] with IntPk[N def defaultValue = JsonObj("1", null) } object jsonobjlist extends MongoJsonObjectListField[NullTestRecord, JsonObj](this, JsonObj) - - override def equals(other: Any): Boolean = other match { - case that: NullTestRecord => this.toString == that.toString - case _ => false - } } object NullTestRecord extends NullTestRecord with MongoMetaRecord[NullTestRecord] @@ -454,10 +440,6 @@ class BoxTestRecord private () extends MongoRecord[BoxTestRecord] with LongPk[Bo } object jsonobjlist extends MongoJsonObjectListField[BoxTestRecord, BoxTestJsonObj](this, BoxTestJsonObj) - override def equals(other: Any): Boolean = other match { - case that: BoxTestRecord => this.toString == that.toString - case _ => false - } } object BoxTestRecord extends BoxTestRecord with MongoMetaRecord[BoxTestRecord] { override def formats = super.formats + new JsonBoxSerializer diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala index d968d26ae0..a1edf42d24 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala @@ -1,5 +1,5 @@ /* - * Copyright 2006-2011 WorldWide Conferencing, LLC + * Copyright 2006-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. @@ -18,7 +18,7 @@ package net.liftweb package mongodb package record -import java.util.{Date, UUID} +import java.util.{Calendar, Date, UUID} import java.util.regex.Pattern import org.bson.types.ObjectId @@ -54,12 +54,13 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundExample def passBasicTests[A]( example: A, + example2: A, mandatory: MandatoryTypedField[A], legacyOptional: MandatoryTypedField[A], canCheckDefaultValues: Boolean = true )(implicit m: scala.reflect.Manifest[A]): Unit = { - def commonBehaviorsForAllFlavors(field: MandatoryTypedField[A]) = { + def commonBehaviorsForAllFlavors(field: MandatoryTypedField[A]): Unit = { "which have the correct initial value" in { field.value must be_==(field.defaultValue).when(canCheckDefaultValues) @@ -88,6 +89,30 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundExample // Failure("my failure") must_== Failure("my failure") pending } + + "which are only flagged as dirty_? when setBox is called with a different value" in { + field.clear + field match { + case owned: OwnedField[_] => owned.owner.runSafe { + field.resetDirty + } + case _ => field.resetDirty + } + field.dirty_? must_== false + val valueBox = field.valueBox + field.setBox(valueBox) + field.dirty_? must_== false + val exampleBox = Full(example) + (valueBox === exampleBox) must_== false + field.setBox(exampleBox) + field.dirty_? must_== true + val exampleBox2 = Full(example2) + (exampleBox === exampleBox2) must_== false + field.setBox(exampleBox2) + field.dirty_? must_== true + field.setBox(valueBox) + success + } } "support mandatory fields" in { @@ -191,7 +216,9 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundExample val rec = MongoFieldTypeTestRecord.createRecord val now = new Date val nowStr = rec.meta.formats.dateFormat.format(now) - passBasicTests(now, rec.mandatoryDateField, rec.legacyOptionalDateField, false) + val now2 = Calendar.getInstance() + now2.add(Calendar.DATE, 1) + passBasicTests(now, now2.getTime, rec.mandatoryDateField, rec.legacyOptionalDateField, false) passConversionTests( now, rec.mandatoryDateField, @@ -204,8 +231,9 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundExample "JsonObjectField" should { val rec = MongoFieldTypeTestRecord.createRecord val ttjo = TypeTestJsonObject(1, "jsonobj1", Map("x" -> "a")) + val ttjo2 = TypeTestJsonObject(2, "jsonobj2", Map("x" -> "b")) val json = ("intField" -> 1) ~ ("stringField" -> "jsonobj1") ~ ("mapField" -> (("x" -> "a"))) - passBasicTests(ttjo, rec.mandatoryJsonObjectField, rec.legacyOptionalJsonObjectField) + passBasicTests(ttjo, ttjo2, rec.mandatoryJsonObjectField, rec.legacyOptionalJsonObjectField) passConversionTests( ttjo, rec.mandatoryJsonObjectField, @@ -220,7 +248,8 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundExample "ObjectIdField" should { val rec = MongoFieldTypeTestRecord.createRecord val oid = ObjectId.get - passBasicTests(oid, rec.mandatoryObjectIdField, rec.legacyOptionalObjectIdField, false) + val oid2 = ObjectId.get + passBasicTests(oid, oid2, rec.mandatoryObjectIdField, rec.legacyOptionalObjectIdField, false) passConversionTests( oid, rec.mandatoryObjectIdField, @@ -231,9 +260,10 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundExample } "PatternField" should { - val rec = MongoFieldTypeTestRecord.createRecord + val rec = PatternFieldTestRecord.createRecord val ptrn = Pattern.compile("^Mo", Pattern.CASE_INSENSITIVE) - passBasicTests(ptrn, rec.mandatoryPatternField, rec.legacyOptionalPatternField, false) + val ptrn2 = Pattern.compile("^MON", Pattern.CASE_INSENSITIVE) + passBasicTests(ptrn, ptrn2, rec.mandatoryPatternField, rec.legacyOptionalPatternField, false) passConversionTests( ptrn, rec.mandatoryPatternField, @@ -246,7 +276,8 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundExample "UUIDField" should { val rec = MongoFieldTypeTestRecord.createRecord val uuid = UUID.randomUUID - passBasicTests(uuid, rec.mandatoryUUIDField, rec.legacyOptionalUUIDField, false) + val uuid2 = UUID.randomUUID + passBasicTests(uuid, uuid2, rec.mandatoryUUIDField, rec.legacyOptionalUUIDField, false) passConversionTests( uuid, rec.mandatoryUUIDField, @@ -280,7 +311,8 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundExample "function correctly" in { val rec = ListTestRecord.createRecord val lst = List("abc", "def", "ghi") - passBasicTests(lst, rec.mandatoryStringListField, rec.legacyOptionalStringListField) + val lst2 = List("ab", "de", "gh") + passBasicTests(lst, lst2, rec.mandatoryStringListField, rec.legacyOptionalStringListField) passConversionTests( lst, rec.mandatoryStringListField, @@ -295,7 +327,8 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundExample "function correctly" in { val rec = ListTestRecord.createRecord val lst = List(4, 5, 6) - passBasicTests(lst, rec.mandatoryIntListField, rec.legacyOptionalIntListField) + val lst2 = List(1, 2, 3) + passBasicTests(lst, lst2, rec.mandatoryIntListField, rec.legacyOptionalIntListField) passConversionTests( lst, rec.mandatoryIntListField, @@ -310,11 +343,12 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundExample "function correctly" in { val rec = ListTestRecord.createRecord val lst = List(TypeTestJsonObject(1, "jsonobj1", Map("x" -> "1")), TypeTestJsonObject(2, "jsonobj2", Map("x" -> "2"))) + val lst2 = List(TypeTestJsonObject(3, "jsonobj3", Map("x" -> "3")), TypeTestJsonObject(4, "jsonobj4", Map("x" -> "4"))) val json = List( ("intField" -> 1) ~ ("stringField" -> "jsonobj1") ~ ("mapField" -> (("x" -> "1"))), ("intField" -> 2) ~ ("stringField" -> "jsonobj2") ~ ("mapField" -> (("x" -> "2"))) ) - passBasicTests(lst, rec.mandatoryMongoJsonObjectListField, rec.legacyOptionalMongoJsonObjectListField) + passBasicTests(lst, lst2, rec.mandatoryMongoJsonObjectListField, rec.legacyOptionalMongoJsonObjectListField) passConversionTests( lst, rec.mandatoryMongoJsonObjectListField, @@ -340,7 +374,8 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundExample "function correctly" in { val rec = MapTestRecord.createRecord val map = Map("a" -> "abc", "b" -> "def", "c" -> "ghi") - passBasicTests(map, rec.mandatoryStringMapField, rec.legacyOptionalStringMapField) + val map2 = Map("a" -> "ab", "b" -> "de", "c" -> "gh") + passBasicTests(map, map2, rec.mandatoryStringMapField, rec.legacyOptionalStringMapField) passConversionTests( map, rec.mandatoryStringMapField, @@ -359,7 +394,8 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundExample "function correctly" in { val rec = MapTestRecord.createRecord val map = Map("a" -> 4, "b" -> 5, "c" -> 6) - passBasicTests(map, rec.mandatoryIntMapField, rec.legacyOptionalIntMapField) + val map2 = Map("a" -> 1, "b" -> 2, "c" -> 3) + passBasicTests(map, map2, rec.mandatoryIntMapField, rec.legacyOptionalIntMapField) passConversionTests( map, rec.mandatoryIntMapField, @@ -378,32 +414,27 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundExample "function correctly" in { val rec = SubRecordTestRecord.createRecord val subRec = SubRecord.createRecord.name("subrecord") + val subRec2 = SubRecord.createRecord.name("subrecord2") val srJson = - JObject(List( - JField("name", JString("subrecord")), - JField("subsub", JObject(List( - JField("name", JString("")) - ))), - JField("subsublist", JArray(List())), - JField("when", JObject(List( - JField("$dt", JString(rec.meta.formats.dateFormat.format(subRec.when.value))) - ))), - JField("slist", JArray(List())), - JField("smap", JObject(List())), - JField("oid", JObject(List(JField("$oid", JString(subRec.oid.value.toString))))), - JField("pattern", JObject(List( - JField("$regex", JString(subRec.pattern.value.pattern)), - JField("$flags", JInt(subRec.pattern.value.flags)) - ))), - JField("uuid", JObject(List(JField("$uuid", JString(subRec.uuid.value.toString))))) - )) + ("name" -> "subrecord") ~ + ("subsub" -> ("name" -> "")) ~ + ("subsublist" -> JArray(Nil)) ~ + ("when" -> ("$dt" -> rec.meta.formats.dateFormat.format(subRec.when.value))) ~ + ("slist" -> JArray(Nil)) ~ + ("smap" -> JObject(Nil)) ~ + ("oid" -> ("$oid" -> subRec.oid.value.toString)) ~ + ("pattern" -> + ("$regex" -> subRec.pattern.value.pattern) ~ + ("$flags" -> subRec.pattern.value.flags) + ) ~ + ("uuid" -> ("$uuid" -> subRec.uuid.value.toString)) val srJsExp = new JsExp { def toJsCmd = Printer.compact(render(srJson)) } - passBasicTests(subRec, rec.mandatoryBsonRecordField, rec.legacyOptionalBsonRecordField, false) + passBasicTests(subRec, subRec2, rec.mandatoryBsonRecordField, rec.legacyOptionalBsonRecordField, false) passConversionTests( subRec, rec.mandatoryBsonRecordField, @@ -418,44 +449,35 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundExample "function correctly" in { val rec = SubRecordTestRecord.createRecord val lst = List(SubRecord.createRecord.name("subrec1"), SubRecord.createRecord.name("subrec2")) + val lst2 = List(SubRecord.createRecord.name("subrec3"), SubRecord.createRecord.name("subrec4")) val sr1Json = - JObject(List( - JField("name", JString("subrec1")), - JField("subsub", JObject(List( - JField("name", JString("")) - ))), - JField("subsublist", JArray(List())), - JField("when", JObject(List( - JField("$dt", JString(rec.meta.formats.dateFormat.format(lst(0).when.value))) - ))), - JField("slist", JArray(List())), - JField("smap", JObject(List())), - JField("oid", JObject(List(JField("$oid", JString(lst(0).oid.value.toString))))), - JField("pattern", JObject(List( - JField("$regex", JString(lst(0).pattern.value.pattern)), - JField("$flags", JInt(lst(0).pattern.value.flags)) - ))), - JField("uuid", JObject(List(JField("$uuid", JString(lst(0).uuid.value.toString))))) - )) + ("name" -> "subrec1") ~ + ("subsub" -> ("name" -> "")) ~ + ("subsublist" -> JArray(Nil)) ~ + ("when" -> ("$dt" -> rec.meta.formats.dateFormat.format(lst(0).when.value))) ~ + ("slist" -> JArray(Nil)) ~ + ("smap" -> JObject(Nil)) ~ + ("oid" -> ("$oid" -> lst(0).oid.value.toString)) ~ + ("pattern" -> + ("$regex" -> lst(0).pattern.value.pattern) ~ + ("$flags" -> lst(0).pattern.value.flags) + ) ~ + ("uuid" -> ("$uuid" -> lst(0).uuid.value.toString)) + val sr2Json = - JObject(List( - JField("name", JString("subrec2")), - JField("subsub", JObject(List( - JField("name", JString("")) - ))), - JField("subsublist", JArray(List())), - JField("when", JObject(List( - JField("$dt", JString(rec.meta.formats.dateFormat.format(lst(1).when.value))) - ))), - JField("slist", JArray(List())), - JField("smap", JObject(List())), - JField("oid", JObject(List(JField("$oid", JString(lst(1).oid.value.toString))))), - JField("pattern", JObject(List( - JField("$regex", JString(lst(1).pattern.value.pattern)), - JField("$flags", JInt(lst(1).pattern.value.flags)) - ))), - JField("uuid", JObject(List(JField("$uuid", JString(lst(1).uuid.value.toString))))) - )) + ("name" -> "subrec2") ~ + ("subsub" -> ("name" -> "")) ~ + ("subsublist" -> JArray(Nil)) ~ + ("when" -> ("$dt" -> rec.meta.formats.dateFormat.format(lst(1).when.value))) ~ + ("slist" -> JArray(Nil)) ~ + ("smap" -> JObject(Nil)) ~ + ("oid" -> ("$oid" -> lst(1).oid.value.toString)) ~ + ("pattern" -> + ("$regex" -> lst(1).pattern.value.pattern) ~ + ("$flags" -> lst(1).pattern.value.flags) + ) ~ + ("uuid" -> ("$uuid" -> lst(1).uuid.value.toString)) + val sr1JsExp = new JsExp { def toJsCmd = compact(render(sr1Json)) } @@ -463,7 +485,7 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundExample def toJsCmd = compact(render(sr2Json)) } - passBasicTests(lst, rec.mandatoryBsonRecordListField, rec.legacyOptionalBsonRecordListField) + passBasicTests(lst, lst2, rec.mandatoryBsonRecordListField, rec.legacyOptionalBsonRecordListField) passConversionTests( lst, rec.mandatoryBsonRecordListField, diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala index 096f9ecbd3..bd2b8f2965 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala @@ -1,5 +1,5 @@ /* - * Copyright 2010-2011 WorldWide Conferencing, LLC + * 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. @@ -53,7 +53,7 @@ class MongoRecordSpec extends Specification with MongoTestKit { val rec = MongoFieldTypeTestRecord.createRecord val allExpectedFieldNames: List[String] = "_id" :: "mandatoryMongoCaseClassField" :: (for { - typeName <- "Date JsonObject ObjectId Pattern UUID".split(" ") + typeName <- "Date JsonObject ObjectId UUID".split(" ") flavor <- "mandatory legacyOptional".split(" ") } yield flavor + typeName + "Field").toList @@ -196,7 +196,6 @@ class MongoRecordSpec extends Specification with MongoTestKit { .mandatoryDateField(new Date) .mandatoryJsonObjectField(TypeTestJsonObject(1, "jsonobj1", Map("x" -> "1"))) .mandatoryObjectIdField(ObjectId.get) - .mandatoryPatternField(Pattern.compile("^Mo", Pattern.CASE_INSENSITIVE)) .mandatoryUUIDField(UUID.randomUUID) .mandatoryMongoCaseClassField(MongoCaseClassTestObject(1,"str",MyTestEnum.TWO)) @@ -205,15 +204,21 @@ class MongoRecordSpec extends Specification with MongoTestKit { ("mandatoryDateField" -> ("$dt" -> mfttr.meta.formats.dateFormat.format(mfttr.mandatoryDateField.value))) ~ ("legacyOptionalDateField" -> (None: Option[JObject])) ~ ("mandatoryJsonObjectField" -> (("intField" -> 1) ~ ("stringField" -> "jsonobj1") ~ ("mapField" -> ("x" -> "1")))) ~ - ("legacyOptionalJsonObjectField" -> (("intField" -> 0) ~ ("stringField" -> "") ~ ("mapField" -> JObject(Nil)))) ~ + ("legacyOptionalJsonObjectField" -> (None: Option[JObject])) ~ ("mandatoryObjectIdField", ("$oid" -> mfttr.mandatoryObjectIdField.value.toString)) ~ ("legacyOptionalObjectIdField" -> (None: Option[JObject])) ~ - ("mandatoryPatternField" -> (("$regex" -> mfttr.mandatoryPatternField.value.pattern) ~ ("$flags" -> mfttr.mandatoryPatternField.value.flags))) ~ - ("legacyOptionalPatternField" -> (None: Option[JObject])) ~ ("mandatoryUUIDField" -> ("$uuid" -> mfttr.mandatoryUUIDField.value.toString)) ~ ("legacyOptionalUUIDField" -> (None: Option[JObject])) ~ ("mandatoryMongoCaseClassField" -> ("intField" -> 1) ~ ("stringField" -> "str") ~ ("enum" -> 1)) + val pftr = PatternFieldTestRecord.createRecord + .mandatoryPatternField(Pattern.compile("^Mo", Pattern.CASE_INSENSITIVE)) + + val pftrJson = + ("_id" -> ("$oid" -> pftr.id.toString)) ~ + ("mandatoryPatternField" -> (("$regex" -> pftr.mandatoryPatternField.value.pattern) ~ ("$flags" -> pftr.mandatoryPatternField.value.flags))) ~ + ("legacyOptionalPatternField" -> (None: Option[JObject])) + val ltr = ListTestRecord.createRecord .mandatoryStringListField(List("abc", "def", "ghi")) .mandatoryIntListField(List(4, 5, 6)) @@ -371,6 +376,14 @@ class MongoRecordSpec extends Specification with MongoTestKit { tr mustEqual mfttr } + pftr.save + + val pftrFromDb = PatternFieldTestRecord.find(pftr.id.value) + pftrFromDb.isDefined must_== true + pftrFromDb foreach { tr => + tr mustEqual pftr + } + ltr.save val ltrFromDb = ListTestRecord.find(ltr.id.value) @@ -406,6 +419,15 @@ class MongoRecordSpec extends Specification with MongoTestKit { tr mustEqual mfttrDef } + val pftrDef = PatternFieldTestRecord.createRecord + pftrDef.save + + val pftrFromDb = PatternFieldTestRecord.find(pftrDef.id.value) + pftrFromDb.isDefined must_== true + pftrFromDb foreach { tr => + tr mustEqual pftrDef + } + val ltrDef = ListTestRecord.createRecord ltrDef.save @@ -437,6 +459,8 @@ class MongoRecordSpec extends Specification with MongoTestKit { "convert Mongo type fields to JValue" in { mfttr.asJValue mustEqual mfttrJson + pftr.asJValue mustEqual pftrJson + ltr.asJValue mustEqual ltrJson mtr.asJValue mustEqual mtrJson @@ -456,6 +480,12 @@ class MongoRecordSpec extends Specification with MongoTestKit { tr mustEqual mfttr } + val pftrFromJson = PatternFieldTestRecord.fromJsonString(compact(render(pftrJson))) + pftrFromJson.isDefined must_== true + pftrFromJson foreach { tr => + tr mustEqual pftr + } + val ltrFromJson = ListTestRecord.fromJsonString(compact(render(ltrJson))) ltrFromJson.isDefined must_== true ltrFromJson foreach { tr => @@ -691,7 +721,6 @@ class MongoRecordSpec extends Specification with MongoTestKit { rec.dirtyFields.length must_== 0 } - val fttr2 = FieldTypeTestRecord.createRecord.save fttr2.legacyOptionalStringField("legacy optional string") @@ -730,9 +759,6 @@ class MongoRecordSpec extends Specification with MongoTestKit { mfttr.mandatoryObjectIdField(ObjectId.get) mfttr.mandatoryObjectIdField.dirty_? must_== true - mfttr.mandatoryPatternField(Pattern.compile("^Mon", Pattern.CASE_INSENSITIVE)) - mfttr.mandatoryPatternField.dirty_? must_== true - mfttr.mandatoryUUIDField(UUID.randomUUID) mfttr.mandatoryUUIDField.dirty_? must_== true @@ -742,7 +768,7 @@ class MongoRecordSpec extends Specification with MongoTestKit { mfttr.legacyOptionalObjectIdField(Empty) mfttr.legacyOptionalObjectIdField.dirty_? must_== true - mfttr.dirtyFields.length must_== 7 + mfttr.dirtyFields.length must_== 6 mfttr.update mfttr.dirtyFields.length must_== 0 @@ -773,6 +799,25 @@ class MongoRecordSpec extends Specification with MongoTestKit { } } + /* save throws an exception here but not above ??? + "update dirty fields for a PatternFieldTestRecord" in { + val pftrd = PatternFieldTestRecord.createRecord.save + + pftrd.mandatoryPatternField(Pattern.compile("^Mon", Pattern.CASE_INSENSITIVE)) + pftrd.mandatoryPatternField.dirty_? must_== true + + pftrd.dirtyFields.length must_== 1 + pftrd.update + pftrd.dirtyFields.length must_== 0 + + val fromDb = PatternFieldTestRecord.find(pftrd.id.is) + fromDb.isDefined must_== true + fromDb foreach { rec => + rec must_== pftrd + rec.dirtyFields.length must_== 0 + } + }*/ + "update dirty fields for a ListTestRecord" in { val ltr = ListTestRecord.createRecord.save @@ -822,8 +867,6 @@ class MongoRecordSpec extends Specification with MongoTestKit { } "update dirty fields for a SubRecordTestRecord" in { - val srtr = SubRecordTestRecord.createRecord.save - val ssr1 = SubSubRecord.createRecord.name("SubSubRecord1") val ssr2 = SubSubRecord.createRecord.name("SubSubRecord2") @@ -835,9 +878,13 @@ class MongoRecordSpec extends Specification with MongoTestKit { .smap(Map("a" -> "s1", "b" -> "s2")) .pattern(Pattern.compile("^Mon", Pattern.CASE_INSENSITIVE)) - val sr2 = SubRecord.createRecord.name("SubRecord2") + val srtr = SubRecordTestRecord.createRecord + .mandatoryBsonRecordField(sr1) + .save + + val sr2 = sr1.copy.name("SubRecord2") - srtr.mandatoryBsonRecordField(sr1) + srtr.mandatoryBsonRecordField(sr2) srtr.mandatoryBsonRecordField.dirty_? must_== true srtr.mandatoryBsonRecordListField(List(sr1,sr2)) diff --git a/persistence/record/src/main/scala/net/liftweb/record/Field.scala b/persistence/record/src/main/scala/net/liftweb/record/Field.scala index b4a65cbd91..0845cd6868 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/Field.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/Field.scala @@ -1,5 +1,5 @@ /* - * Copyright 2007-2011 WorldWide Conferencing, LLC + * 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. @@ -38,6 +38,11 @@ trait BaseField extends FieldIdentifier with util.BaseField { def dirty_? : Boolean = dirty + /** + * Should the dirty flag always be set when setBox is called + */ + def forceDirty_? : Boolean = false + /** * Should the field be ignored by the OR Mapper? */ @@ -146,7 +151,7 @@ trait OwnedField[OwnerType <: Record[OwnerType]] extends BaseField { /** Refined trait for fields holding a particular value type */ trait TypedField[ThisType] extends BaseField { - + /* * Unless overriden, MyType is equal to ThisType. Available for * backwards compatibility @@ -225,19 +230,22 @@ trait TypedField[ThisType] extends BaseField { case (f: Failure) => set_!(f) // preserve failures set in case _ => Failure(notOptionalErrorMessage) } - if(!dirty_?) { - val same = (oldValue, data) match { - case (Full(ov), Full(nv)) => ov == nv - case (a, b) => a == b - } - dirty_?(!same) + if (forceDirty_?) { + dirty_?(true) + } + else if (!dirty_?) { + val same = (oldValue, data) match { + case (Full(ov), Full(nv)) => ov == nv + case (a, b) => a == b + } + dirty_?(!same) } data } // Helper methods for things to easily use mixins and so on that use ValueType instead of Box[MyType], regardless of the optional-ness of the field protected def toValueType(in: Box[MyType]): ValueType - + protected def toBoxMyType(in: ValueType): Box[MyType] protected def set_!(in: Box[MyType]): Box[MyType] = runFilters(in, setFilterBox) @@ -326,7 +334,7 @@ trait TypedField[ThisType] extends BaseField { } trait MandatoryTypedField[ThisType] extends TypedField[ThisType] with Product1[ThisType] { - + /** * ValueType represents the type that users will work with. For MandatoryTypeField, this is * equal to ThisType. @@ -353,7 +361,7 @@ trait MandatoryTypedField[ThisType] extends TypedField[ThisType] with Product1[T def value: MyType = valueBox openOr defaultValue def get: MyType = value - + def is: MyType = value protected def liftSetFilterToBox(in: Box[MyType]): Box[MyType] = in.map(v => setFilter.foldLeft(v)((prev, f) => f(prev))) @@ -373,7 +381,7 @@ trait MandatoryTypedField[ThisType] extends TypedField[ThisType] with Product1[T } trait OptionalTypedField[ThisType] extends TypedField[ThisType] with Product1[Box[ThisType]] { - + /** * ValueType represents the type that users will work with. For OptionalTypedField, this is * equal to Option[ThisType]. @@ -395,16 +403,16 @@ trait OptionalTypedField[ThisType] extends TypedField[ThisType] with Product1[Bo def set(in: Option[MyType]): Option[MyType] = setBox(in) or defaultValueBox def toValueType(in: Box[MyType]) = in - + def toBoxMyType(in: ValueType) = in def value: Option[MyType] = valueBox def get: Option[MyType] = value - + def is: Option[MyType] = value - protected def liftSetFilterToBox(in: Box[MyType]): Box[MyType] = setFilter.foldLeft(in){ (prev, f) => + protected def liftSetFilterToBox(in: Box[MyType]): Box[MyType] = setFilter.foldLeft(in){ (prev, f) => prev match { case fail: Failure => fail //stop on failure, otherwise some filters will clober it to Empty case other => f(other) diff --git a/persistence/record/src/main/scala/net/liftweb/record/MetaRecord.scala b/persistence/record/src/main/scala/net/liftweb/record/MetaRecord.scala index f8e5d6463f..680204b446 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/MetaRecord.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/MetaRecord.scala @@ -440,6 +440,27 @@ trait MetaRecord[BaseRecord <: Record[BaseRecord]] { } } + /** + * Populate the fields of the record with values from an existing record + * + * @param inst - The record to populate + * @param rec - The Record to read from + */ + def setFieldsFromRecord(inst: BaseRecord, rec: BaseRecord) { + for { + fh <- fieldList + fld <- rec.fieldByName(fh.name) + } { + fh.field(inst).setFromAny(fld.valueBox) + } + } + + def copy(rec: BaseRecord): BaseRecord = { + val inst = createRecord + setFieldsFromRecord(inst, rec) + inst + } + /** * Defined the order of the fields in this record * diff --git a/persistence/record/src/main/scala/net/liftweb/record/Record.scala b/persistence/record/src/main/scala/net/liftweb/record/Record.scala index 753f955c91..63fca284ea 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/Record.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/Record.scala @@ -1,5 +1,5 @@ /* - * Copyright 2007-2011 WorldWide Conferencing, LLC + * 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. @@ -14,8 +14,8 @@ * limitations under the License. */ -package net.liftweb -package record +package net.liftweb +package record import common._ import http.js.{JsExp, JsObj} @@ -82,7 +82,7 @@ trait Record[MyType <: Record[MyType]] extends FieldContainer { * Save the instance and return the instance */ def saveTheRecord(): Box[MyType] = throw new BackingStoreException("Raw Records don't save themselves") - + /** * Retuns the JSON representation of this record, converts asJValue to JsObj * @@ -141,6 +141,30 @@ trait Record[MyType <: Record[MyType]] extends FieldContainer { * @return Box[MappedField] */ def fieldByName(fieldName: String): Box[Field[_, MyType]] = meta.fieldByName(fieldName, this) + + override def equals(other: Any): Boolean = { + other match { + case that: Record[MyType] => + that.fields.corresponds(this.fields) { (a,b) => + a.name == b.name && a.valueBox == b.valueBox + } + case _ => false + } + } + + override def toString = { + val fieldList = this.fields.map(f => "%s=%s" format (f.name, + f.valueBox match { + case Full(c: java.util.Calendar) => c.getTime().toString() + case Full(null) => "null" + case Full(v) => v.toString + case _ => "" + })) + + "%s={%s}" format (this.getClass.toString, fieldList.mkString(", ")) + } + + def copy: MyType = meta.copy(this) } trait ExpandoRecord[MyType <: Record[MyType] with ExpandoRecord[MyType]] { diff --git a/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala b/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala index a73498b208..e356508d5d 100644 --- a/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala +++ b/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala @@ -44,8 +44,7 @@ object FieldSpec extends Specification { lazy val session = new LiftSession("", randomString(20), Empty) - def passBasicTests[A](example: A, mandatory: MandatoryTypedField[A], legacyOptional: MandatoryTypedField[A], optional: OptionalTypedField[A])(implicit m: scala.reflect.Manifest[A]): Fragment = { - + def passBasicTests[A](example: A, example2: A, mandatory: MandatoryTypedField[A], legacyOptional: MandatoryTypedField[A], optional: OptionalTypedField[A])(implicit m: scala.reflect.Manifest[A]): Fragment = { val canCheckDefaultValues = !mandatory.defaultValue.isInstanceOf[Calendar] // don't try to use the default value of date/time typed fields, because it changes from moment to moment! @@ -75,7 +74,7 @@ object FieldSpec extends Specification { in.get must_== in.defaultValue } } - + if(!in.optional_?) { "which fail when set with an empty string when not optional" in { in.setFromString(null) @@ -130,29 +129,31 @@ object FieldSpec extends Specification { success } - if(canCheckDefaultValues) { - "which are only flagged as dirty_? when setBox is called with a different value" in { - in.clear - in match { - case owned: OwnedField[_] => owned.owner.runSafe { - in.resetDirty - } - case _ => in.resetDirty - } - in.dirty_? must_== false - val valueBox = in.valueBox - in.setBox(valueBox) - in.dirty_? must_== false - val exampleBox = Full(example) - (valueBox === exampleBox) must_== false - in.setBox(exampleBox) - in.dirty_? must_== true - //dirty value should remain true, even if the same value is set twice before persisting - in.setBox(exampleBox) - in.dirty_? must_== true - in.setBox(valueBox) - success - } + "which are only flagged as dirty_? when setBox is called with a different value" in { + in.clear + in match { + case owned: OwnedField[_] => owned.owner.runSafe { + in.resetDirty + } + case _ => in.resetDirty + } + in.dirty_? must_== false + val valueBox = in.valueBox + in.setBox(valueBox) + in.dirty_? must_== false + val exampleBox = Full(example) + (valueBox === exampleBox) must_== false + in.setBox(exampleBox) + in.dirty_? must_== true + val exampleBox2 = Full(example2) + (exampleBox === exampleBox2) must_== false + in.setBox(exampleBox2) + in.dirty_? must_== true + //dirty value should remain true, even if the same value is set twice before persisting + in.setBox(exampleBox) + in.dirty_? must_== true + in.setBox(valueBox) + success } } @@ -219,7 +220,7 @@ object FieldSpec extends Specification { "which initialize to Empty" in { optional.valueBox must_== Empty } - + "which don't fail when set with an empty string" in { optional.setFromString(null) optional.value must_== None @@ -301,7 +302,8 @@ object FieldSpec extends Specification { "BooleanField" should { val rec = FieldTypeTestRecord.createRecord val bool = true - passBasicTests(bool, rec.mandatoryBooleanField, rec.legacyOptionalBooleanField, rec.optionalBooleanField) + val bool2 = false + passBasicTests(bool, bool2, rec.mandatoryBooleanField, rec.legacyOptionalBooleanField, rec.optionalBooleanField) passConversionTests( bool, rec.mandatoryBooleanField, @@ -331,7 +333,8 @@ object FieldSpec extends Specification { S.initIfUninitted(session){ val rec = FieldTypeTestRecord.createRecord val country = Countries.Canada - passBasicTests(country, rec.mandatoryCountryField, rec.legacyOptionalCountryField, rec.optionalCountryField) + val country2 = Countries.USA + passBasicTests(country, country2, rec.mandatoryCountryField, rec.legacyOptionalCountryField, rec.optionalCountryField) passConversionTests( country, rec.mandatoryCountryField, @@ -345,8 +348,10 @@ object FieldSpec extends Specification { "DateTimeField" should { val rec = FieldTypeTestRecord.createRecord val dt = Calendar.getInstance + val dt2 = Calendar.getInstance + dt2.add(Calendar.DATE, 1) val dtStr = toInternetDate(dt.getTime) - passBasicTests(dt, rec.mandatoryDateTimeField, rec.legacyOptionalDateTimeField, rec.optionalDateTimeField) + passBasicTests(dt, dt2, rec.mandatoryDateTimeField, rec.legacyOptionalDateTimeField, rec.optionalDateTimeField) passConversionTests( dt, rec.mandatoryDateTimeField, @@ -372,7 +377,8 @@ object FieldSpec extends Specification { "DecimalField" should { val rec = FieldTypeTestRecord.createRecord val bd = BigDecimal("12.34") - passBasicTests(bd, rec.mandatoryDecimalField, rec.legacyOptionalDecimalField, rec.optionalDecimalField) + val bd2 = BigDecimal("1.22") + passBasicTests(bd, bd2, rec.mandatoryDecimalField, rec.legacyOptionalDecimalField, rec.optionalDecimalField) passConversionTests( bd, rec.mandatoryDecimalField, @@ -385,7 +391,8 @@ object FieldSpec extends Specification { "DoubleField" should { val rec = FieldTypeTestRecord.createRecord val d = 12.34 - passBasicTests(d, rec.mandatoryDoubleField, rec.legacyOptionalDoubleField, rec.optionalDoubleField) + val d2 = 1.22 + passBasicTests(d, d2, rec.mandatoryDoubleField, rec.legacyOptionalDoubleField, rec.optionalDoubleField) passConversionTests( d, rec.mandatoryDoubleField, @@ -399,7 +406,8 @@ object FieldSpec extends Specification { val session = new LiftSession("", randomString(20), Empty) val rec = FieldTypeTestRecord.createRecord val email = "foo@bar.baz" - passBasicTests(email, rec.mandatoryEmailField, rec.legacyOptionalEmailField, rec.optionalEmailField) + val email2 = "foo2@bar.baz" + passBasicTests(email, email2, rec.mandatoryEmailField, rec.legacyOptionalEmailField, rec.optionalEmailField) passConversionTests( email, rec.mandatoryEmailField, @@ -436,7 +444,8 @@ object FieldSpec extends Specification { "EnumField" should { val rec = FieldTypeTestRecord.createRecord val ev = MyTestEnum.TWO - passBasicTests(ev, rec.mandatoryEnumField, rec.legacyOptionalEnumField, rec.optionalEnumField) + val ev2 = MyTestEnum.ONE + passBasicTests(ev, ev2, rec.mandatoryEnumField, rec.legacyOptionalEnumField, rec.optionalEnumField) passConversionTests( ev, rec.mandatoryEnumField, @@ -449,7 +458,8 @@ object FieldSpec extends Specification { "IntField" should { val rec = FieldTypeTestRecord.createRecord val num = 123 - passBasicTests(num, rec.mandatoryIntField, rec.legacyOptionalIntField, rec.optionalIntField) + val num2 = 456 + passBasicTests(num, num2, rec.mandatoryIntField, rec.legacyOptionalIntField, rec.optionalIntField) passConversionTests( num, rec.mandatoryIntField, @@ -465,13 +475,18 @@ object FieldSpec extends Specification { case "en_US" => "en_GB" case _ => "en_US" } - passBasicTests(example, rec.mandatoryLocaleField, rec.legacyOptionalLocaleField, rec.optionalLocaleField) + val example2 = java.util.Locale.getDefault.toString match { + case "es_ES" => "en_NZ" + case _ => "es_ES" + } + passBasicTests(example, example2, rec.mandatoryLocaleField, rec.legacyOptionalLocaleField, rec.optionalLocaleField) } "LongField" should { val rec = FieldTypeTestRecord.createRecord val lng = 1234L - passBasicTests(lng, rec.mandatoryLongField, rec.legacyOptionalLongField, rec.optionalLongField) + val lng2 = 5678L + passBasicTests(lng, lng2, rec.mandatoryLongField, rec.legacyOptionalLongField, rec.optionalLongField) passConversionTests( lng, rec.mandatoryLongField, @@ -515,8 +530,9 @@ object FieldSpec extends Specification { val session = new LiftSession("", randomString(20), Empty) val rec = FieldTypeTestRecord.createRecord val zip = "02452" + val zip2 = "03344" rec.mandatoryCountryField.set(Countries.USA) - passBasicTests(zip, rec.mandatoryPostalCodeField, rec.legacyOptionalPostalCodeField, rec.optionalPostalCodeField) + passBasicTests(zip, zip2, rec.mandatoryPostalCodeField, rec.legacyOptionalPostalCodeField, rec.optionalPostalCodeField) passConversionTests( zip, rec.mandatoryPostalCodeField, @@ -554,7 +570,8 @@ object FieldSpec extends Specification { { val rec = FieldTypeTestRecord.createRecord val str = "foobar" - passBasicTests(str, rec.mandatoryStringField, rec.legacyOptionalStringField, rec.optionalStringField) + val str2 = "foobaz" + passBasicTests(str, str2, rec.mandatoryStringField, rec.legacyOptionalStringField, rec.optionalStringField) passConversionTests( str, rec.mandatoryStringField, @@ -640,7 +657,8 @@ object FieldSpec extends Specification { "TextareaField" should { val rec = FieldTypeTestRecord.createRecord val txt = "foobar" - passBasicTests(txt, rec.mandatoryTextareaField, rec.legacyOptionalTextareaField, rec.optionalTextareaField) + val txt2 = "foobaz" + passBasicTests(txt, txt2, rec.mandatoryTextareaField, rec.legacyOptionalTextareaField, rec.optionalTextareaField) passConversionTests( txt, rec.mandatoryTextareaField, @@ -656,7 +674,11 @@ object FieldSpec extends Specification { case "America/New_York" => "Europe/London" case _ => "America/New_York" } - passBasicTests(example, rec.mandatoryTimeZoneField, rec.legacyOptionalTimeZoneField, rec.optionalTimeZoneField) + val example2 = java.util.TimeZone.getDefault.getID match { + case "America/Chicago" => "Europe/Paris" + case _ => "America/Chicago" + } + passBasicTests(example, example2, rec.mandatoryTimeZoneField, rec.legacyOptionalTimeZoneField, rec.optionalTimeZoneField) passConversionTests( example, rec.mandatoryTimeZoneField, diff --git a/persistence/record/src/test/scala/net/liftweb/record/Fixtures.scala b/persistence/record/src/test/scala/net/liftweb/record/Fixtures.scala index 87e0a20d13..8ad5d9d670 100644 --- a/persistence/record/src/test/scala/net/liftweb/record/Fixtures.scala +++ b/persistence/record/src/test/scala/net/liftweb/record/Fixtures.scala @@ -212,24 +212,18 @@ class FieldTypeTestRecord private () extends Record[FieldTypeTestRecord] { object legacyOptionalTimeZoneField extends TimeZoneField(this) { override def optional_? = true } object optionalTimeZoneField extends OptionalTimeZoneField(this) + + def fieldsToCompare = { + fields + .filterNot(_.name == "mandatoryBinaryField") // binarys don't compare + .filterNot(_.name == "mandatoryDateTimeField") // toInternetDate is lossy (doesn't retain time to ms precision) + } + override def equals(other: Any): Boolean = other match { - case that:FieldTypeTestRecord => - //this.mandatoryBinaryField.value mustEqual that.mandatoryBinaryField.value - this.mandatoryBooleanField.value == that.mandatoryBooleanField.value && - this.mandatoryCountryField.value == that.mandatoryCountryField.value && - Helpers.toInternetDate(this.mandatoryDateTimeField.value.getTime) == - Helpers.toInternetDate(that.mandatoryDateTimeField.value.getTime) && - //this.mandatoryDecimalField.value == that.mandatoryDecimalField.value && - this.mandatoryDoubleField.value == that.mandatoryDoubleField.value && - this.mandatoryEmailField.value == that.mandatoryEmailField.value && - this.mandatoryEnumField.value == that.mandatoryEnumField.value && - this.mandatoryIntField.value == that.mandatoryIntField.value && - this.mandatoryLocaleField.value == that.mandatoryLocaleField.value && - this.mandatoryLongField.value == that.mandatoryLongField.value && - this.mandatoryPostalCodeField.value == that.mandatoryPostalCodeField.value && - this.mandatoryStringField.value == that.mandatoryStringField.value && - this.mandatoryTextareaField.value == that.mandatoryTextareaField.value && - this.mandatoryTimeZoneField.value == that.mandatoryTimeZoneField.value + case that: FieldTypeTestRecord => + that.fieldsToCompare.corresponds(this.fieldsToCompare) { (a,b) => + a.name == b.name && a.valueBox == b.valueBox + } case _ => false } } diff --git a/persistence/record/src/test/scala/net/liftweb/record/RecordSpec.scala b/persistence/record/src/test/scala/net/liftweb/record/RecordSpec.scala index 470f162ad3..8987a4f791 100644 --- a/persistence/record/src/test/scala/net/liftweb/record/RecordSpec.scala +++ b/persistence/record/src/test/scala/net/liftweb/record/RecordSpec.scala @@ -1,5 +1,5 @@ /* - * Copyright 2010-2011 WorldWide Conferencing, LLC + * 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. @@ -312,7 +312,6 @@ object RecordSpec extends Specification { val fttrFromJson = FieldTypeTestRecord.fromJsonString(json) fttrFromJson.isDefined must_== true fttrFromJson.toList map { r => - r.mandatoryDecimalField.value mustEqual fttr.mandatoryDecimalField.value r mustEqual fttr } } From e6d70f393e32493bfc413ddad79711340903992a Mon Sep 17 00:00:00 2001 From: Torsten Uhlmann Date: Thu, 8 Nov 2012 19:22:11 +0100 Subject: [PATCH 0220/1949] Issue 1353 - Added a null check to make it work with MappedDate* fields. --- web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala index 3a70c691c0..c8df09f688 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala @@ -225,7 +225,7 @@ trait AbstractScreen extends Factory { private lazy val _theFieldId: NonCleanAnyVar[String] = vendAVar(Helpers.nextFuncName) - override def toString = is.toString + override def toString = if (is != null) is.toString else "" def binding: Box[FieldBinding] = Empty From 22e34f12b63b1481702e10884bc921a85c1ed6e2 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Fri, 9 Nov 2012 11:58:58 -0500 Subject: [PATCH 0221/1949] Add RestoringWeakReference to utils. RestoringWeakReference contains a scala.ref.WeakReference that, after it has been nulled out, uses a restorer function to restore the value. This can be used for data that can afford to be evicted by the garbage collector, but will be needed later. One good example is Lift form callbacks, which may need the value of an object, but where you don't necessarily want to be retaining the object indefinitely while someone is on a page in the face of GC contention. --- .../liftweb/util/RestoringWeakReference.scala | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 core/util/src/main/scala/net/liftweb/util/RestoringWeakReference.scala diff --git a/core/util/src/main/scala/net/liftweb/util/RestoringWeakReference.scala b/core/util/src/main/scala/net/liftweb/util/RestoringWeakReference.scala new file mode 100644 index 0000000000..400acc3286 --- /dev/null +++ b/core/util/src/main/scala/net/liftweb/util/RestoringWeakReference.scala @@ -0,0 +1,83 @@ +/* + * Copyright 2006-2011 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 util + +import scala.ref.WeakReference + +/** + * `RestoringWeakReference` contains a `scala.ref.WeakReference` that, + * after it has been nulled out, uses a restorer function to restore the + * value. This can be used for data that can afford to be evicted by + * the garbage collector, but will be needed later. One good example is + * Lift form callbacks, which may need the value of an object, but where + * you don't necessarily want to be retaining the object indefinitely + * while someone is on a page in the face of GC contention. + * + * You can use `RestoringWeakReference` in a couple of basic ways: + * + * {{{ + * val ref = RestoringWeakReference(() => User.find(id)) + * }}} + * In this situation, the `RestoringWeakReference` will immediately call + * the passed function to provide the starting value for the reference, + * and then the same function will be used if the reference is evicted. + * + * {{{ + * val ref = RestoringWeakReference(starter, () => User.find(id)) + * }}} + * Here, starter is an `Option[User]` and `User.find` returns an + * `Option[User]`. The first parameter will be used to initialize the + * weak reference, while the function will be used to restore the value + * if the reference is evicted. + * + * {{{ + * val baseRef = new WeakReference(starter) + * val ref = new RestoringWeakReference(baseRef, () => User.find(id)) + * }}} + * If you already have a `WeakReference` instance, you can instantiate + * `RestoringWeakReference` directly and pass that reference as the + * starting value, as well. + * + * In all these cases, you use `ref.value` to get a hold of the value. + * That function will return the value if it is available, and, if not, + * it will restore the WeakReference and then return the value. + */ +class RestoringWeakReference[T <: AnyRef](private var reference:WeakReference[T], restorer:()=>T) { + def value : T = { + val existing = reference.get + if (! existing.isDefined) { + restoreReference + value + } else { + existing.get + } + } + + private def restoreReference = { + reference = new WeakReference(restorer()) + } +} +object RestoringWeakReference { + def apply[T <: AnyRef](restorer:()=>T) = { + new RestoringWeakReference(new WeakReference(restorer()), restorer) + } + def apply[T <: AnyRef](starter:T, restorer:()=>T) = { + new RestoringWeakReference(new WeakReference(starter), restorer) + } +} + From 491fd6aff122466c60b1d0befd5a6db75c7c6ebc Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Fri, 9 Nov 2012 17:05:30 -0500 Subject: [PATCH 0222/1949] Bump the copyright date to 2012 whoopsies. --- .../main/scala/net/liftweb/util/RestoringWeakReference.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/util/src/main/scala/net/liftweb/util/RestoringWeakReference.scala b/core/util/src/main/scala/net/liftweb/util/RestoringWeakReference.scala index 400acc3286..a2ff9f338f 100644 --- a/core/util/src/main/scala/net/liftweb/util/RestoringWeakReference.scala +++ b/core/util/src/main/scala/net/liftweb/util/RestoringWeakReference.scala @@ -1,5 +1,5 @@ /* - * Copyright 2006-2011 WorldWide Conferencing, LLC + * Copyright 2006-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. From 8803caaa4434232d2b541e7dde696f994b2c49f5 Mon Sep 17 00:00:00 2001 From: Peter Brant Date: Tue, 12 Jun 2012 17:55:20 +0200 Subject: [PATCH 0223/1949] Add Dynamic binding style --- .../scala/net/liftweb/http/CssBoundScreen.scala | 13 ++++++++++++- .../main/scala/net/liftweb/http/LiftScreen.scala | 6 ++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/CssBoundScreen.scala b/web/webkit/src/main/scala/net/liftweb/http/CssBoundScreen.scala index 6c75cebfe1..148ffe04ad 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/CssBoundScreen.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/CssBoundScreen.scala @@ -163,9 +163,20 @@ trait CssBoundScreen extends ScreenWizardRendered with Loggable { } yield traceInline("Binding custom field %s to %s".format(bindingInfo.selector(formName), custom.template), bindingInfo.selector(formName) #> bindField(field)(custom.template)) + def dynamicFields: List[CssBindFunc] = + for { + field <- fields + bindingInfo <- field.binding + dynamic <- Some(bindingInfo.bindingStyle) collect { case d:Dynamic => d } + } yield { + val template = dynamic.func() + traceInline("Binding dynamic field %s to %s".format(bindingInfo.selector(formName), template), + bindingInfo.selector(formName) #> bindField(field)(template)) + } + def bindFields: CssBindFunc = { logger.trace("Binding fields", fields) - List(templateFields, selfFields, defaultFields, customFields).flatten.reduceLeft(_ & _) + List(templateFields, selfFields, defaultFields, customFields, dynamicFields).flatten.reduceLeft(_ & _) } def bindField(f: ScreenFieldInfo): NodeSeq => NodeSeq = { diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala index c8df09f688..78a9be97c3 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala @@ -1672,5 +1672,11 @@ object FieldBinding { */ case class Custom(template: NodeSeq) extends BindingStyle + /** + * Bind the field using the results of a function. The provided function will be called + * every time the field is rendered. + */ + case class Dynamic(func: () => NodeSeq) extends BindingStyle + def apply(fieldName: String) = new FieldBinding(fieldName, Default) } From 91db5e338f20b9e77d02b911a5f8ae168011c657 Mon Sep 17 00:00:00 2001 From: Peter Brant Date: Tue, 12 Jun 2012 20:08:18 +0200 Subject: [PATCH 0224/1949] Drop logging level --- web/webkit/src/main/scala/net/liftweb/http/CssBoundScreen.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/CssBoundScreen.scala b/web/webkit/src/main/scala/net/liftweb/http/CssBoundScreen.scala index 148ffe04ad..72228efa06 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/CssBoundScreen.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/CssBoundScreen.scala @@ -102,7 +102,7 @@ trait CssBoundScreen extends ScreenWizardRendered with Loggable { } protected def setLocalAction(s: String) { - logger.debug("Setting LocalAction (%s) to %s".format( + logger.trace("Setting LocalAction (%s) to %s".format( Integer.toString(System.identityHashCode(LocalAction), 16), s)) LocalAction.set(s) } From 3cb11ddf8d66921ede6f4a6760b12f3d7fc28d2b Mon Sep 17 00:00:00 2001 From: Peter Brant Date: Thu, 19 Jul 2012 16:58:40 +0200 Subject: [PATCH 0225/1949] Pass BaseField to FieldTransform --- .../net/liftweb/http/CssBoundScreen.scala | 2 +- .../scala/net/liftweb/http/LiftScreen.scala | 25 +++++++++---------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/CssBoundScreen.scala b/web/webkit/src/main/scala/net/liftweb/http/CssBoundScreen.scala index 72228efa06..c1b654104d 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/CssBoundScreen.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/CssBoundScreen.scala @@ -232,7 +232,7 @@ trait CssBoundScreen extends ScreenWizardRendered with Loggable { def bindAll() = bindLabel() & bindForm() & bindHelp() & bindErrors() - f.transform map (func => bindAll() andThen func()) openOr (bindAll()) + f.transform map (func => bindAll() andThen func(f.field)) openOr (bindAll()) } def url = S.uri diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala index 78a9be97c3..f2a48cfc2d 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala @@ -229,7 +229,7 @@ trait AbstractScreen extends Factory { def binding: Box[FieldBinding] = Empty - def transform: Box[() => CssSel] = Empty + def transform: Box[BaseField => CssSel] = Empty } protected object currentField extends ThreadGlobal[FieldIdentifier] @@ -267,8 +267,7 @@ trait AbstractScreen extends Factory { */ def make: Field {type ValueType = T} = { val paramFieldId: Box[String] = (stuff.collect { - case FormFieldId(id) => id - }).headOption + case FormFieldId(id) => id }).headOption val confirmInfo = stuff.collect { case NotOnConfirmScreen => false @@ -285,7 +284,7 @@ trait AbstractScreen extends Factory { case Help(ns) => ns }).headOption - val newTransform: Box[() => CssSel] = (stuff.collect { + val newTransform: Box[BaseField => CssSel] = (stuff.collect { case FieldTransform(func) => func }).headOption @@ -404,7 +403,7 @@ trait AbstractScreen extends Factory { protected final case class Help(ns: NodeSeq) extends FilterOrValidate[Nothing] - protected final case class FieldTransform(func: () => CssSel) extends FilterOrValidate[Nothing] + protected final case class FieldTransform(func: BaseField => CssSel) extends FilterOrValidate[Nothing] protected final case class DisplayIf(func: () => Boolean) extends FilterOrValidate[Nothing] @@ -429,7 +428,7 @@ trait AbstractScreen extends Factory { case Help(ns) => ns }).headOption - val newTransform: Box[() => CssSel] = (stuff.collect { + val newTransform: Box[BaseField => CssSel] = (stuff.collect { case FieldTransform(func) => func }).headOption @@ -524,7 +523,7 @@ trait AbstractScreen extends Factory { case Help(ns) => ns }).headOption - val newTransform: Box[() => CssSel] = (stuff.collect { + val newTransform: Box[BaseField => CssSel] = (stuff.collect { case FieldTransform(func) => func }).headOption @@ -722,7 +721,7 @@ trait AbstractScreen extends Factory { case Help(ns) => ns }).headOption - val newTransform: Box[() => CssSel] = (stuff.collect { + val newTransform: Box[BaseField => CssSel] = (stuff.collect { case FieldTransform(func) => func }).headOption @@ -1317,18 +1316,18 @@ trait ScreenWizardRendered { case class ScreenFieldInfo( - field: FieldIdentifier, + field: BaseField, text: NodeSeq, help: Box[NodeSeq], input: Box[NodeSeq], binding: Box[FieldBinding], - transform: Box[() => CssSel]) { - def this(field: FieldIdentifier, text: NodeSeq, help: Box[NodeSeq], input: Box[NodeSeq]) = + transform: Box[BaseField => CssSel]) { + def this(field: BaseField, text: NodeSeq, help: Box[NodeSeq], input: Box[NodeSeq]) = this(field, text, help, input, Empty, Empty) } object ScreenFieldInfo { - def apply(field: FieldIdentifier, text: NodeSeq, help: Box[NodeSeq], input: Box[NodeSeq]) = + def apply(field: BaseField, text: NodeSeq, help: Box[NodeSeq], input: Box[NodeSeq]) = new ScreenFieldInfo(field, text, help, input) } @@ -1525,7 +1524,7 @@ trait LiftScreen extends AbstractScreen with StatefulSnippet with ScreenWizardRe case _ => Empty } - def fieldTransform(field: BaseField): Box[() => CssSel] = + def fieldTransform(field: BaseField): Box[BaseField => CssSel] = field match { case f: Field => f.transform case _ => Empty From 2ccd9aa3c54d7622ebfe06b244c7649e56acdb75 Mon Sep 17 00:00:00 2001 From: Peter Brant Date: Thu, 30 Aug 2012 22:32:39 +0200 Subject: [PATCH 0226/1949] Tweak to FieldTransform - Use NodeSeq => NodeSeq instead of CssSel - Apply all provided FieldTransform --- .../net/liftweb/http/CssBoundScreen.scala | 5 ++- .../scala/net/liftweb/http/LiftScreen.scala | 40 +++++++++---------- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/CssBoundScreen.scala b/web/webkit/src/main/scala/net/liftweb/http/CssBoundScreen.scala index c1b654104d..acf48751e5 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/CssBoundScreen.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/CssBoundScreen.scala @@ -232,7 +232,10 @@ trait CssBoundScreen extends ScreenWizardRendered with Loggable { def bindAll() = bindLabel() & bindForm() & bindHelp() & bindErrors() - f.transform map (func => bindAll() andThen func(f.field)) openOr (bindAll()) + if (f.transforms.isEmpty) + bindAll() + else + (bindAll() :: f.transforms.map(_ apply (f.field))).reduceLeft(_ andThen _) } def url = S.uri diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala index f2a48cfc2d..ee330a90e8 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala @@ -229,7 +229,7 @@ trait AbstractScreen extends Factory { def binding: Box[FieldBinding] = Empty - def transform: Box[BaseField => CssSel] = Empty + def transforms: List[BaseField => NodeSeq => NodeSeq] = Nil } protected object currentField extends ThreadGlobal[FieldIdentifier] @@ -284,9 +284,9 @@ trait AbstractScreen extends Factory { case Help(ns) => ns }).headOption - val newTransform: Box[BaseField => CssSel] = (stuff.collect { + val newTransforms: List[BaseField => NodeSeq => NodeSeq] = stuff.collect({ case FieldTransform(func) => func - }).headOption + }).toList val newShow: Box[() => Boolean] = (stuff.collect { case DisplayIf(func) => func @@ -325,7 +325,7 @@ trait AbstractScreen extends Factory { private lazy val _theFieldId: NonCleanAnyVar[String] = vendAVar(Helpers.nextFuncName) - override def transform = newTransform + override def transforms = newTransforms override def show_? = newShow map (_()) openOr (super.show_?) } @@ -403,7 +403,7 @@ trait AbstractScreen extends Factory { protected final case class Help(ns: NodeSeq) extends FilterOrValidate[Nothing] - protected final case class FieldTransform(func: BaseField => CssSel) extends FilterOrValidate[Nothing] + protected final case class FieldTransform(func: BaseField => NodeSeq => NodeSeq) extends FilterOrValidate[Nothing] protected final case class DisplayIf(func: () => Boolean) extends FilterOrValidate[Nothing] @@ -428,9 +428,9 @@ trait AbstractScreen extends Factory { case Help(ns) => ns }).headOption - val newTransform: Box[BaseField => CssSel] = (stuff.collect { + val newTransforms: List[BaseField => NodeSeq => NodeSeq] = stuff.collect({ case FieldTransform(func) => func - }).headOption + }).toList val newShow: Box[() => Boolean] = (stuff.collect { case DisplayIf(func) => func @@ -497,7 +497,7 @@ trait AbstractScreen extends Factory { override def binding = newBinding or super.binding - override def transform = newTransform or super.transform + override def transforms = super.transforms ++ newTransforms } } @@ -523,9 +523,9 @@ trait AbstractScreen extends Factory { case Help(ns) => ns }).headOption - val newTransform: Box[BaseField => CssSel] = (stuff.collect { + val newTransforms: List[BaseField => NodeSeq => NodeSeq] = stuff.collect({ case FieldTransform(func) => func - }).headOption + }).toList val newShow: Box[() => Boolean] = (stuff.collect { case DisplayIf(func) => func @@ -593,7 +593,7 @@ trait AbstractScreen extends Factory { override def binding = newBinding or super.binding - override def transform = newTransform or super.transform + override def transforms = super.transforms ++ newTransforms } } @@ -721,9 +721,9 @@ trait AbstractScreen extends Factory { case Help(ns) => ns }).headOption - val newTransform: Box[BaseField => CssSel] = (stuff.collect { + val newTransforms: List[BaseField => NodeSeq => NodeSeq] = stuff.collect({ case FieldTransform(func) => func - }).headOption + }).toList val newShow: Box[() => Boolean] = (stuff.collect { case DisplayIf(func) => func @@ -763,7 +763,7 @@ trait AbstractScreen extends Factory { override def toForm: Box[NodeSeq] = theToForm(this) - override def transform = newTransform + override def transforms = newTransforms override def show_? = newShow map (_()) openOr (super.show_?) } @@ -800,7 +800,7 @@ trait AbstractScreen extends Factory { override def toForm: Box[NodeSeq] = theToForm(this) - override def transform = newTransform + override def transforms = newTransforms override def show_? = newShow map (_()) openOr (super.show_?) } @@ -1321,9 +1321,9 @@ case class ScreenFieldInfo( help: Box[NodeSeq], input: Box[NodeSeq], binding: Box[FieldBinding], - transform: Box[BaseField => CssSel]) { + transforms: List[BaseField => NodeSeq => NodeSeq]) { def this(field: BaseField, text: NodeSeq, help: Box[NodeSeq], input: Box[NodeSeq]) = - this(field, text, help, input, Empty, Empty) + this(field, text, help, input, Empty, Nil) } object ScreenFieldInfo { @@ -1524,10 +1524,10 @@ trait LiftScreen extends AbstractScreen with StatefulSnippet with ScreenWizardRe case _ => Empty } - def fieldTransform(field: BaseField): Box[BaseField => CssSel] = + def fieldTransform(field: BaseField): List[BaseField => NodeSeq => NodeSeq] = field match { - case f: Field => f.transform - case _ => Empty + case f: Field => f.transforms + case _ => Nil } renderAll( From ef661c749408272fb50b3b23a66c6b98273f7622 Mon Sep 17 00:00:00 2001 From: Peter Brant Date: Fri, 31 Aug 2012 15:46:13 +0200 Subject: [PATCH 0227/1949] Apply new FormParams to wrapped fields --- .../src/main/scala/net/liftweb/http/LiftScreen.scala | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala index ee330a90e8..67e84c36b8 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala @@ -444,7 +444,8 @@ trait AbstractScreen extends Factory { */ override def onConfirm_? : Boolean = confirmInfo getOrElse super.onConfirm_? - override def toForm: Box[NodeSeq] = underlying.toForm + override def toForm: Box[NodeSeq] = + underlying.toForm.map(ns => SHtml.ElemAttr.applyToAllElems(ns, formElemAttrs)) /** * Give the current state of things, should the this field be shown @@ -546,7 +547,9 @@ trait AbstractScreen extends Factory { */ override def onConfirm_? : Boolean = confirmInfo getOrElse super.onConfirm_? - override def toForm: Box[NodeSeq] = underlying.flatMap(_.toForm) + override def toForm: Box[NodeSeq] = underlying + .flatMap(_.toForm) + .map(ns => SHtml.ElemAttr.applyToAllElems(ns, formElemAttrs)) /** * Give the current state of things, should the this field be shown From f9e68dfb9fab77ee6d1c7cfe4834a6251e376593 Mon Sep 17 00:00:00 2001 From: Peter Brant Date: Fri, 9 Nov 2012 08:45:21 +0100 Subject: [PATCH 0228/1949] Don't use defaultXml for template by default This gives users a better out of the box experience. Override allTemplate to return savedDefaultXml to get the previous behavior. --- .../src/main/scala/net/liftweb/http/CssBoundLiftScreen.scala | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/CssBoundLiftScreen.scala b/web/webkit/src/main/scala/net/liftweb/http/CssBoundLiftScreen.scala index 8943c2199f..8290021124 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/CssBoundLiftScreen.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/CssBoundLiftScreen.scala @@ -53,9 +53,7 @@ trait CssBoundLiftScreen extends LiftScreen with CssBoundScreen { LocalActionRef.get } - override def allTemplate = SavedDefaultXml.get - - protected def defaultAllTemplate = super.allTemplate + protected def savedDefaultXml = SavedDefaultXml.get override protected def doFinish(): JsCmd= { val fMap: Map[String, () => JsCmd] = LocalActions.get From bd5a61a41b57d111260e18262a19412031559ec6 Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Thu, 8 Nov 2012 15:44:23 -0500 Subject: [PATCH 0229/1949] Fixed #1352 - Add FlexMenuBuilder trait to Lift --- .../net/liftweb/sitemap/FlexMenuBuilder.scala | 218 ++++++++++++++++++ .../liftweb/sitemap/FlexMenuBuilderSpec.scala | 98 ++++++++ 2 files changed, 316 insertions(+) create mode 100644 web/webkit/src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala create mode 100644 web/webkit/src/test/scala/net/liftweb/sitemap/FlexMenuBuilderSpec.scala diff --git a/web/webkit/src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala b/web/webkit/src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala new file mode 100644 index 0000000000..106341a03a --- /dev/null +++ b/web/webkit/src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala @@ -0,0 +1,218 @@ +/* + * 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.sitemap + +import net.liftweb.common._ +import net.liftweb.http.{LiftRules, S} +import xml.{Elem, Text, NodeSeq} +import net.liftweb.util.Helpers + + +trait FlexMenuBuilder { + // a hack to use structural typing to get around the private[http] on Loc.buildItem + type StructBuildItem = {def buildItem(kids: List[MenuItem], current: Boolean, path: Boolean): Box[MenuItem]} + + /** + * Override if you want a link to the current page + */ + def linkToSelf = false + + /** + * Should all the menu items be expanded? Defaults to false + */ + def expandAll = false + + /** + * Should any of the menu items be expanded? + */ + protected def expandAny = false + + // This is used to build a MenuItem for a single Loc + protected def buildItemMenu[A](loc: Loc[A], currLoc: Box[Loc[_]], expandAll: Boolean): List[MenuItem] = { + val kids: List[MenuItem] = if (expandAll) loc.buildKidMenuItems(loc.menu.kids) else Nil + loc.asInstanceOf[StructBuildItem].buildItem(kids, currLoc == Full(loc), currLoc == Full(loc)).toList + } + + /** + * Compute the MenuItems to be rendered by looking at the 'item' and 'group' attributes + */ + def toRender: Seq[MenuItem] = { + val res = (S.attr("item"), S.attr("group")) match { + case (Full(item), _) => + for { + sm <- LiftRules.siteMap.toList + req <- S.request.toList + loc <- sm.findLoc(item).toList + item <- buildItemMenu(loc, req.location, expandAll) + } yield item + + case (_, Full(group)) => + for { + sm <- LiftRules.siteMap.toList + loc <- sm.locForGroup(group) + req <- S.request.toList + item <- buildItemMenu(loc, req.location, expandAll) + } yield item + case _ => renderWhat(expandAll) + } + res + + } + + /** + * If a group is specified and the group is empty what to display + */ + protected def emptyGroup: NodeSeq = NodeSeq.Empty + + /** + * If the whole menu hierarchy is empty, what to display + */ + protected def emptyMenu: NodeSeq = Text("No Navigation Defined.") + + /** + * What to display when the placeholder is empty (has no kids) + */ + protected def emptyPlaceholder: NodeSeq = NodeSeq.Empty + + /** + * Take the incoming Elem and add any attributes based on + * path which is true if this Elem is the path to the current page + */ + protected def updateForPath(nodes: Elem, path: Boolean): Elem = nodes + + /** + * Take the incoming Elem and add any attributes based on + * current which is a flag that indicates this is the currently viewed page + */ + protected def updateForCurrent(nodes: Elem, current: Boolean): Elem = nodes + + /** + * By default, create an li for a menu item + */ + protected def buildInnerTag(contents: NodeSeq, path: Boolean, current: Boolean): Elem = + updateForCurrent(updateForPath(
  • {contents}
  • , path), current) + + + /** + * Render a placeholder + */ + protected def renderPlaceholder(item: MenuItem, renderInner: Seq[MenuItem] => NodeSeq): Elem = { + buildInnerTag({item.text}{renderInner(item.kids)}, + item.path, item.current) + } + + /** + * Render a link that's the current link, but the "link to self" flag is set to true + */ + protected def renderSelfLinked(item: MenuItem, renderInner: Seq[MenuItem] => NodeSeq): Elem = + buildInnerTag({renderLink(item.uri, item.text, item.path, + item.current)}{renderInner(item.kids)}, item.path, item.current) + + /** + * Render the currently selected menu item, but with no a link back to self + */ + protected def renderSelfNotLinked(item: MenuItem, renderInner: Seq[MenuItem] => NodeSeq): Elem = + buildInnerTag({renderSelf(item)}{renderInner(item.kids)}, item.path, item.current) + + /** + * Render the currently selected menu item + */ + protected def renderSelf(item: MenuItem): NodeSeq = {item.text} + + /** + * Render a generic link + */ + protected def renderLink(uri: NodeSeq, text: NodeSeq, path: Boolean, current: Boolean): NodeSeq = + {text} + + /** + * Render an item in the current path + */ + protected def renderItemInPath(item: MenuItem, renderInner: Seq[MenuItem] => NodeSeq): Elem = + buildInnerTag({renderLink(item.uri, item.text, item.path, + item.current)}{renderInner(item.kids)}, item.path, item.current) + + /** + * Render a menu item that's neither in the path nor + */ + protected def renderItem(item: MenuItem, renderInner: Seq[MenuItem] => NodeSeq): Elem = + buildInnerTag({renderLink(item.uri, item.text, item.path, + item.current)}{renderInner(item.kids)}, item.path, item.current) + + /** + * Render the outer tag for a group of menu items + */ + protected def renderOuterTag(inner: NodeSeq, top: Boolean): NodeSeq =
      {inner}
    + + /** + * The default set of MenuItems to be rendered + */ + protected def renderWhat(expandAll: Boolean): Seq[MenuItem] = + (if (expandAll) + for { + sm <- LiftRules.siteMap; + req <- S.request + } yield sm.buildMenu(req.location).lines + else S.request.map(_.buildMenu.lines)) openOr Nil + + def render: NodeSeq = { + + val level: Box[Int] = for (lvs <- S.attr("level"); i <- Helpers.asInt(lvs)) yield i + + val toRender: Seq[MenuItem] = this.toRender + + def ifExpandCurrent(f: => NodeSeq): NodeSeq = if (expandAny || expandAll) f else NodeSeq.Empty + def ifExpandAll(f: => NodeSeq): NodeSeq = if (expandAll) f else NodeSeq.Empty + toRender.toList match { + case Nil if S.attr("group").isDefined => emptyGroup + case Nil => emptyMenu + case xs => + def buildANavItem(i: MenuItem): NodeSeq = { + i match { + // Per Loc.PlaceHolder, placeholder implies HideIfNoKids + case m@MenuItem(text, uri, kids, _, _, _) if m.placeholder_? && kids.isEmpty => emptyPlaceholder + case m@MenuItem(text, uri, kids, _, _, _) if m.placeholder_? => renderPlaceholder(m, buildLine _) + case m@MenuItem(text, uri, kids, true, _, _) if linkToSelf => renderSelfLinked(m, k => ifExpandCurrent(buildLine(k))) + case m@MenuItem(text, uri, kids, true, _, _) => renderSelfNotLinked(m, k => ifExpandCurrent(buildLine(k))) + // Not current, but on the path, so we need to expand children to show the current one + case m@MenuItem(text, uri, kids, _, true, _) => renderItemInPath(m, buildLine _) + case m =>renderItem(m, buildLine _) + } + } + + def buildLine(in: Seq[MenuItem]): NodeSeq = buildUlLine(in, false) + + def buildUlLine(in: Seq[MenuItem], top: Boolean): NodeSeq = + if (in.isEmpty) { + NodeSeq.Empty + } else { + renderOuterTag(in.flatMap(buildANavItem), top) + } + + val realMenuItems = level match { + case Full(lvl) if lvl > 0 => + def findKids(cur: Seq[MenuItem], depth: Int): Seq[MenuItem] = if (depth == 0) cur + else findKids(cur.flatMap(mi => mi.kids), depth - 1) + + findKids(xs, lvl) + + case _ => xs + } + buildUlLine(realMenuItems, true) + } + } +} diff --git a/web/webkit/src/test/scala/net/liftweb/sitemap/FlexMenuBuilderSpec.scala b/web/webkit/src/test/scala/net/liftweb/sitemap/FlexMenuBuilderSpec.scala new file mode 100644 index 0000000000..6402868677 --- /dev/null +++ b/web/webkit/src/test/scala/net/liftweb/sitemap/FlexMenuBuilderSpec.scala @@ -0,0 +1,98 @@ +/* + * Copyright 2007-2011 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.sitemap + +import net.liftweb.http.{S, LiftRules} +import net.liftweb.common.{Full, Empty} +import net.liftweb.mockweb.WebSpec +import xml.{Elem, Group, NodeSeq} + +object FlexMenuBuilderSpec extends WebSpec(FlexMenuBuilderSpecBoot.boot _) { + "FlexMenuBuilder Specification".title + + val html1 =
    + + "FlexMenuBuilder" should { + val testUrl = "http://foo.com/help" + val testUrlPath = "http://foo.com/index1" + + "Link to Self" withSFor(testUrl) in { + object MenuBuilder extends FlexMenuBuilder { override def linkToSelf = true} + val linkToSelf = + val actual = MenuBuilder.render + linkToSelf must beEqualToIgnoringSpace(actual) + } + "expandAll" withSFor(testUrl) in { + object MenuBuilder extends FlexMenuBuilder { override def expandAll = true} + val expandAll: NodeSeq = + val actual = MenuBuilder.render + expandAll.toString must_== actual.toString + } + "Add css class to item in the path" withSFor(testUrlPath) in { + object MenuBuilder extends FlexMenuBuilder { + override def updateForPath(nodes: Elem, path: Boolean): Elem = { + if (path){ + nodes % S.mapToAttrs(Map("class" -> "active")) + } else{ + nodes + } + } + } + val itemInPath: NodeSeq = + val actual = MenuBuilder.render + itemInPath.toString must_== actual.toString + } + "Add css class to the current item" withSFor(testUrl) in { + object MenuBuilder extends FlexMenuBuilder { + override def updateForCurrent(nodes: Elem, current: Boolean): Elem = { + if (current){ + nodes % S.mapToAttrs(Map("class" -> "active")) + } else{ + nodes + } + } + } + val itemInPath: NodeSeq = + val actual = MenuBuilder.render + itemInPath.toString must_== actual.toString + } + } + +} + +/** + * This only exists to keep the WebSpecSpec clean. Normally, + * you could just use "() => bootstrap.Boot.boot". + */ +object FlexMenuBuilderSpecBoot { + def boot() { + def siteMap = SiteMap( + Menu.i("Home") / "index", + Menu.i("Help") / "help" submenus ( + Menu.i("Home1") / "index1", + Menu.i("Home2") / "index2" + + ), + Menu.i("Help2") / "help2" submenus ( + Menu.i("Home3") / "index3", + Menu.i("Home4") / "index4" + ) + ) + LiftRules.setSiteMap(siteMap) + } +} + From eca2b0f8500f1fd2afdefb08066b4c51b4e820a2 Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Sat, 10 Nov 2012 01:44:58 -0500 Subject: [PATCH 0230/1949] scalajpa 2.9.1 has a wrong pom checksum on maven central, so use the 2.9.2 for all 2.9.x builds --- build.sbt | 1 - project/Dependencies.scala | 7 ++++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build.sbt b/build.sbt index a2df893f30..4a86ac50e7 100644 --- a/build.sbt +++ b/build.sbt @@ -14,7 +14,6 @@ organizationName in ThisBuild := "WorldWide Conferencing, LLC" crossScalaVersions in ThisBuild := Seq("2.9.2", "2.9.1-1", "2.9.1") - libraryDependencies in ThisBuild ++= Seq(specs2, scalacheck) // Settings for Sonatype compliance diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 62d737b074..da360e8f2c 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -23,7 +23,8 @@ object Dependencies { type ModuleMap = String => ModuleID - lazy val CVMapping2911 = crossMapped("2.9.2" -> "2.9.1", "2.9.1-1" -> "2.9.1") + lazy val CVMapping2911 = crossMapped("2.9.1-1" -> "2.9.1") + lazy val CVMapping29 = crossMapped("2.9.1-1" -> "2.9.2", "2.9.1" -> "2.9.2") lazy val CVMappingAll = crossMapped("2.9.2" -> "2.9.1", "2.9.1-1" -> "2.9.1") lazy val slf4jVersion = "1.6.4" @@ -36,13 +37,13 @@ object Dependencies { lazy val commons_codec = "commons-codec" % "commons-codec" % "1.6" lazy val commons_fileupload = "commons-fileupload" % "commons-fileupload" % "1.2.2" lazy val commons_httpclient = "commons-httpclient" % "commons-httpclient" % "3.1" - lazy val dispatch_http = "net.databinder" % "dispatch-http" % "0.7.8" cross CVMapping2911 + lazy val dispatch_http = "net.databinder" % "dispatch-http" % "0.7.8" cross CVMappingAll lazy val javamail = "javax.mail" % "mail" % "1.4.4" lazy val joda_time = "joda-time" % "joda-time" % "1.6.2" // TODO: 2.1 lazy val htmlparser = "nu.validator.htmlparser" % "htmlparser" % "1.4" lazy val mongo_java_driver = "org.mongodb" % "mongo-java-driver" % "2.7.3" lazy val paranamer = "com.thoughtworks.paranamer" % "paranamer" % "2.4.1" - lazy val scalajpa = "org.scala-libs" % "scalajpa" % "1.4" cross CVMappingAll + lazy val scalajpa = "org.scala-libs" % "scalajpa" % "1.4" cross CVMapping29 lazy val scalap: ModuleMap = "org.scala-lang" % "scalap" % _ lazy val scala_compiler: ModuleMap = "org.scala-lang" % "scala-compiler" % _ lazy val scalaz_core: ModuleMap = sv => scalazGroup(sv) % "scalaz-core" % scalazVersion(sv) cross CVMappingAll From 1f3de5a0b87406915ecf32aa572dcfc20798e04e Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 12 Nov 2012 13:11:22 -0500 Subject: [PATCH 0231/1949] RestoringWeakReference.value becomes .apply. This is in line with how WeakReference works in scala.ref. Also fixed indentation in RestoringWeakReference.scala. Not sure what was up there. --- .../liftweb/util/RestoringWeakReference.scala | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/RestoringWeakReference.scala b/core/util/src/main/scala/net/liftweb/util/RestoringWeakReference.scala index a2ff9f338f..5b8a95265a 100644 --- a/core/util/src/main/scala/net/liftweb/util/RestoringWeakReference.scala +++ b/core/util/src/main/scala/net/liftweb/util/RestoringWeakReference.scala @@ -58,18 +58,18 @@ import scala.ref.WeakReference * it will restore the WeakReference and then return the value. */ class RestoringWeakReference[T <: AnyRef](private var reference:WeakReference[T], restorer:()=>T) { - def value : T = { - val existing = reference.get - if (! existing.isDefined) { - restoreReference - value - } else { - existing.get - } + def apply() : T = { + val existing = reference.get + if (! existing.isDefined) { + restoreReference + apply() + } else { + existing.get + } } private def restoreReference = { - reference = new WeakReference(restorer()) + reference = new WeakReference(restorer()) } } object RestoringWeakReference { From 23ed2a6d42a61deafad22132273b05bd9e67b494 Mon Sep 17 00:00:00 2001 From: David Pollak Date: Mon, 12 Nov 2012 14:03:39 -0800 Subject: [PATCH 0232/1949] Added contribution policy --- contributors.md | 56 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 contributors.md diff --git a/contributors.md b/contributors.md new file mode 100644 index 0000000000..d8665ac509 --- /dev/null +++ b/contributors.md @@ -0,0 +1,56 @@ +# Contributions to Lift by non-committers + +From the beginning of the Lift project, Lift has had a very well +defined and restrictive Intellectual Property (IP) policy. All +code in the various Lift repositories was created exclusively +by committers who signed an IP assignment agreement (we adopted +the Plone IP assignment.) All Lift code was created exclusively +by the committers and the copyright in such code was assigned to +an entity that holds the Lift copyrights. + +The reason for the IP assignment and the rigor was that, 6 years ago, +the way the law treated open source was not as clear. It was also +very important to demonstrate to corporate adopters of Lift that the +use of Lift was free of any potential litigation. + +Times have changed: + +* The GitHub model of pull requests is +prevalent and there have not been legal challenges that I'm aware of. +* Lift is a well accepted, well regarded framework and has not have + any material acceptance issues related to Lift's code provenance. + +So, as of November 12, 2012, the Lift committers have voted 21-0 +to adopt a new contribution acceptance policy. + +We will accept pull requests into the [Lift codebase](https://github.com/lift) +if the pull requests meet the following criteria: + +* One or more of the following: + * Documentation including ScalaDoc comments in code + * Example code + * Small changes, enhancements, or bug fixes to Lift's code +* Each pull request must include a signature at the bottom of the + `/contributors.md` file. + +I look forward to seeing how Lift will continue to grow with the new +contribution policy. + +#### David Pollak #### + + +
    + +By submitting this pull request which includes my name and email address +(the email address may be in a non-robot readable format), I agree that the +entirety of the contribution is my own original work, that there are no prior +claims on this work including, but not limited to, any agreements I may have with +my employer or other contracts, and that I license this work under +an [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0.html) license. + + +### Name: ### + +### Email: ### + + From 1b7c3285293a3d3dde25c118df3e0ab23fb297d1 Mon Sep 17 00:00:00 2001 From: David Pollak Date: Mon, 12 Nov 2012 14:28:02 -0800 Subject: [PATCH 0233/1949] Updated the README --- README.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index cd00f2d60a..0865753fb8 100644 --- a/README.md +++ b/README.md @@ -11,12 +11,17 @@ Lift applications are: Because Lift applications are written in [Scala](http://www.scala-lang.org), an elegant JVM language, you can still use your favorite Java libraries and deploy to your favorite Servlet Container and app server. Use the code you've already written and deploy to the container you've already configured! -## No Pull Requests +## Pull Requests -You must be a committer with signed committer agreement to submit patches. We do not accept pull requests from non-committers. - -Please discuss issues and improvements on the [mailing list](http://groups.google.com/forum/#!forum/liftweb), and read up on [other ways you can contribute](https://www.assembla.com/spaces/liftweb/wiki/Contributing). +We will accept pull requests into the [Lift codebase](https://github.com/lift) +if the pull requests meet the following criteria: +* One or more of the following: + * Documentation including ScalaDoc comments in code + * Example code + * Small changes, enhancements, or bug fixes to Lift's code +* Each pull request must include a signature at the bottom of the + `/contributors.md` file. ## Getting Started From 440e1be3697940bf46bcb19ec0579bbaacfcfa2f Mon Sep 17 00:00:00 2001 From: Marko Elezovic Date: Tue, 13 Nov 2012 02:14:39 +0100 Subject: [PATCH 0234/1949] Add prompt expression to JsCommands --- contributors.md | 4 ++-- .../src/main/scala/net/liftweb/http/js/JsCommands.scala | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/contributors.md b/contributors.md index d8665ac509..8f21e03b90 100644 --- a/contributors.md +++ b/contributors.md @@ -50,7 +50,7 @@ an [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0.html) license. ### Name: ### +Marko Elezović ### Email: ### - - +marko at element dot hr diff --git a/web/webkit/src/main/scala/net/liftweb/http/js/JsCommands.scala b/web/webkit/src/main/scala/net/liftweb/http/js/JsCommands.scala index 58fffd0929..924d26945b 100755 --- a/web/webkit/src/main/scala/net/liftweb/http/js/JsCommands.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/js/JsCommands.scala @@ -822,6 +822,10 @@ object JsCmds { def toJsCmd = "alert(" + text.encJs + ");" } + case class Prompt(text: String, default: String = "") extends JsExp { + def toJsCmd = "prompt(" + text.encJs + "," + default.encJs + ")" + } + case class Confirm(text: String, yes: JsCmd) extends JsCmd { def toJsCmd = "if (confirm(" + text.encJs + ")) {" + yes.toJsCmd + "}" } From cf0615fb3db904b756ee2a1b2e3c02778d4fcb13 Mon Sep 17 00:00:00 2001 From: Marko Elezovic Date: Tue, 13 Nov 2012 04:25:52 +0100 Subject: [PATCH 0235/1949] Losslessly compressed Lift .png images. --- artwork/LiftLogo/liftlogo.png | Bin 4933 -> 4472 bytes artwork/LiftLogo/liftweb-logo-plain-big.png | Bin 15857 -> 14086 bytes artwork/LiftLogo/liftweb-logo-single-big.png | Bin 61309 -> 53416 bytes .../LiftLogo/liftweb-logo-single-small.png | Bin 2031 -> 1741 bytes contributors.md | 4 ++-- core/json/json.png | Bin 59158 -> 20780 bytes 6 files changed, 2 insertions(+), 2 deletions(-) diff --git a/artwork/LiftLogo/liftlogo.png b/artwork/LiftLogo/liftlogo.png index 0a81037b675119a2947c1789d3a3b1bba5649eb5..ed242ead99e1c7276aff29f31098892fa96f3536 100644 GIT binary patch literal 4472 zcmV-;5r^)HP)?WfxySMEv(L#4gh>QsCJFNe5)hmPfe->9AjN_p0n3yGp{;FI>&4FYTCEajwL-X3 zm%X;7Y7#~fZbd{S2xW#qfPl;h1TvnppL=fB>Uq&+CBr%UoSf{>T3O3?zWB0}fA;>J zY{4gNix)38K0Ptu9s`CeG6>P0Kyx4pxS~)Xuno~auUj$ZQ!FIZi7n-cHSKVjvk@Lw z=s-27@^?dwd26#r9SGvcw&a<)LqwSmJPbIgxiS@beEo{lcY`Rh?ZNr)Hi|5bnJQ?8 zLT~EMtp?-PFCU#3;K;V&Gjie$WWES#KrFr_d{t?vPaLgD6%beN^UbVf>{c4fv#Aiw9;MCe$!hMF5?GIs@O)&$m{1ebiP}e3PfYA zsBmXkD)5$lCHq*??44mPUFXM(Y6fs00i>{EefH>C_Lc0rtG2a@&en=^a}nAQ1O$3e zcXs3vX}NbBLZ+!Y0SHG3CT;AgU$}OV{S1z z3#A1Xu%v|@S!KkGHNy@9X}Nz^w^=CgtitVlg9iuQ!IZJ_{Q8H3X%-tnLl&2qksP0t`CPZeC;ubpB9EB1 zK4xTE-kg-Q+`Wiw1|F%-86iB(V8+C50Celzl;1vg51raIroj^F0{qkzb5C4i=9+}0 zjP8Mq?48mi&&Ykna8{lH{e4ZhQez(KM5`82{FCRudHI>U8PMl88afeD6v>ZI%9vth zWbc$Fc~)-f$eFoo9U_NBSXi5_RO6U19!-toUqwYadEv={jD4sRHljh^iW{(CoHw$+ zr!=1F>i4#v9B@QTa#2Rs<^TEZG4V9=g#J2)A8`nG6B5ADdUqz>uGPZkX%Cdx?z*9bQ+0XY3KrBUBcY0@Tk zBg|<~9f`ee<2TRT&Ei*ga_&+I4V5SpyYtG{RSg48Ei4+qdW2I1NanYDG?&wWh!6(5*lpED{yzWa`85_*##y&8iM$o`aeF zfCBI7Ip%k!(29eGq*-XJk$BT6$Y)I2tV3CUgX zrb)lHMs2Rk;%$lhnWCi))G1dB}@-;Kx!2UZ;HGN$)t_M<&93~4Bly<@p>({26* zXpMEeH!()s+YT#@AJLiTGm@�SzW{aZL0wfk6botV!Jia#aoLcYD<%OV6XrP>$?f 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 KMNUMnLSTXhC#)d= literal 4933 zcmV-L6T0k)P)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01ejw01ejxLMWSf00007bV*G`2iOQ4 z6AL$8FZSsG020MXL_t(|+U=crTvXTD$A8bA8Fs{dC8Dec&+St4;$=l>@`gv=jph-*=YnqR(&1;P;ibk=GaX~?i;)((yE+8&|Y%_D8_YYIE z)P?2VJ9mcr`w!;MIp?15oO7OY?(d#M&?JdcQc{EiC4plEAeccTK|BaRX8?`>ZZc3r zzfdd|8jSsG*uD>y2{M*zhW zAsT?2!~|{`1@pk&pH9CnSNZiC!bb#BWr1NNA_+h+06~_F7@Z7e-CF>bYAsnIMa<3Y zB-$f}?^U{&LfO_E%}r12-Z-;LYsoSyJSuCLS>SjxV;%q}g|m0G(;FfHY}6o2tbo`f zhOVaTGl)4+1dk=QTvaT;W4TWwD!j^(CnOF`(RhB6T_Pz$^y(B$aM(E=e!WS$AY zPGu2EFAZ`l6&bnNvBuSDF77Q{i~y^)sID61mbTi(+xqLwx}`PX7%;k6L6P+uwHdB!M^)W=vtQ08O?5Ic#2b2N=YcSjb8Q!&k#R~6B*)s0In(?Z#Bp* z45V~5u8z|HN?;ZzlD2ARJCLWwW^^^0gvGVYap2}Xx7B1Nki!>c1&YjxMkD3`Y_Ijm zHUc>;DchcF#YhGcMdpzjCL4krwm92eG>c17D=r4m<6(m>5iozcFaDN&0ZmP2jiNO` z4voqf526G%i_a0De_WS5eqHuSsw22{r?K4v^d6BILPS9V3){{Xto-(Y zjlxoeo4g>$SV898WjoyLD(-j$$Bj#pyGC5$;!BWJL1Xh|Cr~1If+3>*61c4AY&mVnz zyWqpm3t<-9^<+y&%zyD7nKZEIQ0iq_Acri<7z$8=nK6%88Y@lDZcdmo(c|$ytO*|I z)!hm2t|`FXdQ*D_?E*9Y7r-3JApg)x1IAdXOB_AR4+eenMwb!(-SDU7qmi=m2(I0z zYrkVOW0v3W#NKU&91^pmGuV_s#%oIf^p>Nre*HUQY*5RR_UPk^_um|Yw^NVc^o3jP zc8YA)VRow_ht13Gzy|Z15KZv_oaHQO%nJiRt(fd}>|lpKygmvm)*r_|4_#@mt$A## zqU+G8%$aNu&w%KcaswVUtOthpwcglaXAtnK*x{Hv)u&ZI6dTac7PaK@QMnHGbaw@S zI7LE4fv_lYfE4pbESTX7_g>EUApKi38O8S0mb*8*C5JD}@^zrQd880v00@1iFP`Y- zB3XJ9M)koTULS>y9qigwl-(@IVKLdmMIG-0Fhr@6IXdVu?>QgI(;ev53Gcr-1|EG} z+YRN%LJo<^c#>J%4M5R#!{V!WnAB_?I-89JI(ODx&q+MWiQnFB; z-Rv)+9KAxB1Tb_6KR zB2#t(Vj>4xyh1v;+2fDD8il6=d)X%BY8R*9fY9F(1sOlG4}AK$siIQs>~vTjHv+RC zU{J7X$deXk^d;aI7O7N&fT&r1s;U?wAZnH$UW*wDywttOxFGLdNx2Y>AnotFr-frl_-9tZy`7&;D|3+g)PS{%$=0IMz8u zGr=Mu<%?5&;Ot~jRi%_)yNk@+QtaASW)l`7JpeFap(QeJu5U{TF*EYMtwhG|5)>3x zf^EnLsGqbjqc4B}OJv>>-zAa4_hP+^gAMCAokza%S7+J!%rAcAPmhx?BQV8;} zL0wzq+d5l(8~gIFX=EgntRB>RzOBbEd>Bn4{(0yMGWL|>T=8v0Oz^9w$;$+ba$Ty*|t0DU3=HO zP^&?>L4d)twJDUk5SNL9dB(Q&(`%$i%{wr0 zJbGbPTChQ%UIG&m3wWKVnjIQft=&rkTbby1sPL}chCptwe#B5Iw zKe$8UgaZO%4b@Dmzgq-pu5PKaCEt>1U+SLSoa79yu7Z)!;Nd)U`-bp?a%54%_P<1n zH=*Z?dbL8XZ!}3hj{5fNh?2`SlDAxdRyDxs&wDpaknQbzpTLA*X1v{s$l$bU7elUI zuY+e_Y0u~O_vwV4`y`2h%mBV*LR!xHh`mxRZannI8MMrkMbjn#EuJDU@jlf;zI?4# za>zscy0kTkN+Nt>H1Wq@ZJc^ZiF)w&k9TPlOp#IZ-cExA(qGn#sJcYR%V@<3QaF2Y~8r-mzbkcDSJtecT_*#rz}WuS2EY+hQiY9XJ!cw3fq$35A* zxsV$H{GA0(%~>}!PwD&je`;B0W0PqoQ(F#Z6V?MNen3%p{uY=SB=K_Ogi*b*am!hl zAHLn=WoCRRknqX&^zcec+`I44Wqq2OOxXZ_)`nzm+clWdRSmhiwh1MdYta7*X~*Sm zogFcLRB!CwU;fjNa=}Pz=;yp`Z^{IVZ5`+{B4G-XDIGx9HqPljEi7D9t7^yqaN_Ju z$sq&4^I=}tbD$i|sAb?Y7O-aLx+y0ud9U8yN%rJM?*W|L!o`jU4mD=zQtx-#YKDC5 zOqDF@_Uq${muC(pWMV7_@i+;SEvZA(?|=9YRYPtviun5QRfLY~D@XD@ z0lWhsNztg;Ki;L$LAQW_cmRVT@nddgBws05TW2+p0buuma=9P_Kms%GB%%zt?_;kKRT;E{A)?LL|@SLxl*-ZQnbR-egH}^{c0kMV}a+t-;@es{p z72c%(HXwu+6|6k;sEe$|lAE!+wA5%4<=!nl0>E}=e(VZNzo7V@-T_I!t%iLmGj9Sg zR^ckS!GwwD^B)cP-;mXOLIeZI0b#m-*ZrObz3`p_d5#0{FJ^w=i)3Cw$)kNnByKW@ zx=RGS1)!G&>gA+B!qY`ZQjaz(x(toZybbw6`qt_W2pa_xrDbo7IQM-J#ts0E1MpR( z3_uhSZFPTYnWKZzIG30b01UTm)zC*q*Xm^t3$h+OCKIlMRRj4H0crJ)b)P-xy;xCe;helbSnAR>Afm%RCls)-CPAD@qEFmEiL|8ghpp>0WiaYg>SKy zhM7MDFkh(xR@c-c?UU{J+m`*PZ&3WW!Mh-=VI6*7bSSk|`)^WcbXGFJD=h%G)m9p2 z?gro_fbL2bxawvN(m&6|7uycv<}G-x5Bl)v34E zE?HufebV*AKSH9i<`VHb01mB4%VJgU3nMQm^fS4WGM#Vm>_I97c1Y*G1&3#W5e$i1y%t07y1{QcuynjCfWz(@oz7yt$j zd;*`YibLm4E{d1uPIaAlDF1xkrP9h}FGmL-R7*WsC1lTG$v!$g$Ab}#2dumI_vnuG zY4PaOtFt2IIe+om-Qy=pHeI-0_KROHdEt)Q>dT5Ce{ZD$5d64VK#%TjShp$;zTUlM zDNR$83Hiq^6rV1-`cBNe3G38cbE=2@y_JTS0`O|dcb%N<@jt(vi?B)3JvgeoQEkpE zD9SCneq&kO!jO~J&^%NN*>`x-P*Fev0rLQ~+Db!2czNNoSe7saI-&JT04GnE-7Y*` zwywUq>CNcq308H~yQ&}u1jHKdu)_=@PK3lxi=Yu+crRr>+gRR{8at z$q9=>%3$TPC}h9D#4+MMx5X>~itWMl^r!(?{@P4V&U1#1;v*tUtjXlq+Hmb zWP|J*ki1zW7D>cifZPb&iwQ#^{gbZN>Nqi0*HYu>SMt}^!G|gXpn#0UFQ-g&I0#T55^`YarOBE>8&&xIWVCXfKrcxN`S`(Vh|VSv7364Q!~bj<`T``otuwPXM~o%z4bW??>Bxn1C+0N@>%IE|P#p>ZDt4EcVm4oDb5 zB+OyP={BHb({2m*>A(%sz+0!uEXG)O2&2uL?bcS|i@0!w$_ z2fn}i=KcZqg&%xo&YYSvXXeC)y;YXO!=}JSK|#S&kcX=Dl20%M}!Cm%SspvB|%mY3(TU4R8`hIgMw!pE;_Y`&7>dZ=eN(e zU1MPKLxDZ@sG&8XSM?61lFv$YAlA5lvCMH-(F*Z2(5(=fQRRq5AO1To-0Q|ocv_vm z5Pc^f5#grqffJ^|-+|J2?r5|WRXoZjZugFXypYdBqT=fnocUhZ0WkTyTa~pUo2fRl>P}D42xDzY-%Rm8t&SL|B{NsFOokr?{am@c{<6gY(5N}7abFIj#=#zFxkJn!@na1RI#9|;{ii_tn|KwyVAJ$w2ei)I2;)~KPI14!JmWZ9DDsAUv>3| z2z&zZGWi52&GEV@?dg@h&j!Q(4P;KGAVPf5ykXM(E!|*z0;OlOURX&-I>2PN0_jbq zmdIC^FoO?h#od~>-Q#6G7y*L76qYk*(y$=ybLZ<%-^5fGeK*D(eC(M107A^&_UMk4 zE+w1lVo|;UB|BUsaQny-GXfGE*P2W9LG=^s9mt0R&DjW~qmysz#bk_yf+e7yIy{>- zT*nXzaoPyKI1dApzgc56+f5ltIbfLlf+R7;yRS}7n39lPs%Zv?jq0(^liCoaEMRUZ zg1P!6iNN(N0OQRxg+h(TZ3VZ_0<#Vah>pkme1EZ6M$Xxd04IQ2qNR#>rQ@g3r8E89 zy-qIu^HOaqkZ5fs&fP{`3jgMQ-2U2&$VI*6`{i*^Cxe7Pv%P`g0Hw~hqD#@>7!V3JaN@%!aT5Z=b380eo=F)_ivmpdb3@l!9t z)QO?TYkR&1bPZ_Qek{sPTalowbkuQMEP&d8SV-1c#|znX;w}}YnF=}D>}k2jM1Ktq z_q>K6*$)8WT9Ok*YXSwSws>LYrz5TM9)^Rc(4=2jP~NNOgSmiN9YeC6W$ssUqDb7ou$9se=e{du%&as<%xsxgC!u~ z0WsUpI4#eF=oU47vL)}oLkQD+o&dI74rkmhu`gm2P=!3;VW*S|yj?tjVG#yP@Lrj3 z4`E=Zm*R}j0+Y`5=6{+F{Mq8m;lpX>gb$0?=Sg9NR1YkUc#rCrq7S6Qzz;K$`ayl^ z5qNe=ys+n>?CGw`D2ijgyy2{8(f@;YV0UFyW@EO5K0zBnt_Vut$I?A;5Q@?zpbY(Q zQFs#GN1PyBJIQ+_baR+WB7Y>{zsVQf6#E*FgVMPO=&=EMvFaSj?)G4{q%^j9TJAy** z^%M@1qD}F6@}Q094}hu<4CnmE;NL4=(}5YsrfXArLWT$SaX3Yi=2F-8eIo^TURK4hW<+m^hD>65fF z@w)_d&OtcN(WcvLE7wR^MGwvR=Z}WD>;qzPbjqSXNj_xwfm3-d)?`HXL*e;K0xqxH zk?TK=JQoyFy8CD@U!AKV=DED<)_9Yyr>)$IqujB8?c~BTm&CS+AvWtzXCBT~iYL5_k@#tI^;FvB z?wfV1(yQOu_)4juPXga)WAPR63iE9p@$a$>w3vZXe2{|re@sL%lsy+Bn^DcaTY^&d z)H26ZYzL~Q%!WSsdRcwI@16aU&O}$-;`c>eR|_HLH}$=XMqDHV0vSdcP0-veyn8pZ zVaq2(RprNtoXqf?=B|3bg0S8_Z;efJcUCM2h9TF^+Z6x)@~oUQkS0(X0|UomqoqgG z3pYt%B*-3rcXAmiwWa7Q;_5irIIXL7;J?kelMo3bpOAtg<#CeUySUifP}~bAimBXi zSscevKG)Vm);PxAo0w54ZrT~m6?AmD?y&N$t$PqV|0$i?(80p$8o^cDyqsQq{GWS1 z;elZUwiQJ~bG2O%IPdtyx4AfR;q5EA-3}dossWBSTdF$H_MtwOLhrS2IVIDtf~CGq zcV#>x9z?2gZLZO-N-sKogIM(T*W}*azDvX}PGzE@tDdPRoPa_j+xq(0^vpB;bdH4Z z159ZOZ_gJ#HXGldb#+RcTHj?jD=E$(XOk=wL^A*FYDZ`FLF!d)psQSY*TKJ<<9U2gPgyhQXBB+oZ3Q`g1)kZsM9o-XujXWuPwsdDcSzQY+1 zI~A^dRlk!W{pK;6BK7-`-!jtFp3B64hNl(i4AW+8B_n3TmqK4T%K(BggPVLRDR{y0twg(7|e#r`)8gFfq`AQ=8U@z6cCinq~;f9Y51p)k?uq*sc-#^&>>H|Q27J@ zz!ccI*?8+0;G%A8vj4oUvO85R7$ka~!IEHmnLe6AL%JRh_$fZZSa%U!4@!v`i?h4C zvMVbCldv|G>brlKwTs6Mt1LO2|GJgE_P0~v%F$bP>9C|RJlYJF(B=-K>wKbmtTSul zJXhG5rX$-@wfI)A%3f*@BDio`gwO$bUO~`$5R}!QzK7dEOn?cI`}3N#=xTc_+d&^^4I)B}xL~>&B~3WLi!naDSds zEi1Xnhnn`-1by}5T@{DH%H1Te`)GJy7J9X|XNK(Bji2EA1Cr?SpuOjomtE4PGo0t4 zl}Q)TmzuXF>6_<0FLNcfbPW}FU0_SVXh(b^U`qo9A*ShRAEm2t{Z!FEpx;PO zK|q)+mk<04F`Tbmty*Dxge5dFQXvF1)W>832JZgiR>Mzz_E0UaKF(2fHHGsbgmPb= zYS1SPG)^Xak|Y(YG{90u|&iOiR#q@}JE>zOG8TyvO{@NboCyBKDhYGf~Er zaJwETMU^dg+tdxln5Qe^k#IsjH~SU`xHAgMl2D%>;tt1r%%NIFHQOCpR{)bGX81@P z7H3mGRpPNbpO~t71A%# z@vR5vswv|sdr4KX3ie!w2`GJErwrqlAIjnTNp{_0O@T#G zPUW>Lm~xKGBkaIq#`Qe8<#S&@l6vd&r>T+~0fttWuCr!a8#msnbepaY%4lfd0a#6m`hH=iv+H4h2qqZSa3 zxa=wXv}yoBzJC`?Avcfn<=TPCQ>$s1m+ek3s*M|j2MU3GNQnAdsx)U+D_4L7D~@$e zBRtwvz3;jkT)@t3@RYG0CMeUIs4f`Ie48o6Y;$VSuaH~??8IhY!Q=kbC#JoG zfFOZkU1YvbStL1nR;dha6d1Y=hIZ$o6X>udQJPUADVS#{d7C}n`+yOt%T>GU4&G@Fii*yn1N8GFjnIgM3dzy!=#3- z7IQg5PI5WITw#?jVT8<*lDlP4N5;kkQv_;m<)Kc-b?6jEuOV9vJTpqNYWDc$VDfzT5Gr2i`Io{TzgUGi3|d%*Ek$l+xwt++E3s#MrNZ zt-aD$%LCiB@eu<%5}N7L(@xIVAZA7WzB1xJ}Y*7bE#I9w@t6fqgY z4TW%R2yDf4_Nn^-D*QDS<9$-Tg-83>E}u3B*v#fT^|DI)CoZxao0;K&3U{CQRD_%9 zX~tad>>4wuVrlT353~4xiUUmbx39F!;oMyhC#PZ0;fb+`Dpp`iT_CXwK(oN>wr5uS z<-BH`_fs%=HrU^dF&l6cUmkA^xZNG>`Y9Z;e}fSP`_un)Q7ch)gRO19GEwp|aX7rW zE7(JUvBtVj1TCjkmU31qE0+{f84idsHDCL%R|92e5uJem4jb@@f5Sh1r&|7X!7fii zM~AvzJI=ZT&Lbj>MKOow-h(~~?1ZMc!epS3ZpKjnKX6*4ErEgf1-x8Vz>{yOuK*re zfIsye10B)Z8$b{Df#t&jz-S|2Qh;@#XE)w23~+^|665_p)3m^;1LD2QbfQfvLjWIu ztl3OETk<~cdteaQ>ig*T0dIGJN9^1KeE;I8`QHCUKeH=kd>{G!hw6dJ0A8g7s^v2? zzaDhI>m&*~fO^_Qy-x2AIPvRyp3OC)U)LiGHJiWqJ&xwqtfOG^X;j%%%VYJGbnjni zHD!doeC>Wa(NV-%%zy@jrhuyIj|M~8Ja&VF?;n7>&;USnGZ0-MkhAd9>u*ndty$%W zUa16dU3*+qHx0>XHpe42eX;tTzxi8{h$Iylkn!IAx8YkW+x%1#C?RDp#+G2_L2aB zl9o9I2VfVvYkk~2Lj^7>_aaJs2Ay6${cSw$`G4_T8tNP_?L|%|G7O=Hqde>jWys*O zJq{JI>zl=LP6uY}|9IP~Te5c@*=E;niWK8%Xlju#0G{sN`tD8T-XQ*e4JUH*gj+Og4(-ww0_-mn5(M1; z(NKCo`4nVFtOZEa-3ufo`XRsX0ViA29+2SbGS&g#g#3O{-S?zc1E>slhrstJdcV_U4F^yS3v&zubi5~dr?K5&Z^SC*fO&&nGW|p&? zR+28Z5NypxNVrmIvEr%17~jt7B2yX#g{E;egy6<=*=2#P(FqB=s8fY@$KASlZEZ*b zi-`%RW4>V7^$fSHv4FJc>6Tq5+ZWDsq%e4j8l_5Q=;*2h*o3c1$opo2XC$BiYW!rb zx;+dYRUlyhmM&E)NJlqF^CNOwH~_cu?phQd^!ulb!)OQ$R?^+OZpovGR%Z#C6C9bLU8Q^YVA0N4xzjSs|c3{R=YcQ zMEhe-Rd7y(6g6#UD|w=@7=|o^ zppWW*X>XzR245CY1U3-t1w?li3T$ zq&m(L81rgXX9Y53S>$+*W0bi`u^tshM<%uUjZR3Amf|RgZlm#}6zS+m#tnN737Ht*%5R%nt4NJxPr>ZL!=!oIxV)zE#66BQdj1AdG4RW_jVP zN6nnm1GxX|RAl)F4w|8xa4N>r=vur~o?nxx-5Si#6ThJ1QIIRH6JUNj?P`#A?_P$Tn8s~td#g46fYvJzCzxWUI zvo7rw>bG*VvRj2yc?Y{V=sYR!G(x;9>mnJlra4ZgzblCHDZZwpnK9R~DEw)3HD5MI z2yW}8K|h6vCNx|h?)UmfGP5-!x|@4$ZQPcc!8!Cqq-Ulib7M=+E` zFLUScEJ0yL{iVZl>%TbD-@0rwOIF4-9%8=2f{9}yWjwXC5uVeS{gF^C(tfp z2uPYE4?|86uY13OUwspMjUm;L6Y-q>TY# z9T1!3CH7h90Ge z)wc0^um3Y_jv@{S5f*o^Q{TzZk_U~Omefp7vz33W$PlVJ8)AlU>tGVbxQ611a#id< z`bNGvXf1VEIr2}3u={fKH4s7#H7y9j^^`^G7)wal={t(tR^S@RWP&GWc|H>9>Yx{k zkM#L@{A;6@nnBPI@2bUTO^VU~YP?t*HG~Pywzs@G=EpjZ$rG>%--1FYZBYF%PZ?f% z^XC1V|3FWPmMpqw)uaf7sl?Hi3RKusnTAh~g)EzscM8RB>#mX=Zq&X_#Z7w1)8GV> z`s?(N@uj?h5ks(B`L8(zFTA{t`j2q(-{3DKO(#Lg#Llm4nw(I7#0WhJ$U^6$##lCv z9u0@?lcj>ekjt<7?B7kkcqdj&=Q-?9joF-{PLb#)SDuY?HM(sr1u>;H=)Gx8Tm zUQ@wYxZ2?-Y^ICha$fJ(8_2@sU8&DqV6mxL3D6pk!Cus&rxY~l|5+0A+d16)*QD3* zk`zyjFahenaEWv0_@T$SR=zl{c3-7kcr@{EkKF$h>fRv7{CcO}Mfp1mWS6=~|JHNr z8^&shT-KtWe}@&f+Nh#&(9H0ZOf-77`lA_558i3v%+1Zn!>!&McdCQ!74kUs@!v39 zlFzQfce&cqhYAOiJsTjwlV$J3V?hV;PL*W2&CXu)ezEYiHPZPl+qM9IC6fm3jkzn` z)l8szNb&_j<9kYSr%3Utf4Rc8*wy&-V7=(YU016eIx~DT9fL6DZz$e$$yU}rj(#;N zYI6U@qq{edm~6PlZ$VFUL`vGFL)9N{UEVX_sJ}MmA5Kn((urlybYwopxaweYhGDj1 zVlc-YTsPLr&@&w^0GEuZ4IgL}h^W+6EITrpNX({?Ig~MeCmCNDs5>{`Kx+<&TU=!J z#aE9^wzYs)cPi7}WaBHmXT~Oj$Q;f3!C=yNapzds#Hc}u;bzg^(d;860k#Au*SAg9 zDZj7>cV-7^d@WDQEyS<#1$%P(YcK`E%@x3oc-lZnFuPYWC55$mr);6_AbZ9)nK~^p z8eP%TY`H+Tl-?rfPsojLw9zEI>{BJ{1za#wZ$I|@2qT|v;Z$H7y)Psj*5O_mMD80c zerkbby!s-!EN6`qEKw5MbaLxhB|m6?wikbBd`BO`Yqel9wQp~BfYB&Phi*~X#8PvHQ2VR%n(TAcK5iHZRAw7F=}AfK-(fqDO~JR z>~*+n#NL1+=Hk;u=G-ScnDrN2d52Q8&rs!)9D2HF2sq7zwSb(zvT+Yy5dO;NoKwSy zTwHNm#bwZ*mUW;Ki6HH}NF}NsN2;uOHGfygA+yL%@K;OI;mujU^7?W>5%i!>(UlBaL?IC4J_KS;{#fqtUgm zdnD#=|0q?JeSr{0>2;oga5OQiN<~PMmewe;M7#6#?ti;2#{K>8 zXb-pgO}xttMQ7jGx}6=-pPmlQdt6BSnZ9|>UEXEMfws(boJp8}0EX2F4>gs>>@Z;f zb|FSi;jFnD`6+g#`RbadPS^F?smo72 zln}g9GHp_yah8rXenJw^9h_Ia(V%`b#qt|NV6I<1)7kRGO~IHZR1gDlUqWQZ5*!u_5d^^6_)Ynk2tx?O#DDF5LUFT0=|}(gmTI-S<;3(W z>=>D2&0Br6-e0$8;a`U@DOC>&oW>M53;lA?l_&n!NA#{^t^|epTU|Hr;-{Babxfu- z#FFle>xsGR%ZX$OpFiJy_{3}Zl!X?*=urw-Mh#WO>N9;_q~Hlj7198xBNI z=?|adnNh$8l$^TmkNV$|Z#?A8Xo^QUFlf>aBlMP|7qk$j7jV9Vw>5)ycs{t4@*r6A zCUqnh#kAg!$bauU@$Ju;{hjh{4{7a-lfT$*Bk8#@Qk_&kzVPj#aC#QwO&t@sjBgc% z%k&J->rdnra7Vq2Pj{oYXC3z6V)%fv;B@BHk6alT6dPQ~DM=++PJ;&Kh`bPPu&|H3RfbVb0eAPp7R4CWZ%T#wG6b#zr?u{ydqO`{b}&w z$Ha1?HA=mO?T8OODsym0D`{XXVLP{}=0AF;$wXx`@!KNMtCbHDsKY5+OnClY0p-3& zvm3h}`(>Lx>4)Wbg(@FMTD-GIIWq3%eVi1~|K{kAzFKsPA2j~oqtyX;wBjIcp6Ijc zk7G@1YP7;ae{ii6yp(5<8VxLJtF^J6HF0C6!ODBQEokqka%Ru7DQ{Xo-TkefI`uGA z*DBv+_x3qg3#y9N6p=!m!%+KX{XgkZoAyySJ2kw-sHz>*SMsxcymEi_qj8Gq(yYj?jLT>6>suL(l4*$@!cYRd z=5mvqb(KaSK6%aKk9zzf>D(wZGmbSEu`j+-MyTF-;u-&BW49iSX7V=6`M0YMi^}!d zE8n|Swj#~i6bgpUXVi{z2StR$sLVlR5I^zLNV<)kcWbu8RJ3@8+IT* z%lF>!ya-vhpR{jkuh3ga2|W?Yprd^HD{g-VfWmsewAwCFWn`1?=ou~;OlqcYO3@rRT=LkYw<47S^&k^^SD z?JLE#K8a*U+Q~43&@+T7apr&g=n+Bem8h&GnRT9Ah2XC2VxqiM-+n=-b=dJi$>%`y z$ACNhOq{iK?t+x%W%WM6 zp$4$sQTc!#0}BYl~( z$Arqn9;0IOa$(6NqaFMEn^IF7mW3kSOv~1!?ZKO@FHz}wyq>RSWgjY`Q$Qcap2k=z zO^VMu+)H#nw8-w5(WteA#1CktSrw7Jy)iRHArKm$-|kCRb_2H;RN~3PPpeLqlq28# z)7nRzr7tqJOyM5z50ELeO=VUKUFDNq1l-(K9hk0pHf&UnX_@JSu-3uz-9p-ksR?lA{}DUz3ht^yt#dD86t%;d^H+|$w1VA z%4@J;ajME~j@geG@l&CDFJ~8O*Q|ZLwGr__=T-hLm?Md-tbu zDjJf6E?>Qsa(H;_MT?Pt*`sX247c#`jVD8TLf(tP%%YNWYFJHFCABDIi3Z&AD8$m$ zs+D4ZmJlFB>m3nQfU4lpoX1$RW)*Ua4O?w1!aEpcaW?Bp95qVbPLJZ$Jbg%v6~hgM zAM&jbI)CRrl9=gRb>1pS^In6h%SDRurve1!-+hCx7WkTcO0OIaJQKMVxfi|Z6#m*h zKdO!h7k{o>bKEIOnl2JGo&&aCn;7--??X`xotER5Ri+v{!&8egjKNbvRc7GHH9qM5 zt+hAg=eb!hY8CP%tu#`Iu~Ye!x*ScF;tp4)-UY2r@8rj(H#Q^+fy zPrd74b`ongTQVy_q(i#O*g$tFwV#~NWoquQ%%ydBnmWnh7c~{v&3+w=kG|oM%M{>e z<1DbIoc&#vVf)osw7AOOLy6t`1HVvvEPrWpT?}+C27W87A;i~)xHF6j+!(z?&~)gN zx@8)erptTrnB9(^(0FSewvWo_>G4}@vNp%D%i{gJ_wGjAgRRBYZ>Gd54o8&2-;6H7tb?QK)vOzYQm59(Eey*n)8%)HXxz8UM zDc~56m#Slm&F0g^PK>aNjH3%_b!U;;1pJV&R;>0wXg#g*BTDh1+=nmp+cs1kj}@#? z_x50op6(lTfgsW?L!jTNu5*luUDOn>byGzZ2-GSqzq)(96{zZMrZRDRQED>06cqJ0P)7z6iT_j97Al$rF9MUo39-ON@qxX4>r)PAs` z^!wOg-$B6Hxc}K~3L7Z2y(sC6vHdIwk+q}kbTuEXK_yu5dX$cDAu|^@l<2LcdVCwG zp!n7Am34Q^Zp1Xu#Vo z&h-+TbGvm|QuHT1&`TODkqhK!b^f%3plqO={6sg)@O&bEwItYXh*}ABBLcisSn*d- zM+uHa{&^=zxm#m|zt|*<$wb36dEbyKMLT*sZ1vHz;ShU~N1s5gA9Y9ja8Wd9$_cKM zFZ&<Y){9^M1{D>o7oX#X>rByuwfM zoxQn+zv5PQ?Y^nmAVvS@s5u*%;d5Pd9^tj_b2<9gj7H`O+UD>@LG)uB9K1nH3NUJ! zW((ej^iF>#cvnA<>FL3aR)-xYtCq)r#shHA5KThm%=TG1EoI=W1N2?>im>B`%3z*D z4&~?s;-c?w^WD-Po6P%uXWpwM5{yLD#Gp#(RAC1P!eus?a->#=Vo;3ZXNr%A#S3lv zY_NQN8<5?SEZPTr)B;3OAD60atFI_9Lk+IK;JSe z?h!o$z_l4eDQh_0?v@QtXU+K46eg1I^!%fAwnQszrv+t1^ zl^j?zeNe3FW#Rc)El!oue-JqyTc+~2U!QiFdlO_nqifS zPrTlm>|5Sq^Kv@y=px1mq{#HSc0b)X8@%Fm_)tuJ1GH?LpE_*ku1H=TjHM1Of4e#R zc{<%y;C5#=$f@P9vPSCW6;^j&#RQ-jo`TXQ@4P;_$n34UVG4_0oiF_n=hUn_oGBg@ z13D{4FS`p*y9A#eq>`uv;1+)CHi6yG277gg}M1n(Tr%W)wOb$d7y_yJqlia68` z!4)*1N(EQ~&d;A42jRa_#QqtZUhB>C9IOI&AdED(K)>Uj?AoKJFRmEx&<1a|_k$?T3p=`f_o4$(v2c9T_DID9S?&ahGW~qe18&aT+ zzNV*C7{;oFep|2EFE{AxHBQNsB%d}A`wANCz0Ad~MYK%oQ*-&i*6>p+pfVk+m$6#9|bd_C;$zH1ya%*9<|osi!Y!<0Uaie) z(t*SU2t}&ct7Z1PY}^j_b`EkrUks3Y zT4hi=7un^GMXN5FO4{uJ!9B#w%#XC$qG+OAzBmGbLQJmiec|Fl9Wq|6B z;nd6M$v0TJf1;W(AUW&(6c=t=fdD?q7YB(@x|;Nl^ca2qBix1rzCuUUia)LBX6R!Y zIHdKu1XCy&*Bg}e=_xKco1c(G{i1yj1JbjU)ZCWOL28y$G}1?^P7$&VimZU|Hx5b^ zosRKr2BoO$7^4oNm$?i4usmN=RLg`t?REAtpCWL$6BL%Fbe|uP`#y)vf&Ic_Sb8Sc zneXQExda>V-q~zuJ$*EMPp-wPB1~zvZInb#54z4u=;y)!niS;+trO$*{fpo#`Et1A zzZ|jDSBH!(Jq&bpbTL)wp=+4ycDE`}lkY3=&+XJ{dI=W~KPqs=wteIq;(rvK9Cg`+ zG;%nA@c{iK_wG~oHZeY6S2fco61kiRD8mM*o;=P!a|@EDp>$@6PQm7 zc$&Fb=`(y*j|qcjrA!~^BUN^%RPigo>)fB1?Y0mkDM4VO|6g*^s~V5{As{7ncG!~P z)D8a!$O+i|e}2E08f_51|1c6aSzC%yzJL}*FZ9r=P$7Pt@|9W<5phADOSgjO^h~|& zKkWCVBD^SJ+&ZrilCAuw=slVL(u=-$_7pMiQ{&gD6w|sAsvubR9r^~jP`&!T2?+Ro8;XLA LGW3^}e&GKB$_v1M literal 15857 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 nJwnOn71@%fMgjjHVbpiT diff --git a/artwork/LiftLogo/liftweb-logo-single-big.png b/artwork/LiftLogo/liftweb-logo-single-big.png index 6f7cc5f12f6742e1475a9ebc8130813d19054829..01494a19005d267b0ceadb9e370839b38a9f13c3 100644 GIT binary patch literal 53416 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%0D4OFXZ7X0#^J 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 UEv%xZIsgCw07*qoM6N<$f{d{!9smFU delta 2018 zcmV<82Oap$4et+-BYyw{b3#c}2nYxWdA-@1Ig(Ni4HpvU2AQWGa*0#zxoiT>7?MP!S6>O#g)_=~UL!nMPj$k^&OlzmD z48H?4KI<`1AraFpfYb*BEPTD}_K}ZS#(^5r9LVoSO>mToK_U&eWyrmia&g{JV z3%fRP=D6ploy{RuJBmYC_hPfWt4AcWBffs-$9oxE; zPA44HcwT@ zNJ?W%N1ifzAMmL(V%KD{kq$KtTb5|j-49}(5aNf&UjOdSpt`jU=Gt0W{$t%RyByoP zIgQvx;)FZ}JONAqQOC9>GTBc_N@Gh$r82q^sK~NLk$+`AjgnjhVmW}6XgTRhwgWSP zo2rcm7WCLppYqS^hB+H(Ro&+u+uE9o{RC1PTRJ|Cw?%GCo||rHQY?^o@k}EBnWOqC zU?s@I$%H7}50t6){&Yrk!^{U(1IHZO>Whts0UBF63Vt{?%wDBMDS{jknG0VW8)2y`=co+BxNg*KI z*wQf*cnP>ZVIr*Y)#iFC<`yx(yqITybv(#=TY(Y00GAC5;_s5ZL5#<6`qd{#fJ3R~ zbi)h-t5o+b$F}~R#>^T#LWY1o|AYp7i6lY*)_<&CNX6VD0Pen3=b??)krxiM^{3cM z3CUHe<%Wi@=*u9PD^>tSs(mPf+#Tw9#Idb}yd+H0mH{X!&f~7n*CvduURBAwvO-#S z+gunN&$OtZL9<3Z&vtk3$Y{_H(cB!WIXQQkCbS#e5C@L`)#^_YxzX1?l*)!~m?gkx zRe$&GhO1*VIs2oR=^*G8&=h)J2L4!`Vp5iaKLk8Q$puBDZ)_bF%z#0b8X6 z84*W-egNGt_W`#6b9~}^b;JBVkPq|%-vGV`lmLZFrdc=4H9&#-{60+5ky>6>#I2vX zDq$g!h|9XIe_&)ZYy1oZNu=7>$tj))Uwmf^ zDaFgo|D`?;PC0h(SzCwD1Q&#fQmN*TBx^dhHKycZ-f}*f5J*bU20pmN{sT^KYXm!21&OGM z?b#4+U{5iOO?gmI4uGI1g~(!GdkQH&)T?L8FPl?(od!R@44t^AM1#ka;b_r(VrzCe zkwU~z%F$FdbitebBk>Dx1VG5u@b$^`<_pi$* zrT5HWTqq=1USGE7&7&Xe>TGYC;_fO#{F>^%mCh!BE43Pgat@6gk7I37?650{IM+m!{Hs5p0PZ!r+ z$&r7Y=Ip@v$SbeCbKt#m7antLD_|1mPNw*{(Y<5ilP(tQ%$_UOegrIv`8QQ8&!0 z>y}>K{nV}vt12tzXaM1Gh-(*BMK6tv9RwbDx%1JKNm6LTmrMkn!YqRO_EM6!8__%uh5r4SLv8{0ck(?&zhB;3O zZw8tGt)zGct&e<>1y`1xfBWdEeZB9#|E+Is`@&ct4w6u9Y?>7&zd{n4C1rHB2D3q% zB?U8qF{C>r1w**9|M=hcOi2uN!(0n&0)B@V7j7pWPg)(@+L3Jyx?$c6ti<~t=rHgn z$F?SOkOWe6!&|IjYSJ@^SNv^rY%52vn11Mnc|Wi_X;|VVlaBHfJe?V zz)u|88cd=4e}URAH{!**B|tau8^^W|PS-pB3pVv>(E$mcaR2}S07*qoM6N<$f~vE- AtN;K2 diff --git a/contributors.md b/contributors.md index d8665ac509..8f21e03b90 100644 --- a/contributors.md +++ b/contributors.md @@ -50,7 +50,7 @@ an [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0.html) license. ### Name: ### +Marko Elezović ### Email: ### - - +marko at element dot hr diff --git a/core/json/json.png b/core/json/json.png index 86da677dd6ef826d791f647be21c1b5e8e3d2f1b..0dc928b0d8477f4fcc446daa0d9c7dea1d690178 100644 GIT binary patch literal 20780 zcmaI7Wk6ipvM$;{fB*r42Y0sw8h5902@pI9?rx2HaCdhL?oJ@VoyIi;Z5+}x&SmYj z&w20uxqs)ZQB}jMzWPRlnu;7IIte-e0Kf#vOKSiCh}{4H!ocg74}dpuk4yl7+7(Dz z;-jb0$vUd1rWVrR1z(l-9HFJ6qSdnZh^)AH7@HaT>(@c~K^PK_h%BRO5~d^Z^i;2z zq{6sZyhka_VpBOfJTl?;dRwCGbXM;A!eV))#QWLaSr0LRtMBTHabKMuGJXRiOF&p( z7aB>ZZCm{kTC6dQi;ay@21!^G3V+$(J~=tTdxxvBLBaduAbq&};2>mYr+6nMn~q8d zxy}QTj68eIU|o1kcr^7IcGVml5mMH$F|Ns|lFOfkmUda02~pX(|1x zwU7$CM!#2OK%*{miS@-;rysip=F5T_2fu_eMa1Hi*qqKNY(P{CTQ9Hl$?acY>rCRD~jF< zxS7(<>Gh*|;^(Hr3>n&&Iqy5E`LDBqwol>Tz)7WAvPm*Tvz zyY9z@s!L_obnndx+C1`zMxIgB&mO0`4TiD{n77TfHU0`IwB82kSu}q^`k5POqTsLy#_x%tDwhV!;dFKgVQ8jt#6p={oQ3y@~jItHl@hL zR0;)vM^c8R?^Zz%}G7 zwlm;SPKI!yf0e^P5M_92uPW|KeUK_aRp-Oakw zXbBn`bJuEoE0C0uQ#-vd%dp}Wxu+=&xexzJ$bQsxFApVJ-SFd$>Yw@i?0*A!3zJPI zQB_>sZ;HH=o<|oQEyUSew?fUsEi_UdS|HI|vz=Onus7(%c(bu&Cd97ZxW0wexc7pp zd7I%H{5sG{#7y9a_X*eC6-galIhN2%L*4wfv*ZH`%_~_Ds>Mh3pA-#tuW~BIXw?`uUlm5P*+XJ*Mx&GJ zxt$`FoW$*V{%|l_U(g^nC`n2H40t1rg+D#Xh@$rk+z)`wfMpm02Fej3gI}x~nV2x` zSKwN`8Y8-%@=W!xjuNvY#e*`+dK5vo{-EzN2Ph{R2Kgy2C~xXIY;O9t-c#Yk24LV8 z3@C?KZ|Xd1d~|0zlPtpJ>b4%C}H>SXF&~`sT4-UiqyD~#apIg3IHKJ`vv}rDpE$^EOG<^1>UuLl`~od`t;(H+^%#4!Q&m#W(|j4Xq{LCoWVPyO?GNEpX9 z6y^p!n7)(b#OM)yiPqp-GHaeH#rT}r+9|X|?wqwuZDhp7?`W?Skjj0^xzt4594|ipj4(gHpCGFqE6ucl8s$Fby%Td+66T=3q@#>K2=OZft9cL&j9AEM}!k z5@atagl~&4cz7W6vmQ-hG6$-i#VfVO6=Sx8`6u;D71S zKiS-!lYor%rufeQ)9!DXPY}VjyLhKMeo=Q1hd}-u8Pm}9RT5Ep>29B?M>=-aX7!1W z(B`qCNi)Jw$nOR;AMiKuJm-CO34MthdfSAgfz3{bnpBpMQ4xeeR7Sdc+-Kv;K~Dw3 zUBcO*rhCRpN?Bz0D>}ZlZKN!+JS`*mcyCTaMZ4U2qArkqXTWw@V+CF^%CUIPObabB zPki|GNl$VTc+cwuj5as(nvWlAiYcxr$=Oido0Eemo}&HLm%7Tav4gu|VWu`0H*~>` zN%RbF_&HlI)O`daz{Fx77oZ{SHeJ=Qk)|Bxc=q5qwp+};9p$;b89o%0HY7gvSkwi5P5yZqb-B5Z40S zO{?kl0-c@RejR}A6{YKO3&DdWW-2SIn}LBbDa|#(YiC(>nH;`z;*_s?TX<7b6YDLu zxzWR0Txcuu0HQo9y}K3GVw|((=O}3!uFJ)x8P2i(>k9}eD3b!I%G`$fJf#WU-xs|7 zOD}t+A&RBdM`zRy!e1;Zsek2UF8`95F~2rcwDVp}Ci2-@ay%H%fm=rI(28}>EfB>j zZyJ(&J5KBH?J-;csPgFqhwpHAZm}G6<8R64ca_CKg@DRZ;B}IK*ye8z~{* zca$utuN<$=uh{ih_^6n~5hNh@#o0?s5V~UXA9%4U8ym61u+vCKz9m`rW`?o&jO^sT zS2Qotk_(DK3P+7S$NQ|l=Y0O!i>3r!EIvzi#2aJa+;0XunwfoHy%?$?{~6LJyw7-> z<<;%ffzO~LQ#nf{kDSsu@T+q%(A;-=yU&(tg2?K%CFF^ZvQ9gc&mw1(mR7<{v4SM&gR_W1-$-FcnZ#=G@iR6+p zDoA-Ji)muww2cAH{j7CVWT!y?ryn9xn7 zJ^iH#FCpvHbfRFS$Hx~bhB66#y=klmnbo)A)JiRZghe&WDZDfRi1*s#`6X;>hIG1> z_{+s;ZZ{=lYFqAyk{e1=XwI*1?oMq#$@l+ENJ02mf%vxLZ#Oc2n_)*h-`rF{=_4lmKhQZ-R2&*mBCi zdYD41q~CDQI{N$*B%k;22j^7;*ky6}akRGy|0rrn-nm5p7TN3amIU*L3JDkZQ+-Dx zz_VObuXgyABZZl;Bf*Lf(G@0o{DNA*ZCdKFlncP2pRRuZ99mo08~RE`9woN*ubO1T ze=k{N4uxW=s7_;_Eo|T768ektkap?rpyxKuGYMzPjXu^dQM1%&E3S>IJ2nB_4Is%V zY@b0%PAjVj#>6kY>b36|Y=r6VrxDvi>{;ad-`2fd3}AU3uKlHV+%>>xb~aJ=b@1|w zzJaFQq4@oty?*I?G;!i=QS_1WG2N`0*Y6_XwDUw4A=xu09a3UeRwMks^u4*br)c6B zV;TGL3ylhN8e7o-mx9-r3TKr_KBp^(c`Ho%Sg-XkZdzT*mhI@@Z%57TN~Ue2DNg#v z`7EFyNL*DS6>tYmABxYvvpQT`HRYl@NF!Vwb91VwXLC5`q!el<2AI6|k>+Q44yD@x z%5+Ux5~4Wkh2M~J7yQU~vH}3#|0=s73!Uv#_{oa;d+dhz{me3D?_cp592mOUpucYg zi<&)JG;hab6_a#OQ&Y+7%h?v^YA3jj6TPxe2(`34Scj5Gtzy51|YVM%xQ*Y&xQ?52*D zu@SzAg_#Xk&sW*bwUdL7k0~LGPbIw&`3tG?un6|%YJj%aL!zE4*E_UHoIG z#An@ht9`$VhuAkEexL!z2bld46ISc`wr}*i=oh*`0QYog8eq8djK%LCoj>%C3F?GU z4eg7O-Zc(V$bj%KGq~d9Ayq#d3pw&6nlpbT-gNN>&U{EPkt>Oj z2dOBi41*9GGy&d>eKS8sCgvwhkOuiFFRq%UaBwNAbiQ>vpR6<=Ez-;f_=;a1&Qk0e zi2OzyUJv~HhKh%khgskj?0)Mthw%U9zcLbAQj(W}$VGWq$j7W6J|=gSBKv1a(09bxxB@xiDcs8H!Y?U0i+)TE4jTBqzDLK0=&1t@*PrYq)Hr(B0KiY@qn(+KhYd ze~#OO(Y3U$trhd_vsTO4kh(*W#{%mQhvK|;;%{N44nFMfHhlMTwC89}zxy_#uXRpE z6>FgtZZx?)8W2>MQA`!ctm~~UN(;-WNmkVaYvTW9vafT2`n)l-h z)rvT%obUh2DlHL$%02vf?jx9+DvXgtdgi8;UPLS!8mzGDTPk2&it0W zsS_C9N_W?voLDq6k+B~w<=X|{X^pY~_!qox$CPz=fI(Eym`VblWEt9?&N-~a@d)9^ z{VBYT@KN=S=R_f3{LAm}+J7jbBF&5c@G7Lc+q(1deS8+qMtM;S-E%9f81pa-bncx4 zaGLN_E(}VQM;#aEn}l1K$rq;0(MGLiLPS4ReKhYJ3w>n2oug>`b_Id*w;~4Dd9O1=z09 zd1%>!Z0^pRZg60^8g>Q9(8n?z)$GqT0J;QI{lo!^2})U!?FYsy5GhOYZMZD|>|#G` z??S4@Rqx>&xbtrrJv67z^8&;F$b5tMe$ss zbnJ5&X_j1(qldRQv+xn((#G0y#WI%ZLj3J_yFc&R2;yu{rqx@B+er$>IG5kcdcaFO z6IWMqIm_s^lPawFQB^B;p$%^VByFs$PZ40QwAROy%32B023~*aydj25P2+tDVtCc) z!)fgK|0b^#Mllc9Ew1$#C>+|!BXuog)Jd>OA;a)+3XV%l`yZGf_}i5c(Kb~*@cNAA<8!M}O2wP$yfLB;$K4=BKT__F76CkH|PDfD_=&X7L*7C(FG-S7JH zi*l*|B+aw)x2{7diJ=e-1x3OowLou6mcfKtKYGk2;fqn~1w54UxCK@&1TtBId|oWt zICK)>rV`p|w5)`&t0LB+u+gV?7{MtDlR>=HX$gu5d%hqd3d?Ur!*VnHZ(dA!{^x4P z9ueOWr#8%)LI8$K6t^Su(XC5)>b9UjH}4gY>_8FEDEn{TeTE%OAc=jNLyT~IWf1>u z$n<386+oy#3)|+o;>R$L%ja_Zl$`6Mb@k~B50$bl97a0YUJBCO`EKMkz@caPTqZIt zQ9+igo2b)y&_70A=5Vxm&6}UTxC=Pl@N-|gcujzmq4%3)8{qYSBLkt?Kzz@w58kVZ zrg6pZ0aMhARK@+D(^#dd>Qb2wQtD(hP};B^b{72@U7cllLBhb+$+B4Em`qkf!_S{d z-W1qzsBKY@1k4}3bVH`wTw};Upu^-OsCKA#1AZ%;;&Wm!HgDSw{rbw4G+GIQ_%5@mIUa3y=`1`EC8B_Zniy?+on^im4uxu(!32(v& zkURTb5cRR1Y!4TZh-l_mIKu2vP53?o1Ju(Rd5dt~Yd?VucBFst3!B^Yfd8x>%a1I< z8`gE+L)vMj5U6a_(JzHI?8PFICJsN*Ja3jFl=X3If6cvUaCybtqn>#gPn#Zvv3cnkp3 z7{v0azpqs^xBaGdoza2ry$%EY{{tvMO2O7bsVqxeBYE}AIWpoG@Ju!d#WgMad*l+- zzH*D$b5fr6f*Q~zl_I1ueOXwX6yeTL!+f2A5Tb-sDSgGbILmEQq=&n+o2r>=qn*>^9+dLPdZzu7AX^237PQ?gJGsAcfqS zShrs%E~FDP{|CAF3VB6De@;XR$~B&MO3dsKpA4rN)1WbA8+T800EHD~Q_sC2GdV`w zeh$dQ!Ew6Zcz%wERzli3((JHxT%PSQ^Yu-ct{qH8lG|AhkqfmZ==1?P_q>s@xDRJ- zl;x1*rRohAj#zEAA3uf5y>^}3DwyDu_(we}GcDkwbmV{3Zy|0wjBh(^wBvd0Z&VNS zi|dveWOU>__&ga`_ot|Cq=_305iHo&OBl8-{`K} z;=tVZ!-MmBoX~xeX4`z|`29PJVIa~_dTZa@bB08h$Df7jEIx-dB^2RDq|szn(ye_R zJuJL6%m8WGcEinfEG80GW2ZPBGuk-A!|c~>5S!`m)uewxmd-c`Nnw4aI-={O>~Omq zJ-Lerodu7xQ5Y$o!+KW=nwj|L+x-0eKIho?iU#sD6dHHq&|96UOB>e0*Tt?IsAxL{ z1FSkV`F(TZkg9faKXzf^2Wu9xXJcEn&yM-Mh~`mPtDXA>6`yF*6nNbWX&t*aZ!@IK z>YD59FOW|Ur;V$Y3qtTaAu~b-EbJ>NZTXCicf9o+UG5?F>(5Q@aEX)&9TwEw-6lks4 zcZ$GlfMZUt4Kec&fRAt`xP>s^^i-7R1^Xkuq;%I0I{n}|p21zFqTGx642D7H;o&2% zRz9snXyF-GRt7a_#9^mVDi)hP-kT|w6pqLVY25VLiSOQMF*OXFoxJ z8AYq^uy-Nj(Cir8Ap6Ye-|Q3HlV);jE7J_ z-Ql`^8A&pLi+r%QJz2^|0H#jxs*BwGFG_z_rC=w^%7bTUDq*XAKzP@$@11Oxy?N;3 z0vayXvKBajTnnNuW!t z*__g%c8rxN=at>9H;da1+L8-KGHtP%{D5SU8jADp7+VZ>ZO?m@mE)()Ltd1~-O3!LyvUhOL zHF@3Irn#b!4f5`T@T~pnbnc_aSOE(%l(fl2-T&XKfTYXI3Ek;N{LY z8&w}ZJoGdyYTmS+6PV;-0~B~p`7X+OYb`Eul(F18Jr2@1!$pYLg7uSri;;P$ng5hG zYu+pmPBm276lK6a6-gV?tomN$f})jDG@G9{X=6!HxWVeu(N5*M6xrF-gYoCVls%1Z zPTK$O-k}H!4u85)fv?m6x~iH_pC{8?QXP(486T|wKHf*r&7E%c`aeP|>e#FH{kgw5 z?t}dl#0ggHe=hnPSorCH#{>-uKGgVZZcNq`o%SMvmaajp7B{Ys=Kd)3LDBE^S0+z{ z27FgsElB}KiUy5;KX2trP`^*OD{=roA0R(|t&x+KUGF3!&q>clV>hL+r||1BQ@lrc zh0K&MvqY*h7~>N10hhOSGZ%IoCGwCTeD^c34hc?13bfhyiQ)As^YHY6h%A|%k$j|p zJ)7)wo6E6`c(r4B+zKtG4y_IRuvC*H)cR7gbpJD%BZN)f>B*N7I-2_%w;@JWL3edX zYr}QW4eD-oSXasA74v(nR)fki);ax=(xRuA+7>!B+}PMTzfAd$vL~M0&)kabP6uKF-Rox;^BjNCkL? zd&VtrfQlMA_cB0PNdf}31cs(pE0WsruxwP^GKAVo?#s zo6P%5;4h!vIN~x(rn#6(0-D%qQD`yT@h;e0@H+Z2Z5gPt0zJKs_2o5xghRU*L84r_ zEr0cq(>S@TKHMKO%t$+E7nxNU>u_*;$5>%uPR)F}h+1Ls88W(wo4*ft7?xOhS^f=v zI8E95qTRq!_idqwR9=bt^u3PhVK0Ih4-bWJ-*+3Do>jUticF;vEu}J&$gI!GkV_US zk`(Yx`Uv#a${LV?X?MQM>9!=C78VZPaeSOla?lvBg-4y0*`7+3P1Cu=ySugG3KTIZ z)^wUI#l~tkFOH1TZyCCv4Yy7If(;C6>U~S8muNuHuuP^p;iR~NOW1sxq;zy|#@6C7 zlVO@pAWLs6qZwM(z41P7??Y38-ILkEbVFg6+_Y)$$k{AQDbvD??QaR#SF6GSkzIn+ z9pQNxg}eWUq?RP0!2(|_;Q}px2E);OruJp(x-oEdBA<5|IG^%mRB$zS8?5T-+G5t_ zkL*=jQc|6mp$b;q#u!t2Jh>X^*25~;wf)TPQ^ z^fj?UnQ%7l-OL>XGp^%wKGdUNV!3BXm9WvomKo$~v*%(Cwrr#z6R6t7An=B6N} zJWi`Hi~H%0g)zV4W$;HnzV4=r(aCyMt#0oNKA6>P2==1}Ry;=*&3^UJCRZb`11Pf_ zskfZQs$=ys<_#JdVbaE{Wv(Dxu6z{MN`u=o%bi*G2^haIkmgXKmA0F;SwweCR>6QgvbH7%l=O)da z^+4^0Bn!UV(DCuu-610>yIJtoeJYFbi*6z^c;@l~Jr?18BrP9cw`izag<>~9(WdCZ zbentD?{M=HDE$ifa*CE%CwRMSm#erv?~sg~%SNFmtJbk&okBL$ORW z0wwJ`ox8BTh z?lQy6rL09SoWS%VHRz)o#Ib4troMbK2fVm#@gBtOALC?a->SnNrW*4_*t4~E&Eiu( z_@bh?ndi!BHd+G$)22`cdZ__B>^mIn{eKLFcS@i`JL|HSNe$L^_q3RFw-(%tL#3-D zA(d*{9xP(*rBPRi4IJ4+Hx-rDrNHe7?*!aQ^^l7KxvW%%gth3NX3m5-hhPBx*cw;U z6iP9>W(ReVVAo=kJ5y_D9O(Gqf%MATQKwUE&BAIU2|h9q_-i+10A#R7`!DH0fKpRu zn;Z9Ee!_nN8&u;W?i&H4G+B}@+1<6Nh z2TB%y8ocI+H^#P+BiKM_4RXHyw)dxTQ5zb$|W zV}Vt9lpCp9USpM)ZmPG^mzPS!@74kE59n0fKKey>95ugbYF5||j;0oE1gEfn1@dh& zNwYc?pR$jX>P`lxOu?(+i|zaD5G*xV8o4+mR;L_#dRR zi}EhUhzK$a9qUuhcWu)7zm=pOt6qEk7>J{r!W#GI`($+FOtYssHA0Lu%Mjm}I#q?bwYLZvB&l~kClc)ZCZ zxFy+&_!AMw&ZTm(kUKu!h;1MB=n^pbn1B+S;*U!tp#A|%wXL@O{fr-CG0uwrYfz_- zECZ5f-QmMn&qXNb7EE`0Xu5VGg3*F9dOct!z9rj1_i^-?dHxcYZWg=d>;sD)8`B#& zlUGmETKB4QqH(h3tC*-N^j9^yc&}g8+>JE<9bXAxynI#DfEq9wC#yl}7CxI+zP_er z)@Na+&VKmgWd_A3E|P^mvAh#4o4N-C3jD*3*<+X9V^8dCH)JYp9a-m5k6| zvE(t;#ZX@Q`6w_ze4X8S%$9YNTa*sNqM_ouQYnGZ3J6EN*&#RG?N*In)70&rh+m#O zQq<2R7{hdjY)4*cvsRu`5)Xy^oP>%=b?R9evH7d%M2$RF-}arJ)@{nQqMv%b`etr{ z#xoz&>T*hvESs(|oh>^`u$sytr3ieVG=|mV`3gTPvoem`V^lQ;kEGr=huZmiTQe)| zXCp_28mp({h$dJIW8um(0+jRmu zLl5gFIK{9M(eb&nu|%5KER4ARZM>pqP=RgPFhN+GhEw2%r(oGVF*t4gRWi$BmeA;( zG+wuMRtr4%ksuR_y)!*)kK8oEmI1?Zkg`#4t$M4R^=>({6a-IKSskmH$tl%C)7!9u zKV{W%Q1B{juxN?B$$N4_QuBwv!kV|OkfcrfGZkzVk|K(!=4G99y`Wj%oa zL5uskbSpds5u#PHHlOJ9)p3u^ela&iUe%abvNAd)lz!7@w@BcpTUDCtTFKS^uyIx~nWT%!L{-y-2R$QYg zUyQS|(OB$MStX7pS&?VNRMI7Pfr&2#*c~d@{Z^*)Re(Q`kH4xDs|jQ=$gA5Hr>rrr z%4l9fbB@!L6@{$%a$1^(4VC{WzaIXgj;8Y{jYsy=j+NrwWAV;B>6g}2!c>EboUj&G zVn)lR?0hFS!2wp&IZu}^D-p4gg|s&Cav2)C?Kjm|c4Pj6F)gGPx~?@WX>*IR@ z?b)18Fp9L3;+JsqQG%3oAyd}7nIdX7pi%&+SHj#>ILkc>7w-SI7^2}P=SjARZ7Hf?%5mux@tt^XW zD6IA7DI4-eAokyw1Yivo&9-{ui&z$x-Xel?^5jB|mgH%61qpXO@~roLhie(5&f}~S zFr((@gjDArzZD4sjkP`i6^fmCqCSdo***6W-~?$yRVj%hX6X zkeE%oG&OLiBoEUY8wI^ER%|CkV9I4)H3I(qg-)-+twOk$254Gkok~2r9X}z`Uov^u zy-SqpoaDsr&U^fUF(dSaut)m~O{DC=-PPw1vt9kd;qC)54A~pn64dW3zCzCNa*q~* zkt&>yrD<<>sm zI$$;Uj|CDJ4)v}AWXu9qZQt4I{1nGWs%72p|IFvng<^B$RDpU)qHfP5G1aYWVuszn zq0IdTUFpkA#&L?06sGO?*WZuM%RtBwg7r#YZ7vJIag5``RCg=MR51$Rn77XrHBH$= zcE*d=hT~20|_4{QCKdf*FKb0}Sk(T^ahN2`h80(_Qyczy;A zgC-=+Nt_e&9g&1h9n}TEo}OUOw!Rlb?!Dcq*_Z%#ld>%*-QPqU#$7&)f*5M;ZRKR- zA2~9t1VHk`O30=?B~q0iYczg|lwR5x7g-b#e70EfF`b?0!9HaiHLNKq1@af-`xuJuaqk) zaSkI;h1{A^W^v~y;zmwSo=tx0IkLsaP-rJ>vA>q{JrYDP8*P8g{aKho0St;h+*iBt zj?mOK7?)R)0B+>#xJvt>Q{klJ&OcwpFx3*3qbn<&nO>rsh znrrp)5(1P!|2}!{eqfai=zp&BQfKji|J0s5KNR#@&rpKCEbPa7tXet6vwX*kI7CaP2yqSHD_HZw|EDLAl4G16q#yOb-z0SDy2%^_9eExE;p~mCmub6lO-R-vT>$hit|6b*`YM4u6DLNfzT3KqhC=lnE zmxpRyxe;P#q?`|nL8^RR^nR@G)@lc=VrY8&nj@)syz`R^ZMX;*(y;r;>EIU7HJ1s@ zJnVDi)Qa^grkSmUUc&W(XcY1VSHvz)+neAVEM z{tcEG^}XHNB+a6t_UmBm7MY2XjHdKQ&QYm-i#hkb5wl?zIb(E!!G;_D6E;-9vI4YT z(kU!8seVs}QRe8(emczgiC7;e(1E-~uUiM4zne#*2T4hApKqCV=Urxvw;^>C3Nc}m zb>Eq<%9dN#2sSGDMdm%HI1k(Zw6d|xX zh8A;N2}Gq`IQRLXyLFg8le2HHefEa`4%aQ_T4+hRS5|H^?M_%Xb)cWq1CDh`Ue9XN z*i;5&Lab0Z8a-$JYWDjHyIC6xWf9iZHhy0z1QK><{aIVv=(VW_>v(A`N!dgmuuvu` zkb>;10(`ODi1qS*DVr;%Y-2(jvMxp9Pn{w1J`1ABXfQ$6n>`Gzl-;Wy8 zcoXkf2H7*Y+&woZrxvQ-9^d){;_pS;Br&J<@31I^%q}Ry?MoWdzb$bS)EJV4-+((1 zYjS(2V>-J8FUmv`#(5TKS02O!iUtG-xihr_IW#pU9!~xyt&{U=hhi3E8j2mcebTyY zw&Alk=yhVn2~Z@)#^xi^AyRD{OSmrqNuTQYI4d|iOZIo;15N{XgM)*|!Ucx7~76d^1o|g`{Rz@};#kw`WDtSY~{%3a20fomkv~X|wma)-_)k&;bi@)jdPM#1p zD#!&BK$W?FdEb)xllooe@b-}@X6vugh0aai{7l}zQC}O(#@5A3VraW^pKsu#z_`7; zGsJ>E_}*$LgjxM2yKYZ#yVv(KAs`N5v*rh zZlA;z+XWkwFj!(fc`ZAu8|r=NqA@VOxn`+mcJbqBGDuONE&gs9OM<}_^VCd#8>P;6 zdW~{_%3ZMWlSRb!XJ{@N{j_=6gC$eenQZDP+_>uAO=aGVxose!Fgn}gdS#)cfuO!WTo@-*Zm^1tCa~M}`=c^9`b56Z z=+H?MoJt^~wy=w??7oYWb(FT=rw4hU~-``nkJB?~QqKYHFZg^b1qXcEO3r$Yg*}h@l zB{hS4W-|A5S_@*N-6>7iI|W&cuuxp?PBRB%yiPx+yJmeak(qBbyz`6Kh#6@#=#cy(n#gr-u;+BAqd4Ba8~y z^PER8IPZ^}6&-d(Wwy=`0CISX00iy1#&ONzT4_cYr(LAyf)uB_N-`@7mVw+UXO`&X z5JtM7Q27j93yn2Ai>$AP+NP|R$tETKjZ@+Ho-b~B1^D>B>GN@(UGh<#sn1);&CuBT z?njz@gjvHU;)&wcDJFm~p7ycfL%)f7@T+2ShWU69Y%kW`qm4Gk)WbGj*4ks4I-22z z7UJ}k%Z|gV(B!~8Q+0jQikp4gV~9F*_de&!im-H>yIyay(lM>w=%;LABZlzCtXG9m zMO?938Ag@2HM;Qm42*?9xM~wW%~9$ohddtC9gCUr1^+M%pNEXHDoy+e%(!K!KzkUK zE(zT|skU)1UO8EqX3F^~UoeL%~9p7hISL?fH|dtrSAu zMO9>Ga1ro3-K~q%Nky1~Z+zbG7$2uiGEHv@hLiQ`TmG!b5!yH3)y+#`I(lYA-7Wn|W?tU<0=^=e=tQ zR>Emj#TYovGxPgk$+(uK%Bg1ZW=?`rY2P3A9RJb}AXPHO_&d)(Le9)!;jV>`VuPl89{Xi&A<#BkU|+lC&2fS_*_@_ zqhg_bwd{nCxi~_m$G9#=;bF*yoV5L)wd*vT@G(NYH3F8vI@y$F?4VSluznl2zHj|26A zf9B?K`GO@%HF9zFraWy5v&?*;XYBiOO$()Mq%GDZ=_k?TFk$$qEPk^!Tu_l?3OTgbxv!S(>nD@?=@d0? zF&@)d1l2=%^B+ZdeSc_6&h8!}q%*haIMmCCE$Qbwu?%DL{VS;(Dil3>!J-{!)ldpAn4m-qO)5ipAXx%pcjXoh-H<+*3E{Kb z;(6eYFXHG1$?`);7v~Tjjk_9EE+vcs%#mrcpUKFh*bV2?YOpCW>_CacEtKl)1~uoB zWeLhX4GxPLg&}hi`9C~yVBgRSpZrc&Hci2;!$@0&;{WOA_|@z*{-R@u;KN$558hll zhG|o!bB-sW&+hwlJ@P?J>-oQisRn+Vgm@FMj3k1;2Uqoel`4f86N@Ij)UfJKW4%0C5P-o-woJKgd& zt;{Q+Ra-w|%=w%#-is}g6mb@nXPep`3>cgNOT9?=3Uo_RXC3XpYP~xd>|Hxp)P}u` zl;mSRu6>$McSpH^AI*+|Y(x!FrBI7viX@u zqt?#cd#6n)7d7Sz8>;+N3`;}z(zQ5;{upK!p;~4JUy`Cg#u$pMs1EH)u{Weyy4(5x zxALUqH>dg=Z<;FxWtFj4(WJ|!HB-hm#hOhy?SLj`4rdxYPz;3T2iV2Oz_r-TMI01cN=HMyQsQg!@k?XG%d4_W(mdb*a>Qdh(dG zp!e-YZgY#}d1PL~;gbPh{!&V5Ngxfs@PZM7+5h^W$$}Y=zn*NWN&8i~nLWQt-)DTT zL92_uA$r`G!cNnrGzub~-9q`7g)H`>xDasFQ(SbR@wW8t8k#n(8jCteaZeh?{P512 zk!@Hn#Qy!q(Q)jQ_u7Awrr@@|xH4O&N4YtwqDb5Vk2vds=!8zSTK$wN(`7n~M$GTm zl|%le9W#-CUX&0daj5ad+xH^<HYu<6`(@hm77Z#g)=(5j~dTZNjjJ74Rd!PKxhiD0b_qy+Lk=7Ehiu;hxrMdn( zz3__4pSfo~_kF*wYp!!$@7FtI%i&0b zHGk6yMLD3kq~7lv87UTaQx9Y!t@Pf}4l(n^UQH{+?7Wq4j-<$Ep9-H=gam#&1{B(z z!?2!l5|S)o{5R^W!k7u{rTI3J{*Z#I#RC}AgyY+iTci}@tDDEJ;|>hpS>FLvl4`sI zL0H1NAEhK}&u-bI*JSO93vs@7|7w`?swQU4njrAlIcP21>ubrrLnIKr;_xt2{*|zN`aMScue8%xXRbcAZDzs z+0Qm56rw=QjbK(f$syraO9v$x-K%|0=sG9fX*2O-{hmqFv1L~}{X$E^8suYE@S@3YEK+4z45f-r7Uv6r){jYbHKT*!~@l;Gq8_dvZ-`R=ji@yMQ*WS3wWMK z*loQKs$FHGNogae&)2vgc$f-h?Pl8x?^Sj$M5W0X2T$d4LiGDT`y$GAED6*;-5hH< zu_tg%CHbV7$;DxrH2zlK)0j%>a@)7CqqnI>?CyLUpOc>`Z0zxier<~;Y=$21t3$vo z8@%0lM{SJg1V*kQ$EPuxBlkUH`Zd=^ld5DYs`xC{aM#m1#(JFNu;@ody?TA}Gwddo zsFFx=cQ>}zP2Pp3(nk+mX+#B`zZ|$Sb+e3=5610blFiZ)Do8%KmGRgd&kO&eNTMH0 zs7~uvWO~PVk4~IS>Iy15dsr;z%RLqX#6I2>VlccExY~)RjuDkh^GY%l<%H;6so0-M zg+lPPN#zN*BlMAx!{K2=Lu)T+r6EW7s<)CNGcBt15y(gt(L+c!Q$>5vvuU}?21?FV z`lFB?89M#E+M_R{<^+Y7564;=j_w?>%7I3;jnex>{0IKd|<& z76r|5>zBUH7G4^@XFw*4fz=NektM8I+fpn~PItgDTc(_JOo1NrflhVUfEbaQof^bz zcdN?yqaY+a+dcFmO08vf_NNeiwvyhwTU9i8B`~=i`;kyitCPICcys%LB{~*sFd#4z zcQ-*K?ahAi#CuxQZ184H@yPBu_{}N=u3Hcz6av1j{Yf_=E=qO;RiPly4stcsb!kg+mrOvQ0sh z8-=^-u%xD}j;s7rtn)v1jhW&f1D2oMonakXY)|NID5>|byi{Rm^be;b#&6dS<$unm zwdNrtnyB9%V$lsG4-aHUw_1ZBZ(YoC=G&N9C{>SMrB*d3wpv2{0`>x7Z1suPqTUV| z@9w=t=+fe6R89ysEF7u_Urwr0}S?1$L1|UMJpZ~6j_u~ z5kwoZQU?0!bTYbcNt9na_r)gXmP(kT(pCUxa9V1TG&0k#S%*bgmC*6I0JjiV zlg$(HxT9E@8?17CH{vfuwRMVp>?OJWa-UgyNq$sd;`KzS<0ZR1g!A!@;upyJG>vdC z`X_-HWE0Zqv}{7U_0Sh6Y=_aJl-jj%!|g1`SIyIjYtZ; z**zC$L61a>5V3fQnp$^9z0bzjVFkhn@*pLz zj`H(Gi;BhbuJZKMVcl4kumx#W|=1`g$zbu~p2`0uU~TeKS6du+LARg-hxB8fQnL62 zPJRSWO8)OOp?@-k{v-=A?9xP`%+FeY;RW6UEx;<18CRUOe@^+ICwhNkmAk`kWldm? zmMo??pWFdK+k)kWuqJcUB+YwE{RVvjB(YfNJNEw1CLXnQB4mUv>zO@FNh8G{5be5t zY?uovR7yKP>5M%iQQm#Xow8+bF{7i9*6Y*59${&^44o!RMOH<(oP6 z)PN84?n=*bNXlCJur^e){7p%Pa^3@KBp|dn%!tYe=F$}baG0$h+> zOhta1+Uvm&>vSz{two5C%cDg{JL zDtM$-S-1Z%QEC}sZXj#bn`S51<+YSd$>)OZT%M?V(I8@4!SmdtzFOeMj>tdI(%e5> zHpD&-nBwHwHVJ4dNi%I|y%SgBObtAD%HnF&r@Bj{8o#z!S2Josq4;SrTt)Dstd`?L z9@sSoc`g(?Y3C~LreH(K`OR9-q6iOUNR{-WQ;{s`%ewRQ+PoX)q)}t%cpK$x#9-fB z9fAc^?0&ZJqNTv_NuGsK;i^&)FTZ5m&PO5|3?5Xe=o4p?zc|sHYhV`Nm*fb>9Wej3 z>o>NJDg3MnRH15?Gj(yLa-w}O2{$hn;_j?1NcGL+qaFpg9;13D(to51$P*CknMift zXfmvyl7E#5C;vKHtBw_>GH6H=-v7DS&rd)+=8>z${QJ0uAq#Uok6#k^F3u%ZNyh&TzjTZC;>M>C!F`|13H~`4OdvP=>0d|1xfP8N8 zKFQ}@J^O|zqZ>>{becM)<_r`iFQ@4>VhS0fOQc2}lR$kuR5Fr}H8iPtsa2PDOUAw1 z+d4W5p;x%)i<;Zp@%ZtZHLA~=N^vI%X*_M*3c=sn=;(AQgG0^t0s=@P;L2P&#g&zF zM4$ZTT+Qi_hpV{Odv2`YXGAZUews-BzB$KT5RcYdw+UfUoP4}8t60=fpQhx^JEG8F?1I7bf`FL^*T z)kBx{Wr6#BSE25HK|5;p?k_haaCfJxf9ODlG26@^i_IDScHuaOIbr%(fS;x*B1NOO zW|GD#%o)6cdvn_t(S-ZQK24&Cn^deu3TPR z=?6U6eGL!hM|`jD>usmrcyVdDYN!r)#_x_RM!UJS4vpxIFhBT(i_I!*$Bv%|fpKN- zlx4q(S`r3gFGVNR%sD(zt5c{OnkEBP^2Ezgc$43q*)~6-5dRJ;li%o!(WvABv)a9$ z%sN~5Ceisl5Rp)G>e$7_Es*r~{{q>drpYtW1Yt@r>r|5bR5m9~jvfISvLy(8)Ekuv z1Iv%hsjt0yk9}9cNp|I<^nXoZ!d};edp|Lw?$;SCJ26ytq=ONn$t~jt=_Z{EYAvd5 z!Gavr|1V2vo%>hYa1lQkW2WT<6WDm>NlfLr~tzI1Sal$H4wnd8n% zn-Y8O-7iHdl<&gwg=!FP{MS7^&8?=y80Rg?sLhg4MSb+2m?ACFdm+~#huW1q3#J(= zj0jEkuF%beWT_Ikf$-()EM$k6k;E41a*Nc|u)NDa#8S3PJuU4#BbkFKf-HT(Pg%LZ zMkTQfJ}idy-kPD|V7TicYf#|K6>wS03XnO5RK95Fl>zbRIIa-*J2I*Bl@@g^3VnKK z5@idfs^Huf7j-W8&}Q)hx_?{s$L!o(e)T}}JJ-A{DtH#)1s?thE+eHvjOaw!a&v|R ztXU(6;ZCL-|K(JbjB#PATRZiA#EAVB;$$1TvOtfr&!6ur=e~$&nN=6>o&Xx^5{CL)TmnuZfhB2 z^;OZ*P+wnO1!4gg$O401*e?z{IypL)+cdCKSJXYs_$We-m#DG@cPjZG60n{(o$eYx z!WcfGxsr?RQ3I^j%+Wt_I5e!SwV!PrNXnsM0;QTpk+AEY4YkpyAh&PC#^g`v)xTp9 Nbu+ctWUL?UELhN8)kF%6Q;Bn@U6LrNqRWhV1HR6>&}l@LmXOd%8vNQg3K zj8H_G)_!)s-}bHT+t&JLt>=BV?Y*D7tLysx&htEuV?XwNKhCfd$J80ua<8ROD2#_S zRCFj5>T(K&s)mjh|3>Ir+6?|5t<@2A70L?vzm)Qa*C~|El*1~DdTw{dzg{t7JKIh> z<(bf}c+S6tO))&pR)H;QwLKfd%|zR@X0E7Rb*$;`1NM6*x7wwtg>K8YZ#5FMQjD~! zVr5ojK2Jy8ufusW=+{zTqfgqSg0HD}dR(28&VBeIU3&7pyqvV4fB?01q+}hJ^=zWi z2<6{DMG9@=EdTdU+1&$a3FHqWKY5zYNRfZY_c(5b&waY05~v=-w?$HtB`hpVLsL`9 z*qFPer6o2l?rLP@x^F{60(Tw ztl*9vYgof*YHMp>zkhE~pslBO-z}Nqck?EPi>oX9M_F0frna`=Cr`xIF)~`7Iz>@d zR_5BYNo3ifb7v#`h^J7R5Ji+}JE4!azB3;)GvWnVf3e z9lZCqRPh zZ}_}9tfi%*sk!#s#S$yCUAuPS$!gxdT~}9E7f$`@e<;3v3KuAWoL_S-LWG<=nmD()>gL5moGaxIr){B zALQZT>Fw?H8+v!Urmc;`N^(UzR?=Y3xw+9u=JjbQ(+aPRXW!qc`uyyy^K`$eMuyJr z3tzYUN@nlg-05{CB_$eKTCcvAUSZ+n46$#2(%1Ri9{+}hQe0A! zYEpE;ZSL!p=^v$&H-*xSK9Lh3EWEb4xj8gCx-Q40h?$)|sGwjs|xpNxFj@6ZW&atibu(A@^%1TdBFf`;86BBc~awQ}p zg2A}x0^^k{S9YIkQOR43lr&(+!8ADh=g%KHdittl-{mkt^=JiwxR-C<9M(3vJl?^w zXU`s``al%{#nY#^zQ1=cLi+6c-k*bwJB5T4+n<<8o_@`|j!%xuz`#JMK6^HlE*Du9)?!aJSJuJe5u0(wix)3Web22F*td_% z+1YudBVVvChCik%U`_j|CAF%m>OSwky9+OT4PRQAQ#LW##P2h|<-ze}CUtf7)PYP> zMMbJ(clKRPSC1i&cKGmN2^ksH7`}r|IVOAO=jUJ6)~>cg5>U^Hzjpn)b6@r9I|r^X zrKYBaMnwh1#j(6_{T?ju{r6>05C6@ZH#Dy>W zBKZ&TUAS;TNj=LsDJiMrg{#P(^BsPrS0=6Ir}`52I`Vz_`ZXx}fu2q#TYWUI?mFE! zA3mrjh?zB&UU8iN^QWq&CjI?-ZRUoWko7Fw+~KjY%!^BNJ;*7+xw+zGL-5M!=RQ7A zQpZCm;30lfCus#EDOgSS*PhVTt(h79C~4Ecw%e{%W5ia|P)EoApy%&Xe|`>zhKE%*@QD=`mh>Z&TBW@vfrv zAxym0h~?hz-y`TZY~Q?jvtK=xbe&#sgn8Za@Kj$-;{Hq9v2c?1ZK7}Q9o&T6pj5B3 zU!eNxsx=Z5|U@LrD>Qi#_^km?Tk~H`*rO%yRCF|5nd2x9n{N^r`e!R<| z|C|hRJ2MAI@Sk79)_CFh#a|z^O-)5zvM;_Fytp{?@wn6F%XQ033)s=MTeoeqe1B)Z z=~SDJKpfrj@^Z1uC@VSgi;G@3;HR)88ixO(fN#(uaBR5l!v-66I)LmS5-xNJrz1oqNgPGZ{qr-?SY@+m8rf$#4gZCdk zWZ;dEG^lqzE^dD!nrFYiVgPMlJ-_d()=b@?Xx+0&0#)tpoat&&)p!G%#_5?+-AHbk zwU(BaM$hfoA7~{B7;!w{$*{;fEw%CTWOvlDyR!7WVUh;wNPygtH*T;MmzG*r2htl= zc$G9ckCjbLIU$(VR8>{Ett`#ySFGSLAw;*J(tG|MS6ui#p?~jcX0VDt6&8+#mDN9B z&HCyOAC8Y`U=c!bR5vrT{GFSO@%N{`BkLMozosn~WnkmVpAWuMla(uvXM{`;X*lC+ zURg;hDJ$baQ=fWETQalAuPk0V|M}_ag}F)fWO?tv=vQ1>)^1wj_RPP>KL;>yik)h_ zxl2>TZ9T7SV0XD^I`F{>L&Ffjfjf8ZNVrY)3KxwI46NekPv+J-`HXy<%SaOywkxCI z_eoRJNXwdFMP1zu5057s9InC{w=+68~VcdxAYQc+T}e{pF^-_^|~R7GI%XQMdRAs@M5#!bQV^KOb4CF;f`tg`Qk zh>30AyLYc~u~TZx@99SIN@fbTyw|?kx;h>bfm^dE6-A zuzh#s%JLDUVm5a6jB87~j0-{mj&{n(=x{pWqFzkM|!wx1reU%YfF{+cQs$%k0iLWiygtxI3Z z+(~76i5%RVYjG&!N0wZM@3uz{{I_!(+1c4wJ32Ztvs~z@U^Fo?p~QPuR%Cjoo<4IX z&GmcR{P?pEd8ewVQwOjxRQn^$39Z+yzr2XlXI@&X#zv&=*NeG;^N28 zoooYmo>QnO{%mEgaiygP9vr<*o2eHr zW8b!O$Z%-Mc$17>E1i8?cGX;uZ_L}bTL1m5n8(R-13kUK>pPC8`K~N$evL^;;G~>- z=||O3a%l%DnEA;af`Y3i%YHF2@$92e!kw?>7(e&d*yjSesIpuk&C6=)!MJ%6-X6u08hqm0|PZ?^^F zOL8UY?ET#X%Eynd$FUh{&D@}*q(n*4$#u|Q$Gw}5!nUwV__t5a1T7;s4In83NOfdq zWc~}K*ZJ&`LS3klbd;|pm((*C!lI+MA31WQwnKfxh7H^YE{kF-e#E9%pU4LjXmeK^6nVFXt>$CjlEVaTa z+I1&dnhycyC|dX~J#Xo>04O+VY%H9!6uxuv`LF5eEfgG7`D<@Z-?(;73Ha)X)gNHn z!=fCzfK)<)f&qMoe6;W++w+aIkPC7PmL9YhN|k8Z*x0a8T0VZ%eVg@mVb)=S;oQ$} zZ(1{saHd-0EQNN?3Dp@fB7V52QoejK&6pICmcGs*ZmxysIl)GsQF{h>+^W2ty?l_J zJy*oz$eYtr)CyMlMv})ZERrd)dcxZT1TytBDUV$h%~WS5x)SQ5d9w3Q{{1Ci9RB{EI6~PeC8Z-d`K9C%jo;td?{l4njh+r7*RyVziWz!k zdT6Wmc=G~uct`t-YqI^5jtmo)c#Ac zB+%sM>u&<10WVhpUe4Z1P2KYI*DrnfGtD1AZa~HlSD*ROU2bBtAXfg`exNQY_RZ<} ziJ~5g!swjgyLaz`Dk>^kI@>>fR7TI!(wY0%tn{*F@5`$i8X5$&SUNh!pnBB)MCU^x z(Adg+)Lq!@f!5q)&l*aBZFB$g?{t5Ba-y+cwzjqkX!*!VmGz4-H^`Ma*Fy917oUaA z7?M-TuS0)F1Suy*zjC+bTCB%^ycQ;knwp!d&?!i||2&;-QE8sJ;JrAX-|r<7aQ*rQ zD{JfcHXqe;(~hpr&i>rW%#N0u6vo98X9ySzsH3-mN#0vG61=m+qXVqAvI;9OTPRa zuxnGf=as;~z|Cm>SFc{Z@5&Dzq?gr4j~*4Zs4&gHFk0OD`n5*JqPyqf{B&&Nps=^r z_u2@~xcK;>3hxDB2mZUVuKwxif_oe~?-zLA7{&{~e*OBy7rP&WCJ&C@liPUZ$Cs;G z+iT(k)hUZByQ(OsP^k&z{NTGBfI2I%ZQJT!!yiI!-Qpg4f7c}a`112ik;!z zIu+y+DfIQReilktG!{O-Fa`yn$&YH)>$bL=yJch!o0;9s&d#=d70Tj*Hbv??koh%m z5F5F{=ET#W>MjejAYJC|c6+KO7t!Hc#@dT)dcuJ`<6m^Y)UfYX;ba zwe<8`JUthrzOUL0M!>G8;$TonNc;S>?{qEax4}XEfr*Oq=v+^qKd&|SsA`&5T;seuB3I{y4pqTSPdZMyIV3fz;<@$eLvhg7;yJ4?;gkQ z$SumjtwyDTTLfmlx35Px_wd+Voxi+7MiXpYT%iE9y`VL0+p?3IoVl!BX-G3+RP3|` zk4YGcPYWXDSifAZ{4I8d=d%JuubN}TuGolT=q3c|_+eY)~ zQPtaf93(D&(;&EC`16jegQOawzp?@hrK6)`mSwWeX6lo~&yZ$}bjv*}%Z4L6HP3=< zqRgX*WG}KWy*$Bkv2?H`<>OkvB&?+O{Q<4TwB^dK z%Ip*|e4>Kd+l=?g=qW9#xTcxGwdXt+;Q+=vB&*h%_x@4lcw$yci@mDVt)2JuT5_J~ zlyP%+@B46Qreqmgi0CgaqV(b(x6VFZ;Q3QIu zs<^!q!r2);x%-(-zszBKVMeFVOQtZ5n_rPV9%a`|n7&6M@TX~$CdKJi;&+v)q z)!<;-Zuenctwf1bHOF^#H;xO(Tj4O1z7esI=lb0sFeoVAJ&)EpQlFi?L`!SyE4)NL zfU4H5-Si|FK0Z7i6s<23f8)lfPmlGde*X?e0H8192aU+Un`O$!$48nvoT95>3sBpr zv4y&$A4iP_NvA&85RcZb9tTVAqag+#m zA$H#`3^`O_=T1718pz;@a_&+U^Zg6}p8vY+^`D}8%p--gk)WJ^4IKr};RGS4f~@Ma z^w%}jnqE^Nt~WxwLIHsWX4eUnBlhs&d$zKdzqYK$>q%N!S>;*RGs*cZavk)TQ9uip ze{oO~tS~)qzM`9S724=LuRj;X+%{Tk)*LL4EZ+>m%ff4V z^%K()e-=TtUeF@`rKP1!`FcZ$6Qk10yO1YW#c2d0(_3S6T_1ONU+*e%6vTU>`l=o~ z#`NOKWVD|jg+N{~)=KqRMd)YI%Ld`c`oXoK7^vY;OIlXbpnZMVT~^6u{hScz<$Fx&0er+Yim!<&DBS4lx})%Z@^A1o7j2xLTw^BofFcxBlL ziAe}62|`$Ls|14Gvc}0McIuSND+4XdESOo+GoJL!=;qiL$u)qL>K*rNAE-r#$6B(&F!eu?K zO87UoywFdQ314&`zwTu`LQjud%9UAcn zoVYDmnU&=QaU`;o^z;BA?dq1nY;^r1!yV#avLe}jn)DE!Bfzcnhtl0r;t!2L9iT8Q_7DeiOv{NqmpeHRQ@99$| z&|H1Jy%aEsiAhOS)z#F9Gh1{GM9JZd8pTE)Il_Qtf8EePO#$#x6pjZIs62iB=FMP` zCjwixC~DtFLW8bU{q7wjkt|T9$(9wI|E!butA$z4^Y=Q$V6p3Wj{bpxMz)y!`}c>> zY8^d_(#gcYzyLm`>dO}%1cZc?ls}pi(CDX7$kEn9HBtso^YW#FRlCy9K3ECB>Sq!%Ji+1PA>B!sgY!+%K4!$Vd_M<@2yt#Ghect*+x$6sgc z?Xz~a@j{L1@23W515}hbbXuw|@tRX}V2o#NU)YqnuA8W=xKbx*uf9MoYgp3tpx@0fHeyDcacy>*(sT z@bj+^Qz7g%9+HiNqiud!Re&WjD$3Hwx}ySLs| zx^7Hd%wyvQ4QJ>4wDvvX;_E+u{!FMF$gTtkv zw@DdX6T-FK790u4(bh&5`0#cu24Qq`km-h7KzhPPzm=vQUv-!zaSMMe(>PI zT9IaF5uz3y}dv25$#cj1ZfNyp6cdimSms5LN~=N#9VlO(*okvA`+Q5ZDIhn zA>@lud3{WkWj5bA^d+S0#i!}l^T`3Xc}9(opBFVRCRwoc%-%7DNH` z%@gzTwfTt)Jb1?Vd0jl?`Py*yV&4^cG_m3?H)zF0Mc1iE^Hg2w@vbM&NAE{oc^Eqs zfSpEuSDb!i{lHj-27Mn6Ji&`f^_IxiVWpe6vW6>`|Js9xLM781*~!?3PSH#Mc@rz=SwbY zYCzU}+WS`~yETp+A!Iiee7nmXj~H=*(~<`??fBZR@(ojo7PTuvqvyb^#@{{HYHg5@8QV zOtP+H1tD*=YHLCxN!myReUNvQ>N5>5Pjm*Ym!GE?=9;T^joiGo_kVc-3LS!?Yf!L! z!J}kfm319sLn9uxVaKr)d=!6^6N#yJd^{4l!x}977Xp2a z4Z2$dKt)11le-G6=Xwpfp&o17lhDFxe;kdwXupyLU%N_PHLb^jRVaG|MnoNpz@!22eH% zFCUqD|2_?xScyG*>fT)60ZHQF9XWSaG@jL&x(^Be@ZWDU;@zz&e&{XJp@0%f7lh?j zJZUQQT!eZMl=0=i-^Ks?^s40pJl5+dO(mDkMsi6~BZ(P?$AAxc73((EQG1-i<-?%<_vL+s$vH7dEd4P8$DW6 zP`wEjK4BnAtluU|N&q>?>f}5>{f3qD(syN{$M9ykDV=$loA$OO^M}F@fUT)?E(bJWB{XR|qUJjI>T|ZXv5@6aeZR-gB8k6gYKm=b zZGEH~!Qrwv@9gn+Mri+m0~;uIa9{w>hsrO_=n_&wAPxSJ<~+-q*RNg$LYdjPY18$P zkdU-@*RHYt1)Zaes(e2y>j*+=OQxRjvmM{IVUGpSm_nFvG@V4cX-!jQpjOD>FJcV~ zG=6TUO3?rjV`^r0*w8QmqMj|J+HXI89Q*tCFYv4Sro9*R3iOjMnZ2m|`P)KRO6nTz zIxYn$`&&68G9m4J8y)@AuxE61v=u^nt&6*ZWSx=O%Vg8Yd_(9Uu|-9DT_-wcbH3l? zP=wgO_4=EgVq($YrqwAUK&m!vywYdZp%rUFJ5Nvf+}U~aochtDQIIDOZ`yNyvxS9) zfWXiHCw96NJa#;#DV$xz7Vw3}k3-DVNN_k+ZVeL;8YC4YrmU$wgY4lMzd%uGK=0o| zIc;mJfxgUTdC{F(p{=cL@WQXcOG`hO&F@tJB9D%YoIuw!ig?%4(^J92wxTawTT}&x zgwYq4mj-qg7Q*cO{_%C8Zni-r7|B|d@(77!@B+jTdP z-FgE3GZomqq9R`Nc);DKMMV#_)7A9tO82sttK{B``T73C_fDZn)5@Gvyr3SaQqZZ~ zJZF#N!G0k?vTlsK0w;0&Ir3uwVu480P?iRt=z7>^a~{$JoFbJUasjHnfoHZZ<%K$vJn z^L4s_LaE)%W8=dkBUNA@ICcK~I@j6oo_%M4sk!z!Z3fDb@R+ehlP)MHXPRF&bAcMi zE&OtZ&bf@A2E=f2Q_L)U|D_?l$G!T6Ki|)G5ll)sWo4CiLkoFg6Uh^vyX@`l-`+jI ziCt5MDi>5FGU4nsU9}o#U(wBNpAWcbP4*062a;i(R+ha?wxSC?)@fN>Q@!zjtW9OF_CZ{1D_l|wa$~6<0mfk?xIPZl$Kuqhw z_IRN*gb%{(LGgpffD>d&=F~FueNwL!wqNfpsuRm32kmtZH77o4o480TFa+T82}+C+}zxf zX8i*K-n@M2N90loiP+A9bIO$0EiKo8m>(A(@cWPBHOV)F_ux%+wLi&g8#l&PRv!9V z>07DQg+rMEJv0vdG%`dBcpCfhjsSmu4Q1s32%2W24!|>n9=3^gKnwh>zdtRpz0Cd6 z-@Xw31}!YnT===OXU}RLIU<-=l>9wmZtiCc`OS{}fY4C1#&7TrGqbaVkTx!`Rm47v z?fr!SBF~DpisWX9{N>ySDkCKy2z$-Z))@s6RS5O8qr&?DJQ$gY;;dmH7l{SplAJ+HpcDXoWi=!~p&k$-Qt z-Rz%#AD>P%$1fp-*MZta*Rr_Tg)Jij#xPh2@}M9ZYfr&ln8inGHv5@#=fX&(BDA5Y zDGxGxh7sr*>GR!XCtteA*kjSr@xBBb5ExAzrlOPkK?cy9dcEVRNd0g<;UK6p9&2i9Lc)9)#dR3i}74vT1?6xUvf1N$bCUT)yhv?Y-#Eg!<{Q{){pK)Dluybn4dvg$T` z>m`s7=f4!QKu9;mTN)iC3{}4H_nyi_XwRI{N`%(BE*vN@6;nt4x3seh2f_Cbm1J4E zE)=m@Yn=f2;JwgY76um&18+vBL9Tfesof?;$Dn6P*4-em-ujais3@Ynh>0;F^neMk z0S8pWb43g!s46?5(sNmBzC=r&ZH(>|+-)c^y>yqklMjPt9YFs%4=MZm`M&qSn(y4) zR^g+!cAxVHGiZt4LgV;x;KvMJkTkHmy~6I1CMDk^^4|m81P2IKj)sQj6!Ja3GY@J; z@rxJL_4R8GTplOnd8Fhvf*_5akADAd-JGU6Kih7B!@`?7px2S68W9U22rYMNI?$u8 z6o~N1Poe$&R)p9&cI?>vuMdYBMFj;;pFaH(V*6kTFz@Ds#?;(gi!(lYdTbCX^Wm}p zrFwX<1duk91NgJfE)da1F#32;B|i>AjFk@~2wV(B!gWlq zxVX3%%&;2rCbC8#1n$;9A$EA7EH2m7RgjVs94r3N!Xp<9*aRt7Q~d;Rkw&Z>5)@!^ zpw@O}7$2ejlAe}0Ye4<_pSD84dVHy^;>u!y3+vG0)GByGAOTXBm`0Ezt4D9`Rzy`MvE+)a->#qna^~jR z>*eKVPnwzWVj~Dq1YnkuriY&k{C8M8(PKoSmnJqLl*hp*04XdRH)gh&g51H9iC#$p z1rJ(>SPG%Q>1X`geLfIo`P>8+0E1XTaAXcs8GLeS;kQHlneebM#lwf+i2Ra)9}?zR zx;I_djNxR6<1A`aLy$Uw&pr9;D9v#EOB5A@Rs(@}w|7MV6 zU1A3ze54?9PC@4u|DF&9swm#3`y=*`CRb1DB0TX1pT6a4RFY4g1n%X z5K(E7SlD2P4R@}%b?cUJX(Gfz&sfb2o1vrdcY&r0Mw!%y`JLe_Ypjri}K5h?02n z`VDjDOn^rRT^^+QyAj;PWmXmzdR~7?gB4~LV(GqjFB-0`ty{OALXSuB!?v=)3;g=c z8%4+=Kx{y%C-8nlmaN!#n|n7NWMr&H3s_qa9nDnm{CR3t76Y+ib8)?FYGMZ72Gv19 z2SBPhHj8~|Xb5bee@V$7dq-;|sO)7>0FnkaD4d}&G3r1{{X2w&=!wnz@L{1#!!*DR zAPk8=jJRfkf`Zac$Vf>1>{+moYk=Zvs;a0UXA)ETc?Sn~IvUVV%AlX>x`okp z;kXg~9R|7!Uta`0dbAU&1eKe4(@l=JjG+7Iq|?rR0^zOwmV=XX06#=}MY|z{CDf5s zZswv7|38?VGu;tBLY(9*Oa1?JC$}uD?ds@w2~#q(tU!2@kTCyb;3$O zlvKb07%4^so0DMTP9f$zNSSE)RB`;l9-l&;<2&e)0`7ZidipBZ>i*>y+1cN|ohA%A z;Da(ghvI5^`g9-`JN0<7JfxHm7}}a7u^{E}aMW?r!HZ>GAN@}y1Va{^oE(Fcn*t0q z_3KwaNQnAf{a+&}w6bp9h|6tDXvP;>(5zMT@V2<_9s9N{%T}vW|w3-LtVUDx&jZ z;o*sdx&|wfxwZ>jVj3fEjKL`Jcq@>rf%xfaxc*QD>WH%TSYIS{0O=s$-&bhBF-yH_ z6%mRsN8klV8gc%07CMNy6wr$A+QmpDDhRdUw25$xg!2-%r4idm$+pdeS2{Qa!JD{X zq3sZVF9bHpb1hpDfpol4kf;9NO|o@kG8J|~`LbGK2?Jnb{J~KCp6rQX7ctn35CkjI z=U;S_HB5!5S~vkt{jcaDtk9m0&fcVnM=KkE0H5Q5o#F)h1qE;?=3D ztyP_NvnAwxSJ$N&N$bd^^@z+*ESOuskyC~uK&+#9KL*|~p|Jncvzq(%C)kbu8E}D2 zPzTM78N(DfC>S&l#25jZ4?MmCP%`oH5T`n(0#XD~{rdp9jd%$mhOki(+!y z*Hlv>rsaPgMnD|M)|pd<5U0@i7rRXhAf$<61y!r&(t zR_c2^-(;7M9=xu7;I68`kVrzLgonY<%>--d=HX%0a6@RH+tg-)pa@InyRuBsVDnR( zBl8Oj18|3E%25y6cNOk}Ru~kmy<_}qDJLEzw=Bfl%d2-_K=;g7ARp2~Lhu&?1Hf+G zXt6grUp4?rY$wppKEq7kJKHNVRSi5??tqF)?uz`B(JWd$2Ne z6yAd#H}K7G>g$K2er?PWu^V=juH#~1`C9B802yrrOt}Ae!F9L1cTVsrLv<3IQ z0?2P);GkcOA(tqg{W>FDu9~XL$%Pol@%o(g+N*ACE#JpRqg{KPdKIASSwUFD@6UWl zc|L7zwY>Nz+~hgTD@;iIb^fav%ugQ27zk=GYN}?2_;S|K`bt=hB6$yRfNZx#v_(C? z4S|ewiU7rRAPuMAADC=4>S|C^fi_wLyC@m`a=9ZM*EmRYBN#9w?OJzcOdc2I2)K8} zP2b~g`1wX7hdOIW7a5?pL*OSkg&t5zT}Tk62M1amTn2n|w4jYJb5QtJcE_iLnIqKc42$XFVrEt=J<%UjNnj_>qo8n6s-`}SjnXemU(MaY3hB7+0q zuZTKkXlOXzU0&Mq8QL9@Zy_Y;dvuNQ2(t9S6BGoi_{_}ARI@sNDujFRTf|z8QwUdD z?43K2&2sXB~4?W+k%IN*ya==g4`@Y2r*135nyrj8~nQ{c&XxQ_!&7MY}h$K+5G zbVU&3SeP;y!7OXN?xMF`S+U3~*HOh&HlBE&tb{%jv zh}u?Y{|&ESr{GZQVl#<>*T|?t#Nq$22@jZDBD6Ay*oTQ1+Mn>E0rvn-75T2Fu1wa1S4X2ONCDA3tvQU7v)lXUn;rVH zLy!3eUfMO{6@c}x~cX#$*$E;Ec*P3OJI;<39m{Hg{I9lLz(xTYH zBhG%IG!29&p@g6g2yNdUjKr1k@L}c8dt#FZmtV|zyDs~>echk=dfH%6=JCjIY-#C& zNKHDdBbwke47A!??W+R~J>}nRw6;MY-b5=YVjKL^=MAWOW{T#qV`5is!)?h*0ja6? z7EButRf^BjUoiVgT51#xQI1NPb~q+R!8ICai5unlQJOF<(_nV!|7Lg|FDmGG-Pq_k z;bUjgynrJZ2YREWb7knZ6*0{>T-qyOrH5A8Gnh!6$HSd46EMFgk^2W#F-CQnVzK;^RSvBVjq7FHNh| zTUuHY0<|mYjoy-40a(xR>u<1`Gw_Fk9221IcK`XU{fS9ecsDQ|E2Z}9HoHzN7l6YN z$)!T-Oo8Et0$nJ+A|8EN{fLSOU<(Y+bv@=uhu`3zm0YzsBR&0BokM6Sgl{^^;PCJU z7VQfc#He=uQXO(|FuZ~2rbYcJ?@k@IVmy z!h%_wFvnv3{Kma|<~clj3+>y*2*d$<3s2SV@remEj!%s4#mBGi{Znpr1Y}IYNdsOG z3(!B|IMI)+7b*W>tD*7Qq<$POU@{Jf%(co-IBFZaCLRri37(M5g5Ob*k&i#AVY2dt z%P2KE(MMTXhPIoKbSVlTY~p|KcbhteQO6X`E(6uk@~gA6v+VUd<28F;m_gm6P@u_x z@2GLCT%x4_Q|4ao68A*iqO^{v#x$JJtzUQ=?!yF?c%9h`mnr~3V(5XFE)`f321H#C zwFe%=8*l{Erv0#SljaP3IapV84nffw^Qzhn7Z8@6km5Eb8b z%X~58#NQ+7GyU;nfP~7xTcSG{C{jI&TM=38cWRwo|eLPkr^ zO+c$6!r~`8qRG*%Utf=xFzBl$Z4(8}jd6xIjtK*}?Ch38eNu^f|I~1sd#$f{2OS@R z|5bdR0|p_CLG9lT#v7`!Vd+G>&D#$ja%T!3>yejl@M7|w?+c;a^G#g=_a%vf6o2~k zW5fL1zR3I{=wNQY$2Vd`b}Q=}krlJk16<==D7tVV}k53c4C%unTQHY<2 zH;TwbpzdM0%DH%;!Qg+^XEn+$fFv9R>rwB-EGn|EMQTwV^J`ZbO>B?X97VZu!B{su z)u{koO|UJGAX{Z%w6huI)fU49z;j1_{P@vYa89>&wXpaKN|qhmBLpfwe7IwFq&a|^ zn%Wi_em4jU;}NuA7sV7l`vi2I{xSZ*XIAIF)OSyu2bItR`iDf=U+aC!<# zMAqAjzya`%rvrm@=3B3!O2HqA+)OZ+`C8;?QLFzE#&^s=KbIPC(hkcH6u;==A}Zbe zL=XHoRc`kZ6(w(9oa6uU0{r>=w*@7A=Z+mAa3o=C$!O9!JG+W$F(&pm>B1lqp=pAU z9E-QXpR)1rXu}3 z1qB5oIH}>Y=sQg^*Bpmy*v#44+NLn4LGnUJ-c`zpmF1;Th@C`ZgprxkfFW^eIdk8svyJJ2@c21vrpU4Vb6%-Q>eDEJJi8PcN3_6+nJH(RxIII zQ|c-rx|)ZaFM}vt9w~l-$kzI z6Nvy%%sRFBTLr?OuBb_wTYmpJ?Tkj>KZBeC0>*HvFmg%wfrmP*t9u>&&6|dXaJ5LT z>CT76@LLOs(NHUZJVOI+nDfh+7m2?W4r^{%7u%#iNMJL7VImjnYHRf$o1w6S;oOR? zSW8EDqHOOrR(i|~?Cf*^&$TpFv)<-`#g|+(`@l;qEZU$JFijMT0fN=Ev^p^Aq9Wtd zVn!Q)${@t(7#i+CiXUsqXcP(3z_UYdDzC1`VCp0A$?V;Qvh^A&1ummHc5T@kQ6}PY zCRXyV^jK=&ndtJ%-Q_}aqnl<_VCOq(VUFD#UixgU%tyxaw^@iv-ZaE@7F!d!HdsZ* zcQA*Q`*o~VQPrc-V^h2@T>i|-*LU99Lc4*wbY1CNcOISwsRNhZAxD&dkoy%$zssWH zPIra(5u74+`G?RnpI^An!^*0J%JFA}D?KGe0WP#^hZF5(x1X7TJ16qoyq7#!#%9PE zfkAtxepFY@Jwpa5Gw{LI-a6>Hr}ovW)<4BN-MPEAGv+uRu}BY&R_2%WS=>jp+1GbP zc10|@ofwe#*`KA!*Rihu9erLj6CoQ3#_xaZfZzp)M;EF&fDKVMaQ6eGZJ*YMIbcl4 zT@|>x0xeG}I8*SNBO@cNf6ZVtLt(HQ4f$IEOwtFZA3CLg=g(!JI@&D}KkUP80+BrX zS+QNHT&rU0H`sIBbO8ZT1Kb81$tsw+1Yt(3T>dLUrVXAxm7=4erYOMWm-B>o7MYL=p-N~})z{Y#_-F%><;@#AZ9gThex+>*nE2Rer`yob5DO_C%TeYryB16R zeEI0{jUrca-O@b3ZqNM8n?AZ}r!W;oytW)dPW>ZPKp zdbM$It#~Y@+w8REnKQJINQ{h)`wy-BSx;0k-~vkoeRb)M{LT=xs2IX$CzVK8SXjcb zTdq%kWpZxS@Wm1g6+OBlk02rh2{LgAF;YE!%1X?Is9KB|8w$&&q3f0y_;8lcK_#A@t*WV)@QDH`>XN%QB7~Q~S?? z83zWvwN($Y6@edcm{eO{rucmrCL@;M!{O!(z|0fGSNc_}R@vIyw+q`PiJiV(4Mu~9 zX^qO(^QoQ|=H`4bvXQ-=`s1Z%V379W1vlnQl)wgHqKGh>wAY>)ccHoO!`&0)`Tz+D zX1oRQfw-QOdmpgYbd2;KHWa|+G{fT zf>yiW@1<>-m{55A_U$VeqzP+=@<1kfjZh0Ilj9>3pIj)~StV#OSa0N3d#80)_}IwU zSlZ*qnr^pnkl9g^ot&K&(Xhg$9c%pctT8T5;e$&ksAdY!Nam6f<-KJE-4mzAKTDIb zH&{EA(ZXX@U}$Xu4M#3pfZTUVRv=>A$BwjwZBG%!#K!@Oqgtu(LGe+{{4+V%4*w!- zH5Ro7_phMqtWGBX+|g0o-RIF6fErniLB#u;WUzzSWoz{QAkEb<&x2w|bYGOFzH{0) z$045i<9-AuE^%$=5>o{2DO9Sw!opyr0O*z-KE0wG5wpJJN%YLjqTt&h_wb0DmnlFj znHt6!s8(FWB#9k+zRb#HL>8!vV00S`iVOEJjrBj4aN?OHKoR!oerVvcOip5uRG2~n zY#MJ`qEbJv*ox~8`aT{%q>9o@EEboBv4_;>I!ebzF&mSC>Pdx=?O7?efw zOAGUjL~j;YMRZ?&@c8jsaf^z@M=3c%F80RuklIdJT2e_%KQk62^CCrKQLOj1;c~|4 zPcR6rK4NfgbIllGSP~4~U3fwU@14D#tg7(RiVQAm` zK`}x?V8C@RTlDvyJoTv%blcnHL;UE8cHKID^r|S{45@<5PJ=A)0}}=kg~`FeL2}y} zJnv`?N4eBv#6LtPJOL3x&_KXB9fONjERmZtwuzoWdM37Uqz3HB?uE-K6WTsL@=9i5$N*oC};g45&v94Duy`jqb+^yI+Z6bk66Lh0bX^M|z``_eZww7ty2 z(+u3IB@9u$W`?xLFT~6X$K6r+T}M?w7E2i97zZJQtwF|+3e(k7#E5B;S7d?-Bpp#$ zfavRix2lGJ<-^Pf(l?-{FLZYSs?b#w0V!!|Dhg&io>!crn*2L($YX{MbG`KdX5?Zf zu%Q8=DOp)2SgV7QJwrof z1V0&r=zK1MYe3c>IB8Gi!$e8#t_zU-NO>td|Jfg%PZdTs06c0_6azZsD;-|p{m2H6cRpvJzDgl) z3*gGg%X`=O<6T%*!QLt(g9HffaoWd2#`fak;@oD(*vTXugp#;mfyNIXI612D*5uBP zdvfmnV`FxN&BW;S^R<>ZiI)#yz+5M4mq%Ahc=%e3{jNQJyp62{tJeGL4}7DFnCJ-1 zDT0(008&sdN?(K#qS3zr7Tmmpp%1pgp=fx?kmm!hU)PNY_ag&Qm}7;vEoEM5;Opuz zBb2}gel|2Vl35(`ei)=DhFqBOC483`X(K+8orgsRjd5;K zts!&~JTq)%emFt!Bd=StCgjZeSwj@k|FjPVUp&_qX1bIoPjHz~y>V?GmR5^N)tJwXecp02X*1#;KHWj}fF% zNtmA3@ynZjQ_4psBc^!lCp>)CsK+HR!c5s*21_=Ar@b99rrxFp#;>bC2 z?wk<(nmfliNGFR&>62`Lwu~TRT?W|6P;InE;yG*?(QUAmnQjVooeU~LID)0%C5{Re z1-#4iaFaD_)?nH5a(3q6+t<*oU8{t*LolBLYYf-^|J;M5v%)RsyUYVtxE8s73fxpL zk|}0Xs{E){k=X{oy|x3IG)G*}2om|56nBWcWXub4XAq1mxaX-SP5U^UeSHH10f5!U zUwL6uAQqEeGtNguM8z}#nPvrLj|xl!wucPIflq)Ni@7n#9?6!V3BVLGcm$|MeA77i z=!u?(_nMb`9R8Z1$`P^cc1N0_KQ6SvnABB*>D1KvK`FpZ8w^qaH}C|&_)r5%06ILN zk+tlF(uW1RRDQZkpEn*>mIQgqJ{?%N`?0*EgpXG)CcCR}LlM5p?G$9yw0*|2JXeXytEZzmz$lhT;ViHUV#mONO~{@!8AY| zkI=j+6j(T(ho6E+3WQ^*O$n0pHsQh%_6=7R{Kx|1vInD)3b$!$lseqsHquoj4!(4? zp4gMKwtdtcZD+CjukEA`g4q;6ae4gsG4bcYy9vVT_4coP zCTu8P)dIysC{b%*7{R@CMBxky%6tC&d26!1WxkQaCX<;xy6*1#L8e5ZRqaJxfXzV3 z*;x|%)co)`Kk<=a$_q^#KO_GuyMs8$M29C*GWvHi#syx_4=!z7qL3K9g9TOtcJr<5 zn4W}o5|?a(D?;R?wRL+Tmm%xYljX%^GpT)^o}N&1As!q0+gC?IQY4es;EmiC=Ov-F z29ljYGldq%$;Cwl7X>xpab_la_<_Hx!KvZsT*bJAC1?fK75~`SSU)re{a7G^NZaJh zE4;$M{`aDdBbpn&<`nL&02c|-egm%kVLcgx+Cc^YF^-`K8yi9AIJ}@zYhd2PiB!uq zFDDn5{a(ivQOhcV1EZ~~x;iZ_jm#kt`z9g@;$;wal?YYf_MheyJE$Nk?_I|3ges`ybT%X;C50g^2;c~znz>E)2Tk?hfBT0*lG`pi1$rU#lc3`N2X{}PG#l#NXgD0*CxcO>n*f{ImFLSsV%nGq&;!eK#F5$NDNBwa>M5C^QnT10xBUeCdqr{sgdX#>xgdJt=qQ?;I08Q zu7uykbx7!+C4eaj6^?vu<@ly6nAn=o8(_p)0Z5eGBL#`F28A4_SgZc}Bho~o1Ely> zt}L5;JMcor5cD@W6_BNf#pii=Jt)0C7dW?YUC32(^W?g97~xz6aETpM#^@lfGGgW! z1qaaUw2#2Syr8)Hfln{OG*KeZAqQ3t4-coKodZ?36XUcfJ`kO8n@S8kXW*2!!0>_i zf%Q@yGy*R5+ssTyDckh3@zpC8qwL!lodm^=IYl4rYPKS0Zl;s<5KXiy6QCExeN322UeXJ1@#Gn24-Mggd z9nPo)80M)%$dIY&MHs^)ErgPx4&T4O^?yteKq=_WnK|yjdqJdB4B<@O?C5{h=R~wr z85tQ^-RL45z!;#(Q^NEimKB$aUWKUdCU>txnzReJUhXAQ5t#_bts^`LI4g@|_E4*8 zaH)b5W>QSc+=yDjC2@*^#aKh2#h-q0W;%i^QIN=YppN4?{NUCAOWg#1kz79kdr0Ku ziYQ0cgx#E~BTPDE5*w`P0Aih)j)utDv0IcSyyl(u9XRle)3AUHiV&*|sxO5CyC9kO z2KxDO!8{j^I&yyjLXlh=F^zsenm6+>FlG?$#Gtsf2MNG{qOx_7)9uPw>7z#)^E~eE ze~iuc*ck_*32MfO3V5LBNe1319S~5hK{A6psRdHo+D$Ef4#s$L`3iwS2yHU*L~b)6 zE{qFbiVe!WopDl#hYb`?KlTHoZxoi^-shv8tnassw?j12C&6`zg7hJ)>#8iiF z9~w3TQneN?g8?aPH_OqE#+tlCwbDF*!%lJW5J-`E+b{;XO!S-5KUQz8^6l*Rsyd<# zf~^X7UW6ieNsmtKSNIHWIqe=7NjZ5f0k^o~Qb1lJ7=f=QAj1onu2@xi=k9*=3-`6E>oXe{pf^;NJaVyPH%8zFjG zi$`1McQRbFL=RVBSwo8noE;Q$LmTV`sh~?9KY4OSmM@D1M-Lj$Zk-b+HiPzsfWnJ5 zRuWhHp*h%%krn(w3U*VP_u5Ikfi-dJWbH()zhf) z4cG6&=a*J*B$Dh$-yyO=GLza31(0|Sz*N1$4Tz_Qj+s1-40ZbPW&ioELPm5Ia3cKc znk+1OE_=uS1~Qh!!;{-(0qjscagv?_s}MUMQrYth8QcsbDVUwY$Iw4n@mRwxD_rezi^oyW@ zhzpSX28b|K5R4n&y|K!F7220(A_|TWVC%@t=d;Pc%B&EAn$ISCs{s@M*C$5Tvnpbj zdF9+{p*5nVIg;Q;d z9Cx{VH4ogy*|2+Ro~3)C}^s5oSZv)`uc})Jr)8?4FJMY_ExOI<`h)j z7SwYUkOez%vDv>Vu>xEmN$&fBz%0#sz1HfvpB`s9U=%6=EGq`cv5@ZX!oQ|Ws5W(L zLJ>qRFaA0%wRi6h`Q+me?hx~WI2;>c{@;oZ{hXc60MLCfu)7PxokvW?=FahU{QhX93070@C;x=KAr|DdRPbL?jL3K_?N-oq6s=X$qb8@D;^hyjFsYBho|K!Or;HvwGW$FKmt@n=W zdH?_aUmWKg^VlmgQg$e1lo6+8M1`aXWi(VGN`uUhP-YRSq@;)jrEFP6Dp8b8k&F_B zzSq0==l#2UF5mC({m=XTcAR>>p3ld)-*4-#98l1RLR7v9n$;-4eo6lrz2?`!GE3)M zSQa<)^rCsyp^{wcTu02+;-n`bGJ-&ZlNa5UzW$y)2kmqKV%@Jl#?+RdP1Q?0= zp!cop?1efOVU+&=a>E_P0w7d-gK*C4AcSH+A!m1og=M?zgSs@1?e7JGbmPjE^$Jt^ z?;EA1hq=&0wqJ8x$HS_ncbGsT9JHA|3#X#?c~Tm{)YEw^9YXHSG|&r{)HtXSy~ z^ZnVE?^wH&J_9;|cRFyyv{P1AFPJ-h*1%Z>uLEq>>gnMQ?@2q-P)~BWfsVz2UacCx<-7Lb-RqDf$isR`q1-6E#HEG8nEX3jCO6#k zhee(~9h_NPKH&M%5wZl5(=W${hx@IrE$c)6uq>QVPDb=TLzLspsCwLykG>y5E%Fh!Zr{F0r$gKp$~1d~@Nxj+1|cOy z#p`FSc3xsp6j(_pz-}y;=g*Gm{68ga$94^3v`0PiAIo2@qBzo2PR;&~CU^oL-N(mg z!7|G;ascPij^4zTN;+m4$uSY2z0r1C-g8$cVI=2b=*0r%7r%V*g7ZMx_TdB9uYA< z<cIZICmIBMC#E60u4*Z{Oa9??!gv$`6kx@J}XZabxTmHskm1;p}dr2<$dGG-K=B zi3s*{QPMO~@J=+w^v%!`S2jN&j}B)4c9quraTS^sbK-j(lYf2o6cKWlyiY}<(=LAy z-z6k8)ZE8sRGwQW%pc>CC99!^D<}Sin%B60`f6^!T*^emjsC3n&8%D zI!t<;$Pm}N@!(Rb9SgJs4i7QqulbAs(2(e)p|eNsJA_itBi5B&5mM*}Xr zknJiO9GCLqS*yLCWcnVUWEh8zvytBGtQhB5;x5Xu?7)kgfCkwc>kL>ZwA5mqbM+*<1{QlO}&%47m<2pJRsq8fiWalGWN&U zk`lo6dz6k73=B5XQ!GgHY?}(#7Qkoai*GLL+lGEny`df#;^i!E*S9;y{t8D^F?9cA zo!xu)1|z-iH()>>okqy^D`z>Y&FS!rImAgkn=HVWk)zxtvc&=saJ6gKPWB;^TVt@t z@U7Z|%h6H%XGXx*rqM4FEj8CCMhqv~5)Ns?>_PkJ@(UMbEn1*GaKwoH)N;l%X6!+A zuK()QtI0;eN?>!cgRL&C9-U{V>hkRJ#syAJx80k^_GWvN&D4X#9{2G)oqt|5YS`c> zTD~YY5#c|(LlTQX9UVXcw4m(Sicyh~|3Uye6+W@H9OyPb>g{Mma2Swxo;$ZHee9e* z@u+(zvPsmaBRFs!(lhQwpMw*EFgqYS?$`TGdOdmoiLNwzoVp1pzYW&~ziEi_Q4`!B z$EfIb@k;N6X{=&fJ3l@fL@%+?LwPhH4=-Vea^SV#iSbrdd)c*YD7j#9331wlYj?cc zX8q^S%j8=^c$#w(KU|%jjf`M2z|za7PxYvgCuaqnCHuRfz!7#jTkl9Rl-r1|wMgIg zZ@=Fg(o_T=@+akSC!;dculTd=<@)YDdA}Zqh&ZI>4uU3#js%H4TClZ*!byqF0@Xks zq==PM;y{fwIxNiNd%#c}xY0CgF5R~xmrS1}wfo?~JMOzJqyd=5BG|vZ=kj4LkBz(j z;}1t2vi<%dygb1y&Q+eCH{M^HM-yC)2d?8K7d$&m(vLTim+-hdw@i-P~~=B>EA!EZfAQuK%=dt*T13ESJ})QKOcA`#7IA zZ<3?gKn>3a$Og5?^XAKvHY$PkS9;MxJ+lvSg1244jXkt4y5(B_YEGZ zY!L1H{nfsQ1ofi-l;nGES`)V4S~NDSJ-1KK4dCyhdrg>43$^;&v%e*xo0dkb&g5+` zjRx0s_K!NwCa}MM?_Lj({FR*s^uwrN2qh?ApZ0`@(S(~rg?GVC&%4m3DvvsG;!cTA zM0>d$q=e)s38$csev#Cf2%8?fEj+!g^USOYxc}wdH}Zp*n26eEvJp?CQG#mIGVXto z@q?0$K&DW9d4Sqx$tPY*-;cCP=sr->AGL_5P*vfa8DdO{-74J z7q+$WuGz%GZBD`$7rVGXxB{+@^xiA?>Uzuq6awrNu(~e7(bYTXyG0+BgYWbe{QVC9N?) zdqWz7=*Xl26rAFWH8V9;kS0jYt)U!i^6>P+W^_%bJU`j;cXZ%Rxk`Fr4wVH7px%;D zGFsZuGW&rRac!M)>wbJ+E>>W;gthC}uOG2EJ9sCQ+YuPtr_0VsnhL0Wmb+@mbMoz2 zJ*WA591WlW&Vqt(B6}E%&a5!V&?C zirES#^LLC{UERNT`gqSeG0fz|lP)|L^1C)ezI^l?<(9oyttWbbt9T|PUk9VbKIf2* zXXvZscX1eAC!K=+Q1O<^_j%~TT>!kngpfw&;V#|}^Uy?* zaiKP1%-jwV2D0=KlZ4S+P+Hio{HuWA7X zTkX4jl=VH#J0y@JVXiStVgJpC5BH0)JMmRAgiRv~pkRRW{L+HJ(T7gy_e|Ma!l55= z;zV;uhgMndL+|`Y#KD?XF`4^C{lcQ^1GPR5Gr$43{UvTteAK*O>rr=Grm9UVDjFl~ zjUKQec#3Vv;UrP@U_-^W(%{cOHyR#u9Zj9?vG)X)Os8Y5fnR{SDd$Wc(OP6PT0PEMl^O+ zSQsWQ_*FS+!i1hcY+D8!<-a2SB@Yda!J)%RAI9E~Kw=24!5!-;RPrOfXP)d=cMmSBu|i~lB@R~;OYW!4t!Fxfye2A&J>_Sn-A{QkD}K^I z@9PAE{0p()D(i%miMO{MW#|7$BF&MCSiZ@Awlr#)^~7b}8#iyR%1KL#bAoO){88d@AdKD>P8z~S=&Sa)bXNDc&|Hz?|@UpeO6uP=w|ck*rMe#kdf<fqP)U?tR8q?CdTiW;8g`6=RKB{gY2IhhUhsPI`1hMc(z933Z0d=CAS zY+RO<)Mgv*E_ZiVRY?Cc*SEU8h!v^!B~b$x=oqUv*O#@@t{fay1?$7-S{ z8u>{cYtZtP_K0s(@#CCMN+LBSszdewGLN{f1ncAEGw#vKbmhvgM#Tw<&UxLwHXt3n z7D&F!ljy+scVq)M(TlhKoGwE$MTxA)uWi%Qar@rPKKj~NaWpkFj_|gFqEEg{ zcDa?}dzhZBfrPFFB;9#^zFP!XjT-A+2PSlV=aRzw>IA25ff+dwgCZA|u%@Te^r5`! zrC7lB`aH~jGiVvcUq0tiPQ_u~W2%+?^+#SGp0fJ6&LJ)L6ANx0>d-NR8~X?p=jKrv z{OShbbWnxgYtTX9r;~)QNl~vWF$5M&2OD33^ z1@T1uAVSTBXRS+qtPO)700;Z^rloq8>o zpQ<04nNYoiM_l^boaESkRiOGAwUb76+((9V()L>a21X}%%=u#fenp2BoxA6xvzis$ zn$GhA59HA0qlQ8>9f0~W^v91{n|;yUUc9p!>!v(4^i1WcsF?wC9Vn297YV+RSNPr! z46VZvM{^MY9@csIR!y})@zN?JrFiJ$siX=G3-mexEaT1G&x2v*p zzv07Y4*Zf2Mxp?QoAd5d%hWk`#fA@RBrCb09tO1APXL9m^Pp=;%f3Z+BdH zA?95o_OXU~mzV zl#zWn<%2X^Y5Qip3pKJkc_!v|tHNQeuHkz?N*zWk1C4QV!rl16)0S`l)O}3vgcnV$ zt5OE5uRe<52Cii4%Q9?m6W}lCpe9{R&(x~i(bDqEH~+DhFRFAHg(~9r2~Q^cC(rYX zl#S1vJ}npkl@z!`+=Ei3+c)Vp24ZGY)74Giu`E?Td*umQ+v;!r?@E5JzlM+u134)dhp|$B%FI z^Z04d#cK8PfD@rD^K0G={=IcW9VyDAVq+)J1Gbnlvv67HX=f*5tky9BMGq@pB#Z43pipmXs2WBO}$F`pa# zy6X#r(f?>wd*0tWCVf_LVZ_Wdsyc<1Q+OqJT`mUuCJL;u&AL}&5@-<4`L1)gkK-lU zmq{1vYo}X-ehz*h_K$o(V6kAdWok9j{id-&^?MylVI7H>&&Ua+G3 zH?E*q9&w9=bkjjsDGtrU4;)_gbvK?dn463RW}fv6Hh#ZVs(KWPcp=57}EJxL4ApvU01t(Fx*r zaC+ir3rGXZI;JvyMin9w2!dl^9EL+>2{qVx{J3M-?Sx9%gzDB zo|om&?7FOIxk(fNxYK_>H|7=SD;zVvM!dE!^NtwFd>WdAd~c18W>4Nd+U~c_P+75}TF1JDb=+Gu6&d#tHrM_@dI|cwUJ)LP$A~S~I)0VbSDk<~=a9Z$ zTQ+ke-z+IPM7wX0HY6=`a`wpUYrgG2yS>kn+~Ulnw|o9+*|Lv9zD3YsyTyx*2-`gZ z(gX(I>bH}!N&PPLYgQHXlE%^TS2lySuTIRWHZCOz&oA z3!Sj#;F;w19Xn3reiUtbPTq>=(x*1mLpHa2|9)c*Lnac8^-Zc!Djs$^v!yUYg3AY` zIM}^QL&Q-_gR$0_)#YkU+IoygiarXUXw5* zvy9ufb8>p~Hkpg2xBgxK-)Elo%u72uX^i)5@5A$Z*Y$Lqn!y7!xUuYQ>IOVRp&*iP zS(qj{x%X@Imt*<}q+t`(E$2dEva9yOVu;@i&T-GM#4(?RFAT+i3a;skp4I z(=BqqMDY9flH!*=K$N0Rs##!d%FViyPl(irq0Woque@LL{Z}i8%E7J}wvvd$XPVa$ z6h9V^*b>J2lKfbX6G;kD-eh$}aZRL~ms6Uzq6hfwv&MgDU#JJRy;2YUtE#fv38@Pja_KX?_VtBF6lyuq*e~S6})#U{OX(G`;z$``ufJ8^| zcvSq{Yy2NuR=`HMI`j|X$p7iRv$Cii072Kle2bCUkn}-8WdYMcam9WM%!XWn9+(f&|c$W&tK+CtlC0pfm^0 z?9Yw=fb=3_e!*E;b@i%xzWr|-77@YI-Q3u5-W#{&dhVOtd!;tb$gzM zDl-*7J{{-t_0i&|%dFk|1}Sk^pq`HV=z#!KkfKZa&*uXKg7(_s8bH`M4@fn3q;F8m#FssL-II8^-;<_!EzvU_KKxQzKt{~YB^C$foSAja&zGM7C9Nw$~ z$;i)fZ@0IT0BI7xxB6%GOu8RUXxIGNSx?a4Y}#c!79G-T=I4UScN*@}l{$C-_N%tO zQP&JyVaz^4mh;QB@uD<^J4ZlbNPC!GuG`;)eFedD*}eG|&f+T|Mq;apjy!eM9wC-2 zV8U-$v;7@252ozGF(Oma z!{NjIIdgc6)%Gn7DtS(3mX?iw!j+YwDnkTw1P$HlA*Uy@{Zg;j;%e+p;%OFZ@ z0BN~U=buT9fSLY0e0SfY%nx!

    B78jxPuAvafBIW3PZ7vZxXletvpm13yyc+Q>*)h}!+z zuQ;k;bYaa+dOc`Sv&N16P~nlzaMit?#Q@1uB$IkA15Y@F-iuTR6k&BCX^!84;^CkdxfVcyfk2qXd1hD3xs*@> zFVHbe*92s^b6|K&v2y(8!7VFxS{`u9{QkO&$l4*Go*#5hZ-e3tylXx;>HAJG<}Uav zG__w&!@5TgBVsvrzEu#|37p^tPA0^lp=Bd%mm`L4jsdu=Q$-FE77B}&DE=wMhba3O zLz=8tvD1=D>YOI1!yYzj8jt zrM(3*@5bbsWO#hJw-E7jdwPJIy1l&78iT4WLl9O>fwX#XE7r1N<3ruoKWi@3U*p5c zVeWR1s}K88L-&YybAkwl`p?+=xvMvA-uG ze-4~WFFtX;x&#uh34MKqBqqt>;&ve9>luBB!VfQn#=)Ix0V7n`o~*!I=Z83wx3CF| z=@##=ntgiqY(U||I02LNv!%Y?ZJ4RY84JHhI(0zVh-_#9nd?I^*Vz4xq2lv3rytM< zZ%21|&3*TxxR@9}MPYz> z?}~f74p#v7HvPwd!FzZ_7sYhP8zqM6Ef9WfAXS?1M^pOoQM#yy0_nhRU9E0)BE`UV zy>kMw@oYfpgx~$}CLk@UgZd09Q66Xdc7b=|Bwfs*dIu0)j>z9S!=sOA_5F~7>4Aim z^&PmW1^gmJrTJ*Bg?L(oS^^b;M{@#bW`A7;n|=2~w{??rol}<2RT3j+B6=sL;v#C= zG39@(`^Z5ZLNW+{H9p6})obiHmfa z#sylYEa@!DSai|Rky^9(u`<7iPloDX4%KUSq*oy zy3Ck5s1kkf0WR)OG96YjpBPPHZy%%Du{64M>qU1DwIF_*_p_C!-=zG}nLfh2(h%~H zZ}JEfoa?ALhbnI@FqEogwG%x zNwLtf(CzWbd&>t#Z?n@_qcW2osh2_l`Jy@WwcYX`=*!MBPa7TI9~|l&d#Xz)Dmc3~ zPp@`6d9H$Rz+I1Y|2Vw?bdF5D4Qwp5#yiSJn0yAA!Y;L|qUa(&vf)usySwza72#4QJj)%e1r85R~K#hkt5>UEl@(~K@iovC8YJ9g~I zr?9%j?-lJZe;ioWS0$av*(|V!UCd97`FyS8T>Q|?20zR{nS^oL*tjtd5;D4j0*G+b zs?Yil%X40>p1u9!_a7^tjJV!r?3NB0+D|%T`fj0uj&@1Ry1z#tec#Hpc9KH~lN~UuB%b^O$#EK8rv*8D2 z$qV&KX(;D$1X+#YuonHTvbf<|b?th`=Hm6^g<$!gsLNx|K7I8poR@J0n_r z#vqJrS-2I2Ui^pYkXIttpO$N>X{a*%n7BRwo~V<$grzLnmihJG;!{+c?5cbB?>iQD z{FJJFLohkpIH1?q}pq~rtR=AE6sk~h;rMx^ub1F6Es zBUGg0_zMw=EsF?`mLX>p*Mx>B*~*o!JEp6eS2ZN-=O3B2gN@Md_Nxbv?pwd0W{7`~ zs*T|XFd6T-KEICq6ZmsK>HyB8aD?B`Ib!<)QU{s5^la&dKes~t%Rj_G@&9T3jiNMG zlEUITlC(pQT+q`q%K7VQ(tNPckULD~{ z!kB^9C={pyBat3lOgD_-*D4fvlu;ZkJw)*7<%Iwv<9PzCGI2OUfsqW;_s0eLPW{wB zC!JTdsCvM#dO4S=#J`yxw!u*6FD`4=tL-cs&L8?8aU077Q|=YAP7UDtuzGCJ9jlmc zR`+X4JZgEL^RHWc{&hh5VT4@CE(ts_c~Hpec@ARL0`w9Nm_&HGE(to7xdNEiiHlxu z`CyM)7$?Q=Oii@o-II0LHsg$p{EsZCzm0+TM#;+k1$|Z4k+#4Sz)+W9wI&@&0>$J_ z)6@0mI8(Ts@?SSwZs82SB3Y`5U@VG@e6Ml(5|jzncQ#*)46oD>WxYQZecL0!{pD1^kf{Rz5TbIhPu^h~D+KqTQo51uA>;E9wyciepKzu}HWtBCq z+YWzzxD38R@JX0!Ou_ToXBSv@ZOYQ;;AtC}RO)J$@ck!9n>d76HlRw(uds+aVuCG| zcnP|VHp8}ozYyoSHNJ_}gt&GY+E9-}*utEKzWL<`cJ5R-IlcSqFdi83`OZ#rZ$kfi znO+UKTyt(^v>6dFbkg@P?PI&_hqcQ1Wy?;VO8(BtB{n&J=cQN6r0U=b`x^<)btWVt z;q7z%^l2+V#5x%u#7=yHE8=02!>*4#>c3W48`g-?5_?i3?vDin|Cr>(=a~-xz)QLU zOpCj}U)Qagc*eZEytXY&$DJtlPy~Bor%3EtF$Y&CXSF|4D?EG60v?ABjREdm2lg%I zZmwa1P?}jAF>W>A;)a$L@TP@Eu_|SNB)4t&YPd6{VI0X0L>XBo& z%n2EJW$EbdExTR56LQXGXtxoS%d=M`3{Y(ZL%CkBheF1*%zJctp=FZ2S$xIvWy_?O z1ICo@iAsT#h(2OjgNVFXTG|~_In<}gVD8yIBi8&HCHgg4#jq&;=CS&`28m!5@Kk-m zo}~ zi;m6Y%;ZWaygkQPQ`Q`gj`1o%l50wTDt=^$0~zL&5?;{ko@cJLIv!n$kH_sp(Rt|6 zYxy19=mxGR0y1=a1cfw~WyePr$DsS$duw;xa5N)Mo!(FkHK@nO;mm<76rf2+^!68Z z`qC}RRmcsi$nW%_SCDZlVla_me-3sA%c55>K(?`dfSQJLAwONzR)~J|RNIU@YJMd1 z$_+^8RdrofQk=2u{i{D8CtvlqDU2HNDb0zDVuK3CLPlKemG(>M<2T?ND0QTZE za+ZK(N_)+bs0>6V8lNGgJd;Shwx-#^{T+8?K7ul#k!HLO?nI6L{m#MA-Nid^`4n_b68aqkzK##OzPC#y} z=d*Yh8I0d*^DomG>ewKhoo`dL*b3!nL+K+Ys0RO4^9nQ3$KXn~K_frd+$)pn()44e-$!QX zfJ#j!LMen~u<@?YX0}LsiE!h7`o8^9Z^_|A@##RRBYZ%crW<8&6cB;t#!Z{nqo>x;&oV9JuGD>R0Giv?mMe+UJSYUiu8yM)jb!>Esm<73-kYvGpbX z@PC4s4T~R$dROpc@iI}sNF2WG4C$np_Xa|m_-v{bG;&KG-UzR%z7m!rwl~^vplj+8 zL*R(-+i#3DnfZtl;uzn%?Qg~k6-4m(Blq05S-j(Ri^h())3mQD+qV{t@}pTR2b@7p z!=u68R9?~3``gDOhC^eg_g}VhWi(TI^dl=Ygo~oBLfCw$_-)hs?}-jA94UX!7(G9<$Ku0*IQh2ys+J6RMZ|Yl zU&SoUppt)2JM;~*Hq;V!cG^XQ@@m6IxJBed%pBMukI$$bibG`s2SyndmIC|q_*q!k zB)zqZ;cxMD9cL0byT$RIF;(0;8OY8adGHk;sB%oNM6Jwlq4 z|75`db6-tzE@U7NRr_~7Rj%ly^YeFKwRH=$B_VwWJxzS>K*H9KAT)GHGBNa!k+b#V9egy?n^=bRb$w$!ToAep^WEav)(u$>~h;IX0z9h~`HANWKz=jNIt4 z&0*Ws&hKRpE+c=ds;BE1Njyn^!GbxRB1ERg*>y#Ro)^_6n^lKLREmjgZlE zux(q}o%n;kK0FTX2fqkHY^;Frb~<5VSQ9h7zY-Z`H8(ecK&x9N8QWE~ zgftN_UuR}{tF~?b6+;}pCjhUi3-v#DWbeG1&jn?Oh+mbEJk5}8^f7v@N1rT5H+7pT zLo)mL{YV(k#P0A6#zBJcQRRrKvAiB&r_5d&flR^Hg28cNyk6!Q7Og{61@V$WJ9g|a zEV6Y!F#bq5!ezz+Fzr*nyKhyHVbM>7fD_mjEDWvtHcsI027o4NYu2o3s0VVz?bUOo zdW*bpoaMEZYw9O4`AZHWCYK%ouKt7{Hpw#A8j6M+k!|=e>z$S4bT4pmnZg+`Z)RzV zzmY>a*VfW6 zDcd1bsU7Zms7CKG>)S4A;q4BeKYr}Nm#)mLOPI24k898>uQO-PXaLgDGPb~XNk-1M zfH7)$<6+Ka@&QMfEys&EtXWro0bSVAriok{qfcW%W8(270f@>%${6M%umXuW1{~MZ zheC?^BopPeR}ULL{N$%IKwjLUp!$el1vR`3ML<^Ve}*y2yjpY`^Bo*+`nt}W7yh-z z&mn`>Npi1wtMgc}7{tfnD5n2IMg0c@b0CaZ*D{t@riKs~ONJ^^y0gAH{A7;UrF4pM zaRV?6G@>rHa>luKOi*uVW`0j>*}fAq5H5bisVUyTf=Q@dBS&-Uskk?7(k%L8d>eOi-GNHHh2a^`sSs9~5LwUIgB<4Ai(|-X! zN`s09@zld`}7>h&p6xB%+H9e#VdzNM%4;BU;4 z!uGfKsib=G8n|gc0W^M54#@4vrH5cm#y1G-#9Rsqe~owP8QHPFSN-=l-Gyxur#pc2 zL#tw|ZJR%0^Rorald!tXAD<^>_9{z)_7I{>S{&LY8B+A>wUBJ9<&H!hg!ge+m~jJrld{WwJWb#>Q9(M z!M3@^#!>2G5!}WP8h6C>BAM+R#X6gscV34hn4#IvHx@gYNWB4pOJO=+I9wQL)^urN zC*>Kw>ylALhzuMB`uXn1LSLKbT?}Wji z@wwUm1Q|DCXOM3dP$!l_-65$B5zJhoDFkB}L}JumGVi^vCJU?OR&HnppC3qlWlz5? z(}oX@@l}GWxC-2cFn8kl&$k8$)k;-3$H{WXAOsMXsb{xQC$tw5hA=%gLTTduVUozU zU(2BlCz1?Ok&3_4*yQtUgG&5PGDUJe$gKqV%(_&3OCqeqg3gA-??3mW^05N5{(H^> zQBpwuj(DKamEaui0x%Tp+Jj{_$L-s3Gr}6AYN^o5SjD$p2dhY#F0*RL&+1{b>Q#F( z9!{%RPHH8oNHVhI1q});-4&EjpO9-x43(8`LOLsp#M68$ z)uZN*8W!Hy@R25t*%Y;)W#1u}A7^;)yL|bwJ^h>No{3Eq=hD;j0ClESl0}0{X2EG54};`!Oy1<_!Bxh zg;&}~A<}7y+vTHXMrO3yGtoeX(G4H)hxNF?+`ni|xVigqT1VhS$$X&|Gt0o0jDZy> z&UwS}57yPtIEdBo^r=%a43&ae>UU6S_0K=p=SS##wbGt&#fe_)!1&|mcC4{M_3a&2 zk&+~HRVe@;KHgg`kypOe1v4p?$aaT|Bei{jRB!+fAC;#r=184J$X&*l99?cE{t5?g z5}Lp?{=L#q?lyp)&W}!S1lKbrrL>9%mX;Psn z?peEL7y!~upEYX&;sT~x3`1??7&MI3rY9#Ns!K0uW&8g4a{ub!cc5=Kk>zw7;}i2> zrBwjC&=EgQvIC6WfPk3AqN^6YcPlTW-Y=*l-J_4(4G z$;Rft>U?1^oN@z@?+B}!7Trc!#yc1KpPZ{?k`!m!w=XR( zuufio@3B_nNq}-L&CIx7`$BA$D15&t$7kbUQBt3McBdpE_ic9{`c7I2q@p<J5NM#tEojiyPGfo2c9MrmYE`XJbCmPgcI1lpP+s*Zs)v0C-qgO-}6K`+3y z*xc&*h|oiarm(owcgH+?qb$9|$YoIzcDUxGgtqJ4c>>*CyEbhiRt!)KbC@bNEgT2; zxR(pU{CUMq6mv3tW>q+Ax1VcpQm@B7<8)ea6{&GAtj;JZzTcZtrfN-n&6)+y&L*;a z7?OZ5rCpb}gpIo=s-X?YC75~w{{$QEBn;i=?~YAL>yOl4T>W3aeihqs=8KPuc=!1T zPiTYg6_w=SGc6~@u=@R@X>r#p`|p`Z571Gf!I@tuJ(l|Ec%7uHpS&>&I*#m6SbZK5 z;4!M@V6y8B3=M~UYht%Nr67p|LPp|b9~yn2vahyQ*%~)8eX0)E|1f$oYxCQ(kY)<0 z|AEeq1B826%MQkJ0nh=#*;+OXK$0Q%+hfiBR8Cc-MvCa7#>`>Pg+^n>jA2f)Oeo+Q zS7y*n`bn=kx{<=~-^vdk{uykn8u;4Bg2N5Z^-fe36I9#aV~f1L)kYi=TC7Q{@P4SO!y(8!|b|>8tC;@#DZ+dc&_1O)zEs=({G5z5=|+ zs|v#uO8!?%JzMVC7{s0S0 zOJiaF_@iC?6P~uA)31k4U066kZ7N&z$K!alstM|KczXZ#)QU1$oIc3$ns=mmtjuF( z=Dl~2N3LXu8#LO5yn8S21!Ms zPMBzH+@oX1eM)!p4!-cPD=s{m@W%fA*)ar1G&Fq?kpTH3%jN#lFTbpT6Wgq@8PIpis$Nnu_x0bPoe>CNOE>2UY_tt2l(OyoY%e(pHNrpjc!3L`F zozwdtCD|%7QfqdV3<)4c(z9apmcT$6xZu{kdJWSo9{bW|D@^GjQoq1sEUOtiHP^cE zr-`~3h1v3kdf@+YZr*kyFbbbaJc z)ZC5yY`n_UJ`6izYWc>q$dLztUPLx3Z4@Wr=$W|(!op0&Chq`st`1Q*L9S0?X$A~Odjq~P#y7- zQT#bVW^ZF)2HwkAFP!71_ix&`@x6!ITG>ZU``S}T<1sQJsn>1&w8yBqX*wl5#baAi zVq&bu?!-*H7iU7ff4D5EOUq9-vCFDp$)%yLmm(@~(24s8oxcb3 zMIg=358gbZvc8Jq7OraQi@b9Lh{og;gP}H2aHTFy4cWiHm7mwKB1gwu>x_nG)n_B# z^z#C%nyQdoikJc$mc`vp^*@7jyBiG7%}dPSqvm<0tCxXP_J;0h+Pryw**T+-H2DZX z>XWgiS$QwaOasc33r4ame2g4ctp4%I2&s59pU@%c39vke*sL>N!@h@4@9%(qNoImn zp8ql>^2~6plnS7KIhY#xNrJ!=nidd%u*SEjZ*N2*aSy?$% z?mS{NAZP-|R8s6ol92p37Lwzsr&JV0Pgz=#D=Uh$ztzXORR5ZKq56S!)%7l}_L|LF zwPEbWnt ztIb1@yO`WM=>i%06UtS7`-!IdMFj+(~` z5()o*+hezd6M&E)S%HC-@h48Gvb?R%Ejwyh?dCz*~rYjhK@NtT4WtG z)Pp8EUNiE*|5^(xj+#&47>Ps+U1SikDfVr%Dc^Vk^b zf62}IwD(9(eHqOSW1d9oZt|w50wzYQC?ymy0eMlEmJd}zx0p)?D<-_W1-Y0v|Q>_mK7 z+%7U=!_~l*#6^e$i&eR|fdd4$h7j@``A)rWU%B5*>A@)W5%y|Gkq@^>Jy-B>a8lr) zLB#;y4saE0^?n)}K|Ikje16`{%-fF>z) zGTRHSoI02O;%Ld6e_FPDlX508@d!!+s~xF0H<!>&AIjSNX^HOG0QjVtYGJ#aLb+NWQx5L2XoZ}Yu8W9s#k!X&_BsblM{D$Xy`|A&8H~Lrf{p^rl)Nce$mhamBxm>eq`7bA?gS=Ev&g-k zqz(zSDt^M{QDxOSsg@9tt1(p|(PD{spE3mW!Kg=JrT(R4q1_s+U-&g3p44dix;V2M`tH3?=?2*-Fg z8Ct>(s9o}lx)npk+L6m&heL8!yTF6EWpA_{0VgX^vIu*B_wPpWq{0OF`6-Zm#3hGO z@?Jvwi}1-1rH9DV02c<%K6|PpOSIUEQ&fLeHSD)P>1z)aeIC;i$WO*Ma%nm`%Osy6 zT3Y%*$yUYlJ2?-5P8Kc`?Fg(@PQ-9EwLj5Uh2~tgZ;g)x9GRT?!+ieFXxU}9GQkE< zkN$*b_(K6&l6xYHp6oJVlVsCF(96^xkGTZ7mUz8WC)pd!PO^)E?Vo<=qcU*T=| z5~U7(dB8om4XPkc0QoD-ge0NCsY~KZxQrNKu9Fe zJvpUy$Bx9J4yJQUPPsCqNW=wAH#Q^=@t$Y#iVhjzmo2_pXr$c*j#S8?fJ3{qvPI0Vls&@_x065`$Dl)@M`nKoWU-0phZ4 z9~gV{{ImGIi9n3!KV9##)WgGdsW)(hOeUp0zf28sws;5A%;07&A_RRBbRe5=DOvj~ zSEdvdp~CUow{Kt3p~Lf&CUDEmb8*?uT4lyo+VxQiNzQcgjQTUuSz*diR;r=22TPN} zQ@LfI4gpOrEL1uE zH8qD5*}tvIi1=H$%*>d|Q)& z(Z6}5Gl^~vz`jbZsTNaRNjX+9XWi6!o>g~?yT z`k}q^?Dyj}AvE^M$zo8U_Dvo=_KJO%Y18uFjIqhwY-Ml~gaKWK>$M~k>bX`;-*s&e zIpg@$d*3>>n;VZEM&U<}Q}408iT`2K`O$5{p~ly-WHz#Ada_YTh+W5F#*)c;GWVy~ zQS*K^-#{DxDrU;w-wIT7v|)BNPQkMW{jCU7A3~SlS6{#4crh@j485MmY1%5_+BdF_ z1MU%{h*M>|Z}9i;HENV(?HA}3y_xo;_4J=5$JdLX=jrRCDmhkXcvZRls{od(#M?OV@aTv3gDE3N-Eb8%w% z+qau}D?M1!=meQaV-Xlv9M>$B?)F|@+22l87atr=d+3}~SrhPD))henj7I#EOTg1% z?vvx*Y6T;IC6U)xOjM2^7P(6t7Vw4a#7o5J6uz|sR&S0ZMBO@N9wqPo5pK_$Ei_x4 zJNu^%@U4xrGmi(J$<+Do-62I}J71U;Hdy^=j>{+}El?CV>NJZqI6inD#mcjh69|f1 zYnX7#<%WOv)s6rD`)_<#Ytv)m2eECFGg~%odi=}rit-Xu{#EkxJ1dr=9{KRAyyO)q zOz$@*ely2A zIhcnT$XqtWYua56(y`Qb=9AM*xoAy;YzxAuZayi|JplO9>DOTglh6I)o?xE>*O`aNc_uW8A z8*RAb9Iu)RXe;N4*rNi+7*!w4-~)RmG}qt2TEkBmx8lPqR%y>6Lr&!!8LfVey9sn; zTJaaB_2@IMkQ&}_ZTR(lFrXBEo1oVjryNqbf7@RYeXB2dUrwBM2`&e8JzU}!ek%&) zFqnqS`P=qO2L$(_lq=N;U3f2ioc06lH|u8)I>rbMscFy}pbuFvZ=Q@?(?TmD8GNbE zcDLsbTev0X`rg!pNxrwehIxd1hsjRxwp=(zoS@ zbyjRegV99ssJJ)}!@}7*3SP(eW0CnqeEa5E`ystju(U=pCePv3tv&ksNQn*jlULm? zpva^yebucL(3CN|&VgBlHLu>kfBgL9p0{PevH0j6=W15tKx*WNmg>lZl}QZ_g>3v6 zScS8$AJrEC+s%s?{gBdMSw7zIiSUPXAuo?s=meC2h*B8I*cFpEW^oMHqMv-NYZxbY zpcne{@7r?+vL5Nh%&~GEVG+^jB;bx zQqs;a9!p-0fU2|*4oiCOB2`s7RM2=J>5ln%d6V?r*~B^>AC2TrXHV^T*W?3{a>`>@ zpMUV;#q;O(FK+~KR2tGX3DiTL_7Q%@2hX`rz5>Vmi??6fWdL0!tx+q5jJ&2`5?8tO zZlt2!%*)$FuTBqNsPs9)JoYUNsS03;{1uszN$VRr{onkVk#8foSEqp|x>gj{B~TCT z$j|ikCU+oz!J&@#Nw06H#|ZButX2M%+SKIQm@euB>drlS6Sm1yFT2+8efIA7m)N2Cq`m(clRl%35YM1ft-RDWga&m6oI* zDMlBrM{;0K1>At7TvP#VyV>O&uKs_4a;MRY|JB|wsAIbR)S26M8>>p(Zi!}o-G?!t zy!2mI1+y00-3}YJ!vEyEuWRaOBfC)my)8>+GIc{ezzL2V`g#R8?Awht`+BppnGLR! zJ4p#47`>f@<^0xz{S(=f$}@sb5(bul%x-(-i1Q*>VtFP0ywF)bvn*IZ>cCX>eYXMm zt&Y_cZ)%!ezA%Tm?;gd7s+%ZiMBGwm3|=@IG4F?zH*;(=5{rLjRC&#rl-O1~bD+;O z$HsF8dF`2~Mk>I*67R~9 z^nqdQqc~sHX;e$j4jz6I+Q!VtBsl{@kx>9D11v;w(IE6vA4se=e1j;rK-P)-+6>a% z3;yxo!Gkw|km-l7dUTDKR2PiBD4Gpqg{EwA<-&w0Fy5#!9QAQEDO)|sF4$(Wy>8F+P;rK zCC@fY;(~u%Q9j7)@TgVeOG-+FU|`SSYcHc6U823_EEX?){(+9}q4#JQ@);w;DX6ug ze0mx@CgQA)?l`+c8B77kRZgTt*w{6P_*(r)6Bg8pmesxPt8?ZH`!I;RUD%G{`pnSa zmAb`{Ooa+WWMn!uHD`}p{(usLcqUZRA5u1Xc@-x+Wj(R@jh5a!kz#%PZTzpO)Xb?9 ziXC$g0NQi?S`F10A6e-#YgT7?yIiDyT0w6%jw+6`{;9#y@yunKM{bw)?G0=b&(6rs zH&v(i;pIA>_fMm-F$3{p@Jmm=c6oX9`@>q8kf#l|121 z0oWY{Y>w5v+pG)`3>{w(>=`vgLsPrSr(qZygkwJau;i3lfDLFwLp>@rr?P#~788#K zqcQbTVPb`l-m4=`6Dw5B~+MvK)4Z{{=EY#go9 zt4(XsK73N%GMpzZCrq2hjRcsBAK7>8JIHt8cWBA;LN2G)p6$2qHfNiBKt7Wf^@T(f zJ=nUW;LLC_UL42}dAKNfX;vPuL|@GRmUtld*LONgc0BesrMSoZqSlJ7gdhqXM}$LS zga?&7nYEnfAWujeW<@GoChD#z=o>$SXU4h;N=;3*$k^Nau=zG6?X4hbmx`bMind>J zE|1DmY9I~(DbZiQ_~X!^b4r~%&vNSbfUjNH>2MxqlQd@cttijmk{*vlWjZ01#2h?b z9ee2K{ldaN43JQ|5MHzZEf~u3BkjAt`}B-sf;7n<@X#VQr*w$o3!~wypNiDi_v59% zWjn!wmUzBsp@Y(-G|?)VnnNlR2yk;x&$Bu0rkflwk3GBR0rjWTo#cc-A@Bs;1#q?) zo&W6E;niZ2m)!d;`j@NI7@f2e$DYaIF&QN`_o0V^IU3H^4 zw6C7gzI7(!VLa4Sf6A$I59%UYgwC$Q)+R(IbGj#lt+GmnMcc*7r1X4n_9t(%(Wp6b zHpPdL<7z47-5Pm8#@Cd+|(hnnZl2M1!taaKtV4Z$%zJ?1f%Gm4iqtlFYk ze<~V@vPbC4MIaL=N`vxotlCLplpH=(fr09yK1CpJHh|tT3{)y`OlUp7D4N-Rf=i0@ zbHuK{=7wr}zQnmLYm+B4bLJt7__UedO6|P5kM`2n%XY9#l9BP$)X)@arDjP}Kzn_E zeSh<~a~h)1jT<)HLvL)Iz4o+rKRyz>@IHP0-4<;{Hlc2Fet&k#kwsV(9?y;u(Jey= z!pgtTh@CDekCGf;^J%%$<71MAgRtRcS=j_Kzh3G7b0z;Ypb{@M3K5w@#m!k~j3&Dl zrv&P&1Wg%ebMOA~eS=G=skrV;;aj+^e<$pFrkMw%NC+~p_dSs2Bh&sKu|hKL;V$>m zHj?1HA3lzBq2JlhoJmm-6q&tJbodGK*(Cc}xLf`bcsp%YI0fpN5N*$oT&a273lmYm z(Bw*JY2NWf&+Hkdrh4oHHR?Dvdld@G)Y?a*(Fqw7Mj{LQ!q2Z=O5HjMKhkvwLnask zvAQVQ@1u$3EGS;!&+x*+}?u+C$m=iNM#*xP528bRNyZpqQ3Og2u_<` zbp3p)d*vEMMJPo!lMy|c6Ghm2-nn4*w{Hg!HA^GxEU}OFuM9MNVo#iq%T;Pn36Ual zL&PG8(oy-|=ek1PH_^%aucA=;h+NtcJyMB;8t1S%3IvzBb(RAy)9*ZbNrq&TaYBg3 zdWuONfQca#rUrMopoEpXehzgmMrm;4+O>_~s78_vQR=3o)Hqopjv$3JF7JbEwiX(Cb4) zM}=eqfRjIC#~6WyPI_&d|CGImZ)6jFKk|{|Xa4?UOzYMhEs~r5f+srr**>gZD(m1% z;4^X)?Tp-!6f=MMaSb9S6w6QR)U+gVRS-aNDTan_MP$6Z^jK7sVE!T*;?UXUk&yLEND{4Yf^{hh*3uZLe?ph;lF6LiHiMh5hB^Oh_*amiy} zE(Om<9*{_GWs)0r9NsR4f=>uM?vFNvI(D9&otWbPEA1XOOa@1D<_I);Z1gS8e0qEt zpeSm3blRxUK(wC0JWBisssxmNEfmBzY#?h3m{)_5H7L;57nlB@_Rjn-=lpH|SJOwf zFpS7LV;z(vOO}M8EZHUsDO9xB%TiQoER`*5B}>@}6)J@+EsFY7lC&X7WD=5!N!-u# z%6xzN-v7ZpkNIJy?Yge_`}IDT<2;VzEIh#>w!x;DM(GZ-C{feZ=m$GGF&M>$-jJr( z8%<6R_RMi+U~vUNm{(zSdox+=$jS_HG{C(w;9hc+f=@Ew*5{wNfeD)$HUHoVyACW( zL}CDGvU46lCOp<1aigGyfXuu}5gd?OkZS;rEVhvz{`+rP3(QGvL5clVbEXfs z0O(T%!y_-Q8=Mk9&igp+c2!+=IhNuOTM6OV7F3yZIi6W3-NYBaYD1lCZJ9nzb)xEpnyKP%MgOP!hZqkZ~dL9~Axqq+j;W6s2Ba%*FzL#u^ z#3S}{QqIj~X3mAV%Yzzv=t8+S_k?vFPS^L}o9CIG&G*vHA6Ccf^3 zF}o1f=!ojR$1MIiN0+=@M6cXmv0)L19*)-9!FRuoW+KKS4OX3wp$Y-Qy$t?g{y|SY zG0Iw;VHm+N3$4UbS#CH_L=r}%q5xnFaI1%&0Vd>0s6J%5AgiyD5_AD*MuC+;$c>oU zXuz>E!O!erx;2)8E5+{z{!$4ZkXtF9M;u-@)3*tfk_3$LMU24Avp6!-(DqkrxvtSr zUTx_hgx>DNE;ZJh${KDF1^|$J_DgZ_ntWOS!OM6@$sR_Y9d_Mn>nHBlndz3*{OkAG z{^1zRC_XSzl)a`D-=;3vBP#d=V#g%DTo{&#EfUfso;po$Ym2|&gm>p|p(_~6qrxr% zF*Rd=6&-ZVuW%pp>bK8sc9QR+{S+N{-ZygK^seqvyS%t00cNU}qpF9!Qc zqrM$9Ql`boS!6pfLD&bz-LPT9;Ny~Nt(Py~MGnPW>}LW3 z(f;*FyTj%zA>iy6lk0r_{F0F&5<6wLt*q1_67lG}r#$Ky2lEK3rYwTvIp&qVDHO7i zm8dLLN5QM!LQfsaEc&uX-MSS=a^NKDg0ZaAyQ8a~_(4_`ttGqrfgOsvjvwo3p>mL~Z!{JN~Qqyv{G3>0-06Xo#F+qZ2S zOEEyeM4GKCmBi=I_|-NBlXXv7?$7yIRg&C-TB18ktkJW_vW*dp5y`vAw4E2c?Ot;x z>-ve>xvhRR91odW2Ap6qXmpO@g(u!qa>SA0MyG1EWL6{^_ph|>!)PPLdx%YNyy8Mx zwa(8ng3Zg^aY{FF_ z+m^Ti*(|m;0s|R}%O~3;|H|@cobzwl#!R-b`eb;4q|soCl)zcqd*Hx-_D7QXon>&T+JCO@nFAu1v|Zw0*BX^%o7U>*o*Mq=;k&&r)lr ztt+bCY%pPi9~d6^BsVwEqo8n;#ni02m0-YP827KM0VYvoa>lD{aP&rhBHTO=@V0p1 zhW;0J5Ma?rI2`>7UXQb#GsvViHkww;I1JTe8xB1`p3J3Ri{mxBKCUj_`5leRqd0U3L4;5 zcb8)#TI;d4)$$=6H7S0%_oNKWTPJWCV-uccWV9q%l7IStSJT_|Fz7^#;rOKb1i5!| zI4Iy|N40C;{$y;drWC`}o=8L}W{9=d7VNS29L|jHctk{|MmkgyA9!A~CypFGTx8U* zXQ%U7bg!Q!kP732aJ;1LvCMWSa*DM4CySvEeV8KFPy4y!@p%vs_yDS^n>!BbF{-eT zjkL{<&uG%4$4LvL>c}FNEK7feugKcNhn>z3$@+34P!7H3t0D$$x-2ukq<603=Z{^y zlwUB=V9EUT_tlaB==qW-}{5t8f)##`fpTx^K%Y%u3HCxJmgZcW=4FEO|hPypGz+G znPu8?Ld%d`i;H(R)t#9#yq!ILv8~|0=302(j$}0YIl&AM-dA` zZhxze5I3Cn?{_l=fb$fypgo!E{-T7;%-y)b67#)*+kiw$_v5Kx7RIosH@O=ikqwXc zkd)0mV}E&$HoC9?>LtZOy&&D7DT}}DOf@~5vqXrP2dDH#8Ip+Yr-W)sAcdxPybLYO zF0|uK2#ia#@ATN*VjkE{#qHeCnLadBn|QS?z<5)7|AL}lt6oJNI`Q6qb}TA2c2X~h zwBOB&u;pJV>-C}BEyet}BOSaxJ5d(C4>@#b$-#8HlOz*wHf~J|F^J()6h z@{>-wiW2HaxR1Oa(hKG_=d@+piypD>uX8e z9lS2HQPZuE`lZDkb`cJ6dYU<|fR3=m9WM6iu7T^OUdFjZ(~CH@+ce~Ol$(F)+|(YQ zo}JI=w9DyxeQhT%CxrZo1X?fFjjzovpS%?8>yU~jRJ|x{*@vWlPFwKJIk8~sch5n} z>uihF50v;OiMr?JM)q#EqtTALe*nIO*0WFcl0}&0VtOZq%s2r5A@j`9Zs%6?qL=F3 z*5&3Y`b*MSeS&@f)zd}urJ-x%Mkl~O)o{;scdJ=j@kM_(xo8p- zxcjN8FGMED)QXeHD*YO`W`H;#$X@@(71?tMZY5Afp8(r zpn+5IE)WSh$W|4tJ={-tW220#5ZYz$G{PV_@h-UHhWAxbni%)Q{Sw zVNoMio^tJ7Lv86|@Qymz@BShicy|{quk9QCwYb&)dyN7Zq)w|>i)k>5cV`jKBND~W z`(~!N{NyitMORfykMjaD-IPOvn}X1?EN;Py=s98Llu*eOn4(-p5pRoqcuf^ekAf|O zyJ7ykx*B}ib*L+Rt(wyvn7q^kWq&+ZWgNWzRR@Jo48-q-Bl-ne%nFNnj>{EDnkZt$ zjC2fSxD_L={?TwNDWAS#Epd{xZva^d9E~%2cV91)w;o;HKa73YqCCX+0g70M$J*@8*_I-hFy)3u-3A8nM1LqSwuYP!W60%!y&!pC73Mfg{Hj*y1VGHyypZu;L*7Gb>- z?2+bDOxBd(@286&GZpdS_%b=79NLFIq$^tWHH66Lm8&g$^dMK_CA zNMJmij;q-4#b4Q2MGcrp*SoSHGk@*3eRplTvB?vBrKA1|_v~AT-f;F>Uu%w~MY^Lc z$m}6Nt-#QTLFHS@2gGX>qJw5w5ThP(+q{Z>PJ?^yB}_A9$CcGe&-MFZZ1WxKCCM=z*%3 zTm(4O!dxM)YnUIN)pyOncC;;D6jrw@pp&CXBkcS!kxV&gcFIEc6>RspCR zKxJ8cs6{~PB;}j742JBnS*=Avc6Q{WDgaWE4{f@zZ~LkC`MmQ}lLa^fLe{RfgnV*# z+R=n%kKK=5c(}lv@1EbnWNrpuToBE|HeF;cjpnyMdpleXOVi%_LmydnMr^qL#kSv% z%<`Yrw*N{#;og4O!EG)pD{Hfy+1zVXpVN4!+wJ5MpGqZ-bl~BrvBlmXc*Ea)Uu*Q# zFjl_N^9RlbZ%DnB1md`dQvUbZJvGLrbyB{?;S5apB~9Y3 zRjJ;=-JQJ#OCFsYONKc|x{qFxbAkZ_uMSgBm0U~qn&vj`nuqeln1+%===Jh%y6F=e zZ9t7g8b-QRnE_>vuD+r;`g01_Q`}F5dg4NQwK&)rme}5Vp-^;Y0^&B2uK5++o%|+m zNkM+WdG`eK12Xx8D~nASUXA5_9I3LN7^FOy_pH*FL|?(N0B{19T(?1keu=#`<+#}e z^QdL{`IK~UBxt#jn($d zAbf7zs9QcdVwkt`G(B01X5~J+E5+Cd9xZ_uLM)gET;=%2m=+BoCuZpE7tOsm4M7)S zTq^q_g^u{{>0u&3HV_VH{HWELhUea;>EU$g$bNPIpaO~DBt5pl$9p{}HbWQ{630s3 zwpsZG_CEVe58sbF!wJp;F+V}hUrDl%GYyU!K-~#T7RWpy0n!4s4hCIGI zc3gOr_8(#BiH`XSV|wvZfXL#?7KRRn?y>cy!$XHP{0V4q9llnq^y~pS0yizL++9k> zNyM0=RuxzO{wN9>y`||3rK~0{m znR8Jg1;I1-s;OAnP6Fx{!z74X89HgF8$weef_z=z9cD?X++bWij@ap6Gc2!vv17m$ zNVJmEo~?h~C9HPs&$-`6Z{k56mO~~`kQHJ%OW{UT-?9D~( zkRQD?Ffz?~Qo}X+nNFAq&_2x3_mXS&pweY@ADMPtYt%nwCnd8Q;|#C61KUs~2j zHKJQzm!XGup$Ee@I~aZ$I`iRM(pgszldVa>*nf=Rfdby94;%in7_!DISkG+ zbk3T}$dU+WfNTi`$A<^wS`EK(YH~yHPE998&IN~+srd7o|1oad zQ7hBaXNE{ZFx6opfrJ|LH}=$W89FJw#O9YWgOVwYD(|S(w9{vvDua_{+=qnbPe6NG zx_wzf_3`O#KCGCZ87fy+Rh39>csZr(jS9DQxGNG&57_#brY##9$s9-amYbUHi%biB zq|84r{kXZ}h$<=df&z_(v&oAZQqZ)N)=1KAEi#%%EYEV&os(_jj%IOEOPPVX4I-Bko zL=8pI`jYASZ5�`E;j(Bt>59!R-`@@}51R%>HT!Dgb9~xqikpPF;o?OF`+1tocjX$=!Xl zwZEGEO3_YWn0wi#k)rqhru*-YhxRfsXbRnz4+!Bvo1`YRS5(_kwb!s0ky*u*-b)@| zoJ3FoXmoXRV-ff$CL6ZD{*Es<{(E9dN#^?z$lZA{m#ABx&G7=6%1kFTQzzz(@=u=#Jrc#Db`)K)}$?@+*Eu9R1HEg zNl7X5Q#QW16>$VuO3>7@RVK;Lo;fj_4t8^~h2iX0gn&j52U!uiEgRa24_3<&4#ns-CA5%tu+Y1)xf3U?`7Pxi(#(fg@D5q)BN)d;D zVG2W)rE=-^#M&y~nUt^loXmdIYW-s9tgqlxLs05m zGvFJK#A-0xCr?d`14V$AYrjz|b@l?ghrwzJg__Hx@ndIs;Aq;L>F=7d9ZPQ?KwZb! zmqOwErGZx4eq1oA(b+aVdx1ykrGXqU@{uS5NbHzs&US`3BbMzX*0pZiHi^NN z3F0trEK8RkoSO8Dq&sQC-XG{Y^Fw82H^iF2JS>W|8Qs2X*J(T`v;zzy3JalC@l~CWM>e8^5|<^SefP5X}xh z+oVa8KDlnQHI%8wIYj~k!e3X$R;0x1k!eonpwLsG_@vuuT|QIGQ@O?4yA2+BIZOJ- zR8qY;Jc}F72s{p^bm_XH^Ts~{Ur@T;x60j~GG;;k@G~UN{(yK z?)%WlksW#Kp|aiB>%5jojd|L18oe)s9j!G3=FO(;9ZPxnhV4o+7&dvU z-7RLqQe`b|`II+$kw<~r0{>WW>UHZT`KK}m5$_19Lgbq>{)#!Gd)18gRkH2>$jDgjfwhnZmVp9fKV=pS@|2j;|4oC@S?)|lgLU1m`3BBbw2eWt_oFo zwz~&X6N#T4-h{rSAqt1S4TXa)t+YLrTU<@SQYn$>O)lfdY9F{ZS66-WZo_|kDVRVH zrm}PhczGQey`fX-oVsl2AgUAD)WH)Zw-|J-JFTG0I>%|zZnZ1;l$z{7f+c8;Lu{{G z)8?iID$~m>U(P1pyYxeM2|aHZO%hUYs5jK9?4C=pc#kMJ7f&8)Sa80*{Pk?e#mkM%JJb@?nyRJpch7JuoA< zalne}%Z~f>Gjnh;Yj{R;uguYQhbAZ*HBvc19mRgkLxIk z25Kr%0HKG<$3K8)Kqi(*Cyww)$bc8W`4?@{23c-vxPKkFAmBb%NU;2xn9Y*{sJ`0} zxhVS_9vo9aw||`s?XuR@sw+~YtBTgCPcbEwn0Lv z0Ul`h>j6>+|`ccd6#ps?~h@ltddC zYgkuaQeqp@A>6mLjj7YWaKoFlzSO_}AKanVbAa_Cr>aQ@A{6{H$!Oa6@IMxA`X4sD BeSZJ| From 13e3e25f5d8379e392f6414e930167b4df8d72c5 Mon Sep 17 00:00:00 2001 From: ggarcia Date: Mon, 12 Nov 2012 21:34:28 -0200 Subject: [PATCH 0236/1949] PostgreSQL limit reserved word limit is a reserved word for PostgreSQL. So it should be included in the default list of reserved words 1. Here is the sample app to demonstrate the problem -> https://github.com/colt44/limitcolumn 2. Here is a known workaround: Adding DB.userReservedWords = Full(Set("limit")) into Boot.scala does solve the problem. --- contributors.md | 7 +++++++ persistence/db/src/main/scala/net/liftweb/db/DB.scala | 1 + 2 files changed, 8 insertions(+) diff --git a/contributors.md b/contributors.md index 8f21e03b90..41b3b860b7 100644 --- a/contributors.md +++ b/contributors.md @@ -49,8 +49,15 @@ my employer or other contracts, and that I license this work under an [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0.html) license. +### Name: ### +Gilberto T. Garcia Jr + +### Email: ### +ggarcia at eureka dot inf dot br + ### Name: ### Marko Elezović ### Email: ### marko at element dot hr + diff --git a/persistence/db/src/main/scala/net/liftweb/db/DB.scala b/persistence/db/src/main/scala/net/liftweb/db/DB.scala index a58725b7b4..bd3b4423e4 100644 --- a/persistence/db/src/main/scala/net/liftweb/db/DB.scala +++ b/persistence/db/src/main/scala/net/liftweb/db/DB.scala @@ -885,6 +885,7 @@ trait DB extends Loggable { "layer", "level", "like", + "limit", // reserved word for PostgreSQL "limited", "link", "lists", From aa359caf4570289938cf6fce879a9ab1365dae1b Mon Sep 17 00:00:00 2001 From: Marko Elezovic Date: Tue, 13 Nov 2012 02:14:39 +0100 Subject: [PATCH 0237/1949] Add prompt expression to JsCommands --- contributors.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/contributors.md b/contributors.md index 41b3b860b7..e62bedf398 100644 --- a/contributors.md +++ b/contributors.md @@ -48,7 +48,6 @@ claims on this work including, but not limited to, any agreements I may have wit my employer or other contracts, and that I license this work under an [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0.html) license. - ### Name: ### Gilberto T. Garcia Jr @@ -60,4 +59,3 @@ Marko Elezović ### Email: ### marko at element dot hr - From 54ec185ecc2f9dcf5e0fe5c0656736852042fd52 Mon Sep 17 00:00:00 2001 From: Dave Briccetti Date: Fri, 16 Nov 2012 14:47:22 -0800 Subject: [PATCH 0238/1949] Make some minor comment improvements to SHtml.scala --- .../src/main/scala/net/liftweb/http/SHtml.scala | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala b/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala index cf89836740..7d2dd25aeb 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala @@ -377,7 +377,7 @@ trait SHtml { } /** - * Create an Ajax buttun that when it's pressed it submits an Ajax request and expects back a JSON + * Create an Ajax button that when pressed, submits an Ajax request and expects back a JSON * construct which will be passed to the success function * * @param text -- the name/text of the button @@ -395,7 +395,7 @@ trait SHtml { } /** - * Create an Ajax button. When it's pressed, the function is executed + * Create an Ajax button that when pressed, executes the function * * @param text -- the name/text of the button * @param func -- the function to execute when the button is pushed. Return Noop if nothing changes on the browser. @@ -410,7 +410,7 @@ trait SHtml { } /** - * Create an Ajax buttun that when it's pressed it submits an Ajax request and expects back a JSON + * Create an Ajax button that when pressed, submits an Ajax request and expects back a JSON * construct which will be passed to the success function * * @param text -- the name/text of the button @@ -428,7 +428,7 @@ trait SHtml { } /** - * Create an Ajax button. When it's pressed, the function is executed + * Create an Ajax button that when pressed, executes the function * * @param text -- the name/text of the button * @param jsFunc -- the user function that will be executed. This function will receive as last parameter @@ -444,7 +444,7 @@ trait SHtml { } /** - * Create an Ajax button. When it's pressed, the function is executed + * Create an Ajax button that when pressed, executes the function * * @param text -- the name/text of the button * @param jsFunc -- the user function that will be executed. This function will receive as last parameter @@ -458,7 +458,7 @@ trait SHtml { ajaxButton(Text(text), func, attrs: _*) /** - * Create an Ajax button. When it's pressed, the function is executed + * Create an Ajax button that when pressed, executes the function * * @param text -- the name/text of the button * @param func -- the function to execute when the button is pushed. Return Noop if nothing changes on the browser. From 400cc330ac545e1ebfc38312080975b5fdbef550 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Fri, 9 Nov 2012 18:14:54 -0500 Subject: [PATCH 0239/1949] Add a DateTime serializer to lift-mongodb. This allows the use of Joda Time DateTime objects in lift-mongodb MongoDocuments. --- .../net/liftweb/mongodb/Serializers.scala | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/Serializers.scala b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/Serializers.scala index 049d04a901..37b102c234 100644 --- a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/Serializers.scala +++ b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/Serializers.scala @@ -23,6 +23,8 @@ import java.util.regex.{Pattern, PatternSyntaxException} import org.bson.types.ObjectId +import org.joda.time.DateTime + /* * Provides a way to serialize/de-serialize ObjectIds. * @@ -90,6 +92,28 @@ class DateSerializer extends Serializer[Date] { } } +/* +* Provides a way to serialize/de-serialize joda time DateTimes. +* +* Queries for a Date (dt) using the lift-json DSL look like: +* ("dt" -> ("$dt" -> formats.dateFormat.format(dt))) +*/ +class DateTimeSerializer extends Serializer[DateTime] { + private val DateTimeClass = classOf[DateTime] + + def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), DateTime] = { + case (TypeInfo(DateTimeClass, _), json) => json match { + case JObject(JField("$dt", JString(s)) :: Nil) => + new DateTime(format.dateFormat.parse(s).getOrElse(throw new MappingException("Can't parse "+ s + " to DateTime"))) + case x => throw new MappingException("Can't convert " + x + " to Date") + } + } + + def serialize(implicit format: Formats): PartialFunction[Any, JValue] = { + case x: DateTime => Meta.dateAsJValue(x.toDate, format) + } +} + /* * Provides a way to serialize/de-serialize UUIDs. * From c026da264e8855747379586b10ae3bd95df29a8a Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Tue, 13 Nov 2012 18:35:42 -0500 Subject: [PATCH 0240/1949] Add DateTimeSerializer to allFormats. --- .../mongodb/src/main/scala/net/liftweb/mongodb/MongoMeta.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/MongoMeta.scala b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/MongoMeta.scala index 31bfdc9d00..86a3dca877 100644 --- a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/MongoMeta.scala +++ b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/MongoMeta.scala @@ -27,7 +27,7 @@ trait JsonFormats { implicit lazy val _formats: Formats = formats - lazy val allFormats = DefaultFormats.lossless + new ObjectIdSerializer + new DateSerializer + new PatternSerializer + new UUIDSerializer + lazy val allFormats = DefaultFormats.lossless + new ObjectIdSerializer + new DateSerializer + new DateTimeSerializer + new PatternSerializer + new UUIDSerializer } /* From 0f4f82d9988bff0fae5e8b6fcfbdd7550ef53d46 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Tue, 13 Nov 2012 18:35:52 -0500 Subject: [PATCH 0241/1949] Complete specs for MongoDB serializers. Includes specs for pattern serialization, UUID serialization, and DateTime serialization. --- .../mongodb/CustomSerializersSpec.scala | 140 ++++++++++++++++-- 1 file changed, 126 insertions(+), 14 deletions(-) diff --git a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/CustomSerializersSpec.scala b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/CustomSerializersSpec.scala index cf53193974..3f0de3256c 100644 --- a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/CustomSerializersSpec.scala +++ b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/CustomSerializersSpec.scala @@ -17,32 +17,77 @@ package net.liftweb package mongodb -import java.util.{Calendar, Date, TimeZone} +import java.util.{Calendar, Date, TimeZone, UUID} +import java.util.regex.Pattern import org.bson.types.ObjectId +import org.joda.time.{Instant, DateTime} + import org.specs2.mutable.Specification package customserializersspecs { /* - * Date as String + * ObjectId as String */ - case class Person(_id: String, birthDate: Date) + case class Person(_id: String) extends MongoDocument[Person] { def meta = Person } object Person extends MongoDocumentMeta[Person] + /* + * ObjectId as ObjectId + */ + case class PersonWithObjectId(_id: ObjectId) + extends MongoDocument[PersonWithObjectId] + { + def meta = PersonWithObjectId + } + object PersonWithObjectId extends MongoDocumentMeta[PersonWithObjectId] { + override def formats = allFormats + } + + /* + * Pattern as Pattern + */ + case class PersonWithPattern(_id:ObjectId, pattern: Pattern) extends MongoDocument[PersonWithPattern] { + def meta = PersonWithPattern + } + object PersonWithPattern extends MongoDocumentMeta[PersonWithPattern] { + override def formats = allFormats + } + /* * Date as Date */ - case class Person2(_id: ObjectId, birthDate: Date) extends MongoDocument[Person2] { - def meta = Person2 + case class PersonWithDate(_id: ObjectId, birthDate: Date) extends MongoDocument[PersonWithDate] { + def meta = PersonWithDate + } + object PersonWithDate extends MongoDocumentMeta[PersonWithDate] { + override def formats = allFormats + } + + /* + * DateTime as DateTime + */ + case class PersonWithDateTime(_id: ObjectId, birthDate: DateTime) extends MongoDocument[PersonWithDateTime] { + def meta = PersonWithDateTime + } + object PersonWithDateTime extends MongoDocumentMeta[PersonWithDateTime] { + override def formats = allFormats } - object Person2 extends MongoDocumentMeta[Person2] { + + /* + * UUID as UUID + */ + case class PersonWithUUID(_id: UUID) extends MongoDocument[PersonWithUUID] { + def meta = PersonWithUUID + } + object PersonWithUUID extends MongoDocumentMeta[PersonWithUUID] { override def formats = allFormats } } @@ -59,14 +104,11 @@ class CustomSerializersSpec extends Specification with MongoTestKit { val utc = TimeZone.getTimeZone("UTC") "CustomSerializers" should { - "handle Date as String value" in { + "handle ObjectId as String value" in { checkMongoIsRunning // test data - val bdjack = Calendar.getInstance - bdjack.setTimeZone(utc) - bdjack.setTimeInMillis(1288742280000L) - val jack = Person(ObjectId.get.toString, bdjack.getTime) + val jack = Person(ObjectId.get.toString) // save the Person document jack.save @@ -76,6 +118,59 @@ class CustomSerializersSpec extends Specification with MongoTestKit { jack2.isDefined must_== true jack2.toList map { j => j._id mustEqual jack._id + } + } + + "handle ObjectId as ObjectId value using ObjectIdSerializer" in { + checkMongoIsRunning + + // test data + val jack = PersonWithObjectId(ObjectId.get) + + // save the PersonWithObjectId document + jack.save + + // retrieve it and compare + val jack2 = PersonWithObjectId.find(jack._id) + jack2.isDefined must_== true + jack2.toList map { j => + j._id mustEqual jack._id + } + } + + "handle Pattern as Pattern value using PatternSerializer" in { + checkMongoIsRunning + + // test data + val pattern = Pattern.compile("(?idmsux-idmsux)m(a)gi(?:ic)?[a-zA-Z]+boom") + val jack = PersonWithPattern(ObjectId.get, pattern) + + // save the PersonWithPattern document + jack.save + + // retrieve it and compare + val jack2 = PersonWithPattern.find(jack._id) + jack2.isDefined must_== true + jack2.toList map { j => + j.pattern.pattern mustEqual jack.pattern.pattern + j.pattern.flags mustEqual jack.pattern.flags + } + } + + "handle DateTime as DateTime value using DateTimeSerializer" in { + checkMongoIsRunning + + // test data + val birthday = (new Instant(1288742280000L)).toDateTime + val jack = PersonWithDateTime(ObjectId.get, birthday) + + // save the Person document + jack.save + + // retrieve it and compare + val findJack = PersonWithDateTime.find(jack._id) + findJack.isDefined must_== true + findJack.toList map { j => j.birthDate mustEqual jack.birthDate } } @@ -87,18 +182,35 @@ class CustomSerializersSpec extends Specification with MongoTestKit { val bdjack = Calendar.getInstance bdjack.setTimeZone(utc) bdjack.setTimeInMillis(1288742280000L) - val jack = Person2(ObjectId.get, bdjack.getTime) + val jack = PersonWithDate(ObjectId.get, bdjack.getTime) // save the Person document jack.save // retrieve it and compare - val findJack = Person2.find(jack._id) + val findJack = PersonWithDate.find(jack._id) findJack.isDefined must_== true findJack.toList map { j => - j._id mustEqual jack._id j.birthDate mustEqual jack.birthDate } } + + "handle UUID as UUID value using UUIDSerializer" in { + checkMongoIsRunning + + // test data + val uuid = UUID.randomUUID + val jack = PersonWithUUID(uuid) + + // save the Person document + jack.save + + // retrieve it and compare + val findJack = PersonWithUUID.find(jack._id) + findJack.isDefined must_== true + findJack.toList map { j => + j._id mustEqual jack._id + } + } } } From 9281228c8a1a8a6b0d14ae524cbed435d05a36c9 Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Thu, 18 Oct 2012 16:12:42 -0400 Subject: [PATCH 0242/1949] Fixed #1333 - replace open_! for openOrThrowException() --- .../scala/net/liftweb/actor/LiftActor.scala | 63 +++++++++---------- .../main/scala/net/liftweb/common/LRU.scala | 2 +- .../scala/net/liftweb/common/BoxSpec.scala | 6 +- .../main/scala/net/liftweb/util/FatLazy.scala | 2 +- .../scala/net/liftweb/util/JSONParser.scala | 1 - .../net/liftweb/util/BindHelpersSpec.scala | 2 +- .../net/liftweb/util/BundleBuilderSpec.scala | 4 +- .../net/liftweb/util/ClassHelpersSpec.scala | 4 +- .../net/liftweb/util/Html5ParserSpec.scala | 14 ++--- .../net/liftweb/util/JsonParserSpec.scala | 10 +-- .../scala/net/liftweb/util/MailerSpec.scala | 2 +- .../liftweb/util/PCDataXmlParserSpec.scala | 6 +- .../net/liftweb/util/XmlParserSpec.scala | 2 +- .../net/liftweb/couchdb/CouchRecord.scala | 2 +- .../scala/net/liftweb/couchdb/Database.scala | 4 +- .../net/liftweb/couchdb/DocumentHelpers.scala | 8 ++- .../liftweb/couchdb/CouchDocumentSpec.scala | 2 +- .../net/liftweb/couchdb/CouchQuerySpec.scala | 18 +++--- .../net/liftweb/couchdb/CouchRecordSpec.scala | 23 ++++--- .../scala/net/liftweb/mapper/AjaxMapper.scala | 2 +- .../net/liftweb/mapper/HasManyThrough.scala | 2 +- .../net/liftweb/mapper/MappedField.scala | 10 +-- .../net/liftweb/mapper/MappedForeignKey.scala | 2 +- .../scala/net/liftweb/mapper/MetaMapper.scala | 10 +-- .../net/liftweb/mapper/view/ModelView.scala | 3 +- .../net/liftweb/mapper/view/TableEditor.scala | 2 +- .../scala/net/liftweb/mapper/DbSpec.scala | 6 +- .../liftweb/mapper/MappedBooleanSpec.scala | 6 +- .../liftweb/mapper/MappedDecimalSpec.scala | 6 +- .../mapper/MappedLongForeignKeySpec.scala | 2 +- .../scala/net/liftweb/mapper/MapperSpec.scala | 44 ++++++------- .../mongodb/record/MongoMetaRecord.scala | 2 +- .../record/MongoRecordExamplesSpec.scala | 2 +- .../scala/net/liftweb/proto/Crudify.scala | 6 +- .../scala/net/liftweb/proto/ProtoUser.scala | 6 +- .../net/liftweb/http/testing/TestRunner.scala | 2 +- .../net/liftweb/builtin/snippet/Comet.scala | 4 +- .../scala/net/liftweb/http/LiftScreen.scala | 8 +-- .../scala/net/liftweb/http/LiftServlet.scala | 6 +- .../scala/net/liftweb/http/LiftSession.scala | 4 +- .../src/main/scala/net/liftweb/http/Req.scala | 2 +- .../src/main/scala/net/liftweb/http/S.scala | 12 ++-- .../scala/net/liftweb/http/Templates.scala | 4 +- .../provider/servlet/HTTPRequestServlet.scala | 4 +- .../net/liftweb/http/rest/RestHelper.scala | 2 +- .../test/scala/net/liftweb/http/ReqSpec.scala | 2 +- .../scala/net/liftweb/http/SnippetSpec.scala | 30 ++++----- .../net/liftweb/sitemap/MenuDSLSpec.scala | 2 +- .../net/liftweb/webapptest/OneShot.scala | 12 ++-- .../net/liftweb/webapptest/ToHeadUsages.scala | 4 +- .../scala/net/liftweb/wizard/WizardSpec.scala | 22 +++---- 51 files changed, 209 insertions(+), 197 deletions(-) diff --git a/core/actor/src/main/scala/net/liftweb/actor/LiftActor.scala b/core/actor/src/main/scala/net/liftweb/actor/LiftActor.scala index 182ce838f4..9c030e1c29 100644 --- a/core/actor/src/main/scala/net/liftweb/actor/LiftActor.scala +++ b/core/actor/src/main/scala/net/liftweb/actor/LiftActor.scala @@ -211,20 +211,19 @@ trait SpecializedLiftActor[T] extends SimpleActor[T] { } } -/** - * A list of LoanWrappers that will be executed around the evaluation of mailboxes - */ -protected def aroundLoans: List[CommonLoanWrapper] = Nil - -/** - * You can wrap calls around the evaluation of the mailbox. This allows you to set up - * the environment - */ -protected def around[R](f: => R): R = aroundLoans match { - case Nil => f - case xs => CommonLoanWrapper(xs)(f) -} + /** + * A list of LoanWrappers that will be executed around the evaluation of mailboxes + */ + protected def aroundLoans: List[CommonLoanWrapper] = Nil + /** + * You can wrap calls around the evaluation of the mailbox. This allows you to set up + * the environment + */ + protected def around[R](f: => R): R = aroundLoans match { + case Nil => f + case xs => CommonLoanWrapper(xs)(f) + } private def proc2(ignoreProcessing: Boolean) { var clearProcessing = true baseMailbox.synchronized { @@ -257,27 +256,27 @@ protected def around[R](f: => R): R = aroundLoans match { while (keepOnDoingHighPriory) { val hiPriPfBox = highPriorityReceive - if (hiPriPfBox.isDefined) { - val hiPriPf = hiPriPfBox.open_! - findMailboxItem(baseMailbox.next, mb => testTranslate(hiPriPf.isDefinedAt)(mb.item)) match { - case Full(mb) => - mb.remove() - try { - execTranslate(hiPriPf)(mb.item) - } catch { - case e: Exception => if (eh.isDefinedAt(e)) eh(e) - } - case _ => - baseMailbox.synchronized { - if (msgList.isEmpty) { - keepOnDoingHighPriory = false + hiPriPfBox.map{ + hiPriPf => + findMailboxItem(baseMailbox.next, mb => testTranslate(hiPriPf.isDefinedAt)(mb.item)) match { + case Full(mb) => + mb.remove() + try { + execTranslate(hiPriPf)(mb.item) + } catch { + case e: Exception => if (eh.isDefinedAt(e)) eh(e) } - else { - putListIntoMB() + case _ => + baseMailbox.synchronized { + if (msgList.isEmpty) { + keepOnDoingHighPriory = false + } + else { + putListIntoMB() + } } - } - } } - else {keepOnDoingHighPriory = false} + } + }.openOr{keepOnDoingHighPriory = false} } val pf = messageHandler diff --git a/core/common/src/main/scala/net/liftweb/common/LRU.scala b/core/common/src/main/scala/net/liftweb/common/LRU.scala index 9fe0ec913d..11a3b9252d 100644 --- a/core/common/src/main/scala/net/liftweb/common/LRU.scala +++ b/core/common/src/main/scala/net/liftweb/common/LRU.scala @@ -85,7 +85,7 @@ class LRUMap[K, V](initMaxSize: Int, loadFactor: Box[Float], expiredFunc: ((K, V Full(v.value2) } - def apply(key: K) = get(key).open_! + def apply(key: K) = get(key).openOrThrowException("Simulating what happens with a regular Map, use contains(key) to check if it is present or not.") def contains(key: K): Boolean = localMap.containsKey(key) diff --git a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala index 8ae9ed345f..1e3ee0c104 100644 --- a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala +++ b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala @@ -71,7 +71,7 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { Empty.isDefined must beFalse } "be implicitly defined from an Option. The open_! method can be used on an Option for example" in { - Some(1).open_! must_== 1 + Some(1).openOrThrowException("This is a test") must_== 1 } "be defined from some legacy code (possibly passing null values). If the passed value is not null, a Full(value) is returned" in { Box.legacyNullTest("s") must_== Full("s") @@ -98,7 +98,7 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { Full(1).isDefined must beTrue } "return its value when opened" in { - Full(1).open_! must_== 1 + Full(1).openOrThrowException("This is a test") must_== 1 } "return its value when opened with openOr(default value)" in { Full(1) openOr 0 must_== 1 @@ -223,7 +223,7 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { Empty.isDefined must beFalse } "throw an exception if opened" in { - {Empty.open_!; ()} must throwA[NullPointerException] + {Empty.openOrThrowException("See what happens?, at least we expect it in this case :)"); ()} must throwA[NullPointerException] } "return a default value if opened with openOr" in { Empty.openOr(1) must_== 1 diff --git a/core/util/src/main/scala/net/liftweb/util/FatLazy.scala b/core/util/src/main/scala/net/liftweb/util/FatLazy.scala index 73fb209019..97a300682c 100644 --- a/core/util/src/main/scala/net/liftweb/util/FatLazy.scala +++ b/core/util/src/main/scala/net/liftweb/util/FatLazy.scala @@ -48,7 +48,7 @@ class FatLazy[T](f: => T) { value match { case Full(v) => v case _ => value = Full(f) - value.open_! + value.openOrThrowException("We just checked that this is a Full box.") } } diff --git a/core/util/src/main/scala/net/liftweb/util/JSONParser.scala b/core/util/src/main/scala/net/liftweb/util/JSONParser.scala index b521562def..36069f2d42 100644 --- a/core/util/src/main/scala/net/liftweb/util/JSONParser.scala +++ b/core/util/src/main/scala/net/liftweb/util/JSONParser.scala @@ -19,7 +19,6 @@ package util import scala.util.parsing.combinator.{Parsers, ImplicitConversions} import common._ - object JSONParser extends SafeSeqParser with ImplicitConversions { implicit def strToInput(in: String): Input = new scala.util.parsing.input.CharArrayReader(in.toCharArray) type Elem = Char diff --git a/core/util/src/test/scala/net/liftweb/util/BindHelpersSpec.scala b/core/util/src/test/scala/net/liftweb/util/BindHelpersSpec.scala index 93990d5e85..2937e29167 100644 --- a/core/util/src/test/scala/net/liftweb/util/BindHelpersSpec.scala +++ b/core/util/src/test/scala/net/liftweb/util/BindHelpersSpec.scala @@ -190,7 +190,7 @@ object BindHelpersSpec extends Specification { e => e.attribute("id"). filter(_.text == "3"). map(i => e) - }.open_! must ==/ () + }.openOrThrowException("Test") must ==/ () } "not find an ide" in { diff --git a/core/util/src/test/scala/net/liftweb/util/BundleBuilderSpec.scala b/core/util/src/test/scala/net/liftweb/util/BundleBuilderSpec.scala index 7aca5b3b57..530a3e12eb 100644 --- a/core/util/src/test/scala/net/liftweb/util/BundleBuilderSpec.scala +++ b/core/util/src/test/scala/net/liftweb/util/BundleBuilderSpec.scala @@ -36,7 +36,7 @@ object BundleBuilderSpec extends Specification {

    Dog
    Chien
    hi
    -
    , Locale.US).open_! +
    , Locale.US).openOrThrowException("Test") b.getObject("dog") must_== "Dog" b.getObject("cat").asInstanceOf[NodeSeq] must ==/ (
    hi
    ) @@ -47,7 +47,7 @@ object BundleBuilderSpec extends Specification {
    Dog
    Chien
    hi
    -
    , Locale.US).open_! +
    , Locale.US).openOrThrowException("Test") b.getObject("dog") must_== "Chien" b.getObject("cat").asInstanceOf[NodeSeq] must ==/ (
    hi
    ) diff --git a/core/util/src/test/scala/net/liftweb/util/ClassHelpersSpec.scala b/core/util/src/test/scala/net/liftweb/util/ClassHelpersSpec.scala index 053df420ad..92a3c96efa 100644 --- a/core/util/src/test/scala/net/liftweb/util/ClassHelpersSpec.scala +++ b/core/util/src/test/scala/net/liftweb/util/ClassHelpersSpec.scala @@ -174,10 +174,10 @@ object ClassHelpersSpec extends Specification { createInvoker("length", null) must_== Empty } "return a Full can with the function from Unit to a can containing the result of the method to invoke" in { - createInvoker("length", "").open_!.apply().get must_== 0 + createInvoker("length", "").openOrThrowException("Test").apply().get must_== 0 } "The invoker function will throw the cause exception if the method can't be called" in { - createInvoker("get", "").open_!.apply must throwA[Exception] + createInvoker("get", "").openOrThrowException("Test").apply must throwA[Exception] } } diff --git a/core/util/src/test/scala/net/liftweb/util/Html5ParserSpec.scala b/core/util/src/test/scala/net/liftweb/util/Html5ParserSpec.scala index 2b0fef1449..4a8f28d04d 100644 --- a/core/util/src/test/scala/net/liftweb/util/Html5ParserSpec.scala +++ b/core/util/src/test/scala/net/liftweb/util/Html5ParserSpec.scala @@ -54,12 +54,12 @@ object Html5ParserSpec extends Specification with PendingUntilFixed with Html5Pa val (page1, page2, page3) = (new String(p._1), new String(p._2), new String(p._3)) "parse valid page type1" in { - val parsed = parse(page1).open_! + val parsed = parse(page1).openOrThrowException("Test") (parsed \\ "script").length must be >= 4 } "parse valid page type2" in { - val parsed = parse(page2).open_! + val parsed = parse(page2).openOrThrowException("Test") (parsed \\ "script").length must be >= 4 } @@ -76,7 +76,7 @@ object Html5ParserSpec extends Specification with PendingUntilFixed with Html5Pa } "change to " in { - val parsed = parse("
    123
    ").open_! + val parsed = parse("
    123
    ").openOrThrowException("Test") val heads = parsed \\ "head" heads.length must_== 1 heads.text must_== "123" @@ -85,17 +85,17 @@ object Html5ParserSpec extends Specification with PendingUntilFixed with Html5Pa "Parse stuff with lift: namespace" in { val parsed = parse("""
    """) - val e = parsed.open_!.asInstanceOf[Elem] + val e = parsed.openOrThrowException("Test").asInstanceOf[Elem] e.prefix must_== "lift" e.label must_== "surround" - (parsed.open_! \ "@with").text must_== "dog" + (parsed.openOrThrowException("Test") \ "@with").text must_== "dog" } "Parse stuff without lift: namespace" in { val parsed = parse("""
    """) - val e = parsed.open_!.asInstanceOf[Elem] + val e = parsed.openOrThrowException("Test").asInstanceOf[Elem] e.label must_== "div" - (parsed.open_! \ "@with").text must_== "dog" + (parsed.openOrThrowException("Test") \ "@with").text must_== "dog" } } diff --git a/core/util/src/test/scala/net/liftweb/util/JsonParserSpec.scala b/core/util/src/test/scala/net/liftweb/util/JsonParserSpec.scala index 6e09b8ce99..abb3031ea8 100644 --- a/core/util/src/test/scala/net/liftweb/util/JsonParserSpec.scala +++ b/core/util/src/test/scala/net/liftweb/util/JsonParserSpec.scala @@ -95,26 +95,26 @@ object JsonParserSpec extends Specification { "Parse doubles" in { val p2 = JSONParser.parse("51.348484") p2.isDefined must be_==(true) - p2.open_! must_== 51.348484 + p2.openOrThrowException("Test") must_== 51.348484 } "Parse negative doubles" in { val p2 = JSONParser.parse("-51.348484") p2.isDefined must be_==(true) - p2.open_! must_== -51.348484 + p2.openOrThrowException("Test") must_== -51.348484 } "Parse negative longs" in { val p2 = JSONParser.parse("-517272833222") p2.isDefined must be_==(true) - p2.open_! must_== -517272833222L + p2.openOrThrowException("Test") must_== -517272833222L } "complex JSON objects #2" in { val p2 = JSONParser.parse( """{"command":"setPoint","params":{"mf":51.3256123,"$a":-0.6379592,"x":-0.6379592,"y":51.3256123}}""") p2.isDefined must be_==(true) - val params = p2.open_!.asInstanceOf[Map[String, Map[String, Any]]].apply("params") + val params = p2.openOrThrowException("Test").asInstanceOf[Map[String, Map[String, Any]]].apply("params") params("mf") must_== 51.3256123D params("$a") must_== -0.6379592D params("y") must_== 51.3256123D @@ -132,7 +132,7 @@ object JsonParserSpec extends Specification { val p2 = JSONParser.parse( """-0.5033""") p2.isDefined must be_==(true) - p2.open_! must_== -0.5033 + p2.openOrThrowException("Test") must_== -0.5033 } diff --git a/core/util/src/test/scala/net/liftweb/util/MailerSpec.scala b/core/util/src/test/scala/net/liftweb/util/MailerSpec.scala index d23de56a40..59ddd77706 100644 --- a/core/util/src/test/scala/net/liftweb/util/MailerSpec.scala +++ b/core/util/src/test/scala/net/liftweb/util/MailerSpec.scala @@ -44,7 +44,7 @@ object MailerSpec extends Specification { while (lastMessage.isEmpty) { MailerSpec.this.wait(100) } - lastMessage.open_! + lastMessage.openOrThrowException("Test") } } diff --git a/core/util/src/test/scala/net/liftweb/util/PCDataXmlParserSpec.scala b/core/util/src/test/scala/net/liftweb/util/PCDataXmlParserSpec.scala index 6d7d349c61..b3aa3168d7 100644 --- a/core/util/src/test/scala/net/liftweb/util/PCDataXmlParserSpec.scala +++ b/core/util/src/test/scala/net/liftweb/util/PCDataXmlParserSpec.scala @@ -65,15 +65,15 @@ val data3 = """ "PCDataMarkupParser" should { "Parse a document with whitespace" in { - PCDataXmlParser(data1).open_! must ==/ (dude) + PCDataXmlParser(data1).openOrThrowException("Test") must ==/ (dude) } "Parse a document with doctype" in { - PCDataXmlParser(data2).open_! must ==/ (dude) + PCDataXmlParser(data2).openOrThrowException("Test") must ==/ (dude) } "Parse a document with xml and doctype" in { - PCDataXmlParser(data3).open_!.apply(0).label must_== "html" + PCDataXmlParser(data3).openOrThrowException("Test").apply(0).label must_== "html" } } diff --git a/core/util/src/test/scala/net/liftweb/util/XmlParserSpec.scala b/core/util/src/test/scala/net/liftweb/util/XmlParserSpec.scala index 750311e973..8597ae9bf2 100644 --- a/core/util/src/test/scala/net/liftweb/util/XmlParserSpec.scala +++ b/core/util/src/test/scala/net/liftweb/util/XmlParserSpec.scala @@ -43,7 +43,7 @@ object XmlParserSpec extends Specification { val bis = new ByteArrayInputStream(actual.toString.getBytes("UTF-8")) - val parsed = PCDataXmlParser(bis).open_! + val parsed = PCDataXmlParser(bis).openOrThrowException("Test") parsed must ==/(expected) } diff --git a/persistence/couchdb/src/main/scala/net/liftweb/couchdb/CouchRecord.scala b/persistence/couchdb/src/main/scala/net/liftweb/couchdb/CouchRecord.scala index cc9112bd22..eff437ba21 100644 --- a/persistence/couchdb/src/main/scala/net/liftweb/couchdb/CouchRecord.scala +++ b/persistence/couchdb/src/main/scala/net/liftweb/couchdb/CouchRecord.scala @@ -89,7 +89,7 @@ trait CouchRecord[MyType <: CouchRecord[MyType]] extends JSONRecord[MyType] { case Full(database) => database case _ => _database = Full(_calcDatabase) - _database.open_! + _database.openOrThrowException("We just made this a Full Box") } /** Set the current database (if any) for this record */ diff --git a/persistence/couchdb/src/main/scala/net/liftweb/couchdb/Database.scala b/persistence/couchdb/src/main/scala/net/liftweb/couchdb/Database.scala index 7bbf098b0f..3f773ba7ac 100644 --- a/persistence/couchdb/src/main/scala/net/liftweb/couchdb/Database.scala +++ b/persistence/couchdb/src/main/scala/net/liftweb/couchdb/Database.scala @@ -69,7 +69,7 @@ trait Document extends Request with FetchableAsJObject { def @@ (rev: String): DocumentRevision = at(rev) /** Refine to a particular revision of the document by getting _rev from a given JObject. */ - def at(doc: JObject): DocumentRevision = at(doc._rev.open_!) + def at(doc: JObject): DocumentRevision = at(doc._rev.openOrThrowException("legacy code")) /** Alias for at */ def @@ (doc: JObject): DocumentRevision = at(doc) @@ -198,7 +198,7 @@ class Database(couch: Request, database: String) extends Request(couch / databas def apply(id: String): Document = new Request(this / id) with Document { } /** Access a particular document in the database with _id from a given JObject */ - def apply(doc: JObject): Document = this(doc._id.open_!.s) + def apply(doc: JObject): Document = this(doc._id.openOrThrowException("legacy code").s) /** Access a series of documents by ID. */ def apply(ids: Seq[String]): AllDocs = all.includeDocs.keys(ids.map(JString): _*) diff --git a/persistence/couchdb/src/main/scala/net/liftweb/couchdb/DocumentHelpers.scala b/persistence/couchdb/src/main/scala/net/liftweb/couchdb/DocumentHelpers.scala index c28df7dcf2..872b7d8a34 100644 --- a/persistence/couchdb/src/main/scala/net/liftweb/couchdb/DocumentHelpers.scala +++ b/persistence/couchdb/src/main/scala/net/liftweb/couchdb/DocumentHelpers.scala @@ -27,7 +27,13 @@ object DocumentHelpers { def forceStore(http: Http, database: Database, doc: JObject): Box[JObject] = tryo(http(database(doc) fetch)) match { case Full(existingDoc) => - tryo(http(database store updateIdAndRev(doc, existingDoc._id.open_!, existingDoc._rev.open_!))).flatMap(b => b) + tryo{ + http(database.store(updateIdAndRev( + doc, + existingDoc._id.openOrThrowException("We just got this document, so it has an id"), + existingDoc._rev.openOrThrowException("We just got this document, so it has a rev")))) + }.flatMap(b => b) + case Failure(_, Full(StatusCode(404, _)), _) => tryo(http(database store doc)).flatMap(b => b) diff --git a/persistence/couchdb/src/test/scala/net/liftweb/couchdb/CouchDocumentSpec.scala b/persistence/couchdb/src/test/scala/net/liftweb/couchdb/CouchDocumentSpec.scala index ed616d6f79..6c4b210cb7 100644 --- a/persistence/couchdb/src/test/scala/net/liftweb/couchdb/CouchDocumentSpec.scala +++ b/persistence/couchdb/src/test/scala/net/liftweb/couchdb/CouchDocumentSpec.scala @@ -47,7 +47,7 @@ object CouchDocumentSpec extends Specification { private final def verifyAndOpen[A](b: Box[A]): A = { b.isDefined must_== true - b.open_! + b.openOrThrowException("This is a test") } "A document" should { diff --git a/persistence/couchdb/src/test/scala/net/liftweb/couchdb/CouchQuerySpec.scala b/persistence/couchdb/src/test/scala/net/liftweb/couchdb/CouchQuerySpec.scala index f34d168c43..ae09660853 100644 --- a/persistence/couchdb/src/test/scala/net/liftweb/couchdb/CouchQuerySpec.scala +++ b/persistence/couchdb/src/test/scala/net/liftweb/couchdb/CouchQuerySpec.scala @@ -43,7 +43,7 @@ object CouchQuerySpec extends Specification { private def verifyAndOpen[A](b: Box[A]): A = { b.isDefined must_== true - b.open_! + b.openOrThrowException("This is a test") } "Queries" should { @@ -98,7 +98,11 @@ object CouchQuerySpec extends Specification { val (design, docs) = prep(http, database) val students = findStudents(docs) - verifyAndOpen(http(database(List(students(0)._id.open_!, students(3)._id.open_!, students(5)._id.open_!)) query)) must beLike { + verifyAndOpen(http(database(List( + students(0)._id.openOrThrowException("This is a test"), + students(3)._id.openOrThrowException("This is a test"), + students(5)._id.openOrThrowException("This is a test")) + ) query)) must beLike { case QueryResults(Full(count), Full(offset), rows) => sortedAndPrintedValues(rows.flatMap(_.doc).toList) must_== sortedAndPrintedValues(students(0)::students(3)::students(5)::Nil) } @@ -125,7 +129,7 @@ object CouchQuerySpec extends Specification { verifyAndOpen(http(database.design("test").view("students_by_age").from(11) query)) must beLike { case QueryResults(_, _, rows) => (rows.flatMap(_.value.asA[JObject]).toList.sortWith(compareName) must_== - students.filter(_.getInt("age").map(_ >= 11).open_!).sortWith(compareName)) + students.filter(_.getInt("age").map(_ >= 11).openOrThrowException("This is a test")).sortWith(compareName)) } } @@ -137,7 +141,7 @@ object CouchQuerySpec extends Specification { verifyAndOpen(http(database.design("test").view("students_by_age").to(12) query)) must beLike { case QueryResults(_, _, rows) => (rows.flatMap(_.value.asA[JObject]).toList.sortWith(compareName) must_== - students.filter(_.getInt("age").map(_ <= 12).open_!).sortWith(compareName)) + students.filter(_.getInt("age").map(_ <= 12).openOrThrowException("This is a test")).sortWith(compareName)) } } @@ -150,7 +154,7 @@ object CouchQuerySpec extends Specification { case QueryResults(_, _, rows) => rows.length must_== 2 (rows.flatMap(_.value.asA[JObject]).toList.sortWith(compareName) must_== - students.filter(_.getInt("age").map(_ == 11).open_!)) + students.filter(_.getInt("age").map(_ == 11).openOrThrowException("This is a test"))) } } @@ -172,7 +176,7 @@ object CouchQuerySpec extends Specification { case QueryResults(_, _, rows) => rows.length must_== 3 (rows.flatMap(_.value.asA[JObject]).toList.sortWith(compareName) must_== - students.filter(_.getInt("age").map(_ <= 11).open_!)) + students.filter(_.getInt("age").map(_ <= 11).openOrThrowException("This is a test"))) } } @@ -185,7 +189,7 @@ object CouchQuerySpec extends Specification { case QueryResults(_, _, rows) => rows.length must_== 4 (rows.flatMap(_.value.asA[JObject]).toList must_== - students.filter(_.getInt("age").map(age => age >= 11 && age <= 12).open_!)) + students.filter(_.getInt("age").map(age => age >= 11 && age <= 12).openOrThrowException("This is a test"))) } } } diff --git a/persistence/couchdb/src/test/scala/net/liftweb/couchdb/CouchRecordSpec.scala b/persistence/couchdb/src/test/scala/net/liftweb/couchdb/CouchRecordSpec.scala index 2da027d618..b95c8e2c2e 100644 --- a/persistence/couchdb/src/test/scala/net/liftweb/couchdb/CouchRecordSpec.scala +++ b/persistence/couchdb/src/test/scala/net/liftweb/couchdb/CouchRecordSpec.scala @@ -108,7 +108,7 @@ object CouchRecordSpec extends Specification { newRec.id.valueBox.isDefined must_== true newRec.rev.valueBox.isDefined must_== true - val Full(foundRec) = Person.fetch(newRec.id.valueBox.open_!) + val Full(foundRec) = Person.fetch(newRec.id.valueBox.openOrThrowException("This is a test")) assertEqualPerson(foundRec, testRec1) foundRec.id.valueBox must_== newRec.id.valueBox foundRec.rev.valueBox must_== newRec.rev.valueBox @@ -119,7 +119,7 @@ object CouchRecordSpec extends Specification { val newRec = testRec1 newRec save - val foundDoc = Http(defaultDatabase(newRec.id.valueBox.open_!) fetch) + val foundDoc = Http(defaultDatabase(newRec.id.valueBox.openOrThrowException("This is a test")) fetch) compact(render(stripIdAndRev(foundDoc))) must_== compact(render(testDoc1)) } @@ -129,7 +129,7 @@ object CouchRecordSpec extends Specification { newRec.save newRec.id.valueBox.isDefined must_== true - val id = newRec.id.valueBox.open_! + val id = newRec.id.valueBox.openOrThrowException("This is a test") Person.fetch(id).isDefined must_== true newRec.delete_!.isDefined must_== true @@ -137,7 +137,7 @@ object CouchRecordSpec extends Specification { newRec.delete_!.isDefined must_== false newRec.save - Http(defaultDatabase(newRec.id.valueBox.open_!) @@ newRec.rev.valueBox.open_! delete) + Http(defaultDatabase(newRec.id.valueBox.openOrThrowException("This is a test")) @@ newRec.rev.valueBox.openOrThrowException("This is a test") delete) newRec.delete_!.isDefined must_== false } @@ -155,7 +155,8 @@ object CouchRecordSpec extends Specification { val expectedRows = newRec1::newRec3::Nil - Person.fetchMany(newRec1.id.valueBox.open_!, newRec3.id.valueBox.open_!).map(_.toList) must beLike { + Person.fetchMany(newRec1.id.valueBox.openOrThrowException("This is a test"), + newRec3.id.valueBox.openOrThrowException("This is a test")).map(_.toList) must beLike { case Full(foundRows) => assertEqualRows(foundRows, expectedRows); 1 must_== 1 } } @@ -228,14 +229,14 @@ object CouchRecordSpec extends Specification { newRec.saved_? must_== true - val foundRecBox = Person.fetchFrom(database2, newRec.id.valueBox.open_!) + val foundRecBox: Box[Person] = newRec.id.valueBox.flatMap{ id => Person.fetchFrom(database2, id)} foundRecBox.isDefined must_== true val Full(foundRec) = foundRecBox assertEqualPerson(foundRec, testRec1) foundRec.id.valueBox must_== newRec.id.valueBox foundRec.rev.valueBox must_== newRec.rev.valueBox - Person.fetch(newRec.id.valueBox.open_!).isDefined must_== false + newRec.id.valueBox.flatMap(id => Person.fetch(id)).isDefined must_== false } "support multiple databases for fetching in bulk" in { @@ -258,11 +259,15 @@ object CouchRecordSpec extends Specification { val expectedRows = newRec1::newRec3::Nil - Person.fetchManyFrom(database2, newRec1.id.valueBox.open_!, newRec3.id.valueBox.open_!).map(_.toList) must beLike { + Person.fetchManyFrom(database2, + newRec1.id.valueBox.openOrThrowException("This is a test"), + newRec3.id.valueBox.openOrThrowException("This is a test") + ).map(_.toList) must beLike { case Full(foundRows) => assertEqualRows(foundRows, expectedRows); 1 must_== 1 } - Person.fetchMany(newRec1.id.valueBox.open_!, newRec3.id.valueBox.open_!) must beLike { + Person.fetchMany(newRec1.id.valueBox.openOrThrowException("This is a test"), + newRec3.id.valueBox.openOrThrowException("This is a test")) must beLike { case Full(seq) => seq.isEmpty must_== true } } diff --git a/persistence/mapper/src/main/scala/net/liftweb/mapper/AjaxMapper.scala b/persistence/mapper/src/main/scala/net/liftweb/mapper/AjaxMapper.scala index cfccf76072..c9243a5b3b 100644 --- a/persistence/mapper/src/main/scala/net/liftweb/mapper/AjaxMapper.scala +++ b/persistence/mapper/src/main/scala/net/liftweb/mapper/AjaxMapper.scala @@ -29,7 +29,7 @@ trait AjaxEditableField[FieldType,OwnerType <: Mapper[OwnerType]] extends Mapped if (editableField) { { toForm.map { form => - SHtml.ajaxEditable(super.asHtml, toForm.open_!, () => {fieldOwner.save; onSave; net.liftweb.http.js.JsCmds.Noop}) + SHtml.ajaxEditable(super.asHtml, form, () => {fieldOwner.save; onSave; net.liftweb.http.js.JsCmds.Noop}) } openOr super.asHtml } } else { diff --git a/persistence/mapper/src/main/scala/net/liftweb/mapper/HasManyThrough.scala b/persistence/mapper/src/main/scala/net/liftweb/mapper/HasManyThrough.scala index fdc5fa76b9..fedbf858b4 100644 --- a/persistence/mapper/src/main/scala/net/liftweb/mapper/HasManyThrough.scala +++ b/persistence/mapper/src/main/scala/net/liftweb/mapper/HasManyThrough.scala @@ -37,7 +37,7 @@ class HasManyThrough[From <: KeyedMapper[ThroughType, From], DB.use(owner.connectionIdentifier) { conn => val query = "SELECT DISTINCT "+otherSingleton._dbTableNameLC+".* FROM "+otherSingleton._dbTableNameLC+","+ through._dbTableNameLC+" WHERE "+ - otherSingleton._dbTableNameLC+"."+otherSingleton.indexedField(otherSingleton.asInstanceOf[To]).open_!._dbColumnNameLC+" = "+ + otherSingleton._dbTableNameLC+"."+otherSingleton.indexedField(otherSingleton.asInstanceOf[To]).openOrThrowException("legacy code")._dbColumnNameLC+" = "+ through._dbTableNameLC+"."+throughToField._dbColumnNameLC+" AND "+ through._dbTableNameLC+"."+throughFromField._dbColumnNameLC+" = ?" DB.prepareStatement(query, conn) { st => diff --git a/persistence/mapper/src/main/scala/net/liftweb/mapper/MappedField.scala b/persistence/mapper/src/main/scala/net/liftweb/mapper/MappedField.scala index 0832874534..0918cd35fe 100644 --- a/persistence/mapper/src/main/scala/net/liftweb/mapper/MappedField.scala +++ b/persistence/mapper/src/main/scala/net/liftweb/mapper/MappedField.scala @@ -140,11 +140,11 @@ trait BaseMappedField extends SelectableField with Bindable with MixableMappedFi val name = dbColumnName val conn = DB.currentConnection - if (conn.isDefined) { - val rc = conn.open_! - if (rc.metaData.storesMixedCaseIdentifiers) name - else name.toLowerCase - } else name + conn.map{ + c => + if (c.metaData.storesMixedCaseIdentifiers) name + else name.toLowerCase + }.openOr(name) } /** diff --git a/persistence/mapper/src/main/scala/net/liftweb/mapper/MappedForeignKey.scala b/persistence/mapper/src/main/scala/net/liftweb/mapper/MappedForeignKey.scala index 9b92136bf5..e16fba64e6 100644 --- a/persistence/mapper/src/main/scala/net/liftweb/mapper/MappedForeignKey.scala +++ b/persistence/mapper/src/main/scala/net/liftweb/mapper/MappedForeignKey.scala @@ -127,7 +127,7 @@ with LifecycleCallbacks { override protected def dirty_?(b: Boolean) = synchronized { // issue 165 // invalidate if the primary key has changed Issue 370 if (_obj.isEmpty || (_calcedObj && _obj.isDefined && - _obj.open_!.primaryKeyField.is != this.i_is_!)) { + _obj.openOrThrowException("_obj was just checked as full.").primaryKeyField.is != this.i_is_!)) { _obj = Empty _calcedObj = false } diff --git a/persistence/mapper/src/main/scala/net/liftweb/mapper/MetaMapper.scala b/persistence/mapper/src/main/scala/net/liftweb/mapper/MetaMapper.scala index d12014429b..7ef0a1981d 100644 --- a/persistence/mapper/src/main/scala/net/liftweb/mapper/MetaMapper.scala +++ b/persistence/mapper/src/main/scala/net/liftweb/mapper/MetaMapper.scala @@ -869,7 +869,7 @@ trait MetaMapper[A<:Mapper[A]] extends BaseMetaMapper with Mapper[A] { val ret = if (saved_?(toSave)) { _beforeUpdate(toSave) val ret: Boolean = if (!dirty_?(toSave)) true else { - val ret: Boolean = DB.prepareStatement("UPDATE "+MapperRules.quoteTableName.vend(_dbTableNameLC)+" SET "+whatToSet(toSave)+" WHERE "+thePrimaryKeyField.open_! +" = ?", conn) { + val ret: Boolean = DB.prepareStatement("UPDATE "+MapperRules.quoteTableName.vend(_dbTableNameLC)+" SET "+whatToSet(toSave)+" WHERE "+thePrimaryKeyField.openOrThrowException("Cross your fingers") +" = ?", conn) { st => var colNum = 1 @@ -1083,7 +1083,7 @@ trait MetaMapper[A<:Mapper[A]] extends BaseMetaMapper with Mapper[A] { * A partial function that takes an instance of A and a field name and returns the mapped field */ lazy val fieldMatcher: PartialFunction[(A, String), MappedField[Any, A]] = { - case (actual, fieldName) if _mappedFields.contains(fieldName) => fieldByName[Any](fieldName, actual).open_! // we know this is defined + case (actual, fieldName) if _mappedFields.contains(fieldName) => fieldByName[Any](fieldName, actual).openOrThrowException("we know this is defined") } def createInstance: A = rootClass.newInstance.asInstanceOf[A] @@ -1347,7 +1347,7 @@ trait MetaMapper[A<:Mapper[A]] extends BaseMetaMapper with Mapper[A] { val conn = DB.currentConnection if (conn.isDefined) { - val rc = conn.open_! + val rc = conn.openOrThrowException("We just checked that this is a Full Box") if (rc.metaData.storesMixedCaseIdentifiers) name else name.toLowerCase } else name @@ -2035,7 +2035,7 @@ trait KeyedMetaMapper[Type, A<:KeyedMapper[Type, A]] extends MetaMapper[A] with * @return a mapped object of this metamapper's type */ def editSnippetSetup: A = { - objFromIndexedParam.open_! + objFromIndexedParam.openOrThrowException("Comment says this is broken") } /** * Default setup behavior for the view snippet. BROKEN! MUST OVERRIDE IF @@ -2044,7 +2044,7 @@ trait KeyedMetaMapper[Type, A<:KeyedMapper[Type, A]] extends MetaMapper[A] with * @return a mapped object of this metamapper's type */ def viewSnippetSetup: A = { - objFromIndexedParam.open_! + objFromIndexedParam.openOrThrowException("Comment says this is broken") } /** * Default callback behavior of the edit snippet. Called when the user diff --git a/persistence/mapper/src/main/scala/net/liftweb/mapper/view/ModelView.scala b/persistence/mapper/src/main/scala/net/liftweb/mapper/view/ModelView.scala index 676bf21cfa..8bee93dbba 100644 --- a/persistence/mapper/src/main/scala/net/liftweb/mapper/view/ModelView.scala +++ b/persistence/mapper/src/main/scala/net/liftweb/mapper/view/ModelView.scala @@ -170,7 +170,8 @@ class ModelView[T <: Mapper[T]](var entity: T, val snippet: ModelSnippet[T]) { def edit(name: String) = { entity.fieldByName(name).map { (field: net.liftweb.mapper.MappedField[_,_]) => TheBindParam(name, field.toForm.openOr(field.asHtml)) - }.open_! + }.openOrThrowException("If nobody has complained about this giving a NPE, I'll assume it is safe") + } } diff --git a/persistence/mapper/src/main/scala/net/liftweb/mapper/view/TableEditor.scala b/persistence/mapper/src/main/scala/net/liftweb/mapper/view/TableEditor.scala index 4bc7a5afe9..13d04d8e3e 100644 --- a/persistence/mapper/src/main/scala/net/liftweb/mapper/view/TableEditor.scala +++ b/persistence/mapper/src/main/scala/net/liftweb/mapper/view/TableEditor.scala @@ -207,7 +207,7 @@ package snippet { private def getInstance: Box[TableEditorImpl[_]] = S.attr("table").map(TableEditor.map(_)) def dispatch = { case "edit" => - val o = getInstance.open_! + val o = getInstance.openOrThrowException("if we don't have the table attr, we want the dev to know about it.") o.edit _ } } diff --git a/persistence/mapper/src/test/scala/net/liftweb/mapper/DbSpec.scala b/persistence/mapper/src/test/scala/net/liftweb/mapper/DbSpec.scala index 516edfb94b..a75bd71b88 100644 --- a/persistence/mapper/src/test/scala/net/liftweb/mapper/DbSpec.scala +++ b/persistence/mapper/src/test/scala/net/liftweb/mapper/DbSpec.scala @@ -50,12 +50,12 @@ object DbSpec extends Specification { val session = new LiftSession("hello", "", Empty) val elwood = S.initIfUninitted(session) { - val r = User.find(By(User.firstName, "Elwood")).open_! + val r = User.find(By(User.firstName, "Elwood")) S.queryLog.size must_== 1 r } - statements.size must_==1 - elwood.firstName.is must_== "Elwood" + statements.size must_== 1 + elwood.map( _.firstName.is) must_== Full("Elwood") } } } diff --git a/persistence/mapper/src/test/scala/net/liftweb/mapper/MappedBooleanSpec.scala b/persistence/mapper/src/test/scala/net/liftweb/mapper/MappedBooleanSpec.scala index 9bb68777f9..a426211fcb 100644 --- a/persistence/mapper/src/test/scala/net/liftweb/mapper/MappedBooleanSpec.scala +++ b/persistence/mapper/src/test/scala/net/liftweb/mapper/MappedBooleanSpec.scala @@ -44,8 +44,8 @@ object MappedBooleanSpec extends Specification { val charlie = Dog2.create charlie.isDog(true).save - val read = Dog2.find(charlie.dog2id).open_! - read.dirty_? must_== false + val read = Dog2.find(charlie.dog2id) + read.map(_.dirty_?) must_== Full(false) } "be marked dirty on update if value has changed" in { @@ -53,7 +53,7 @@ object MappedBooleanSpec extends Specification { val charlie = Dog2.create charlie.save - val read = Dog2.find(charlie.dog2id).open_! + val read = Dog2.find(charlie.dog2id).openOrThrowException("This is a test") read.dirty_? must_== false read.isDog(false) read.dirty_? must_== false diff --git a/persistence/mapper/src/test/scala/net/liftweb/mapper/MappedDecimalSpec.scala b/persistence/mapper/src/test/scala/net/liftweb/mapper/MappedDecimalSpec.scala index 831fb459d2..cc5544c03b 100644 --- a/persistence/mapper/src/test/scala/net/liftweb/mapper/MappedDecimalSpec.scala +++ b/persistence/mapper/src/test/scala/net/liftweb/mapper/MappedDecimalSpec.scala @@ -44,8 +44,8 @@ object MappedDecimalSpec extends Specification { val charlie = Dog.create charlie.price(42.42).save - val read = Dog.find(charlie.id).open_! - read.dirty_? must_== false + val read = Dog.find(charlie.id) + read.map(_.dirty_?) must_== Full(false) } "be marked dirty on update" in { @@ -53,7 +53,7 @@ object MappedDecimalSpec extends Specification { val charlie = Dog.create charlie.price(42.42).save - val read = Dog.find(charlie.id).open_! + val read = Dog.find(charlie.id).openOrThrowException("This is a test") read.dirty_? must_== false read.price(100.42) read.price(100.42) diff --git a/persistence/mapper/src/test/scala/net/liftweb/mapper/MappedLongForeignKeySpec.scala b/persistence/mapper/src/test/scala/net/liftweb/mapper/MappedLongForeignKeySpec.scala index cec0327b10..e26e9718b8 100644 --- a/persistence/mapper/src/test/scala/net/liftweb/mapper/MappedLongForeignKeySpec.scala +++ b/persistence/mapper/src/test/scala/net/liftweb/mapper/MappedLongForeignKeySpec.scala @@ -56,7 +56,7 @@ object MappedLongForeignKeySpec extends Specification with org.specs2.specificat ret } dog.owner(user).save - val d2 = Dog.find(dog.id).open_! + val d2 = Dog.find(dog.id).openOrThrowException("Test") d2.id.is must_== user.id.is (d2.owner == user) must_== true (d2.owner == d2) must_== false diff --git a/persistence/mapper/src/test/scala/net/liftweb/mapper/MapperSpec.scala b/persistence/mapper/src/test/scala/net/liftweb/mapper/MapperSpec.scala index 73383b2397..670d92b56e 100644 --- a/persistence/mapper/src/test/scala/net/liftweb/mapper/MapperSpec.scala +++ b/persistence/mapper/src/test/scala/net/liftweb/mapper/MapperSpec.scala @@ -67,10 +67,10 @@ class MapperSpec extends Specification with BeforeExample { ("Mapper for " + provider.name) should { "schemify" in { - val elwood = SampleModel.find(By(SampleModel.firstName, "Elwood")).open_! - val madeline = SampleModel.find(By(SampleModel.firstName, "Madeline")).open_! - val archer = SampleModel.find(By(SampleModel.firstName, "Archer")).open_! - val notNull = SampleModel.find(By(SampleModel.firstName, "NotNull")).open_! + val elwood = SampleModel.find(By(SampleModel.firstName, "Elwood")).openOrThrowException("Test") + val madeline = SampleModel.find(By(SampleModel.firstName, "Madeline")).openOrThrowException("Test") + val archer = SampleModel.find(By(SampleModel.firstName, "Archer")).openOrThrowException("Test") + val notNull = SampleModel.find(By(SampleModel.firstName, "NotNull")).openOrThrowException("Test") elwood.firstName.is must_== "Elwood" madeline.firstName.is must_== "Madeline" @@ -81,7 +81,7 @@ class MapperSpec extends Specification with BeforeExample { val disabled = SampleModel.find(By(SampleModel.status, SampleStatus.Disabled)) - val meow = SampleTag.find(By(SampleTag.tag, "Meow")).open_! + val meow = SampleTag.find(By(SampleTag.tag, "Meow")).openOrThrowException("Test") meow.tag.is must_== "Meow" @@ -133,17 +133,17 @@ class MapperSpec extends Specification with BeforeExample { } "Can JSON decode and write back" in { - val m = SampleModel.find(2).open_! + val m = SampleModel.find(2).openOrThrowException("Test") val json = m.encodeAsJson() val rebuilt = SampleModel.buildFromJson(json) rebuilt.firstName("yak").save - val recalled = SampleModel.find(2).open_! + val recalled = SampleModel.find(2).openOrThrowException("Test") recalled.firstName.is must_== "yak" } "You can put stuff in a Set" in { - val m1 = SampleModel.find(1).open_! - val m2 = SampleModel.find(1).open_! + val m1 = SampleModel.find(1).openOrThrowException("Test") + val m2 = SampleModel.find(1).openOrThrowException("Test") (m1 == m2) must_== true @@ -245,9 +245,9 @@ class MapperSpec extends Specification with BeforeExample { } "work with Mixed case" in { - val elwood = Mixer.find(By(Mixer.name, "Elwood")).open_! - val madeline = Mixer.find(By(Mixer.name, "Madeline")).open_! - val archer = Mixer.find(By(Mixer.name, "Archer")).open_! + val elwood = Mixer.find(By(Mixer.name, "Elwood")).openOrThrowException("Test") + val madeline = Mixer.find(By(Mixer.name, "Madeline")).openOrThrowException("Test") + val archer = Mixer.find(By(Mixer.name, "Archer")).openOrThrowException("Test") elwood.name.is must_== "Elwood" madeline.name.is must_== "Madeline" @@ -259,11 +259,11 @@ class MapperSpec extends Specification with BeforeExample { } "work with Mixed case update and delete" in { - val elwood = Mixer.find(By(Mixer.name, "Elwood")).open_! + val elwood = Mixer.find(By(Mixer.name, "Elwood")).openOrThrowException("Test") elwood.name.is must_== "Elwood" elwood.name("FruitBar").weight(966).save - val fb = Mixer.find(By(Mixer.weight, 966)).open_! + val fb = Mixer.find(By(Mixer.weight, 966)).openOrThrowException("Test") fb.name.is must_== "FruitBar" fb.weight.is must_== 966 @@ -276,11 +276,11 @@ class MapperSpec extends Specification with BeforeExample { } "work with Mixed case update and delete for Dog2" in { - val elwood = Dog2.find(By(Dog2.name, "Elwood")).open_! + val elwood = Dog2.find(By(Dog2.name, "Elwood")).openOrThrowException("Test") elwood.name.is must_== "Elwood" elwood.name("FruitBar").actualAge(966).save - val fb = Dog2.find(By(Dog2.actualAge, 966)).open_! + val fb = Dog2.find(By(Dog2.actualAge, 966)).openOrThrowException("Test") fb.name.is must_== "FruitBar" fb.actualAge.is must_== 966 @@ -300,8 +300,8 @@ class MapperSpec extends Specification with BeforeExample { val i1 = Thing.create.name("frog").saveMe val i2 = Thing.create.name("dog").saveMe - Thing.find(By(Thing.thing_id, i1.thing_id.is)).open_!.name.is must_== "frog" - Thing.find(By(Thing.thing_id, i2.thing_id.is)).open_!.name.is must_== "dog" + Thing.find(By(Thing.thing_id, i1.thing_id.is)).openOrThrowException("Test").name.is must_== "frog" + Thing.find(By(Thing.thing_id, i2.thing_id.is)).openOrThrowException("Test").name.is must_== "dog" } @@ -331,7 +331,7 @@ class MapperSpec extends Specification with BeforeExample { "CreatedAt and UpdatedAt work" in { val now = Helpers.now - val dog = Dog2.find().open_! + val dog = Dog2.find().openOrThrowException("Test") val oldUpdate = dog.updatedAt.is @@ -343,7 +343,7 @@ class MapperSpec extends Specification with BeforeExample { dog.name("ralph").save - val dog2 = Dog2.find(dog.dog2id.is).open_! + val dog2 = Dog2.find(dog.dog2id.is).openOrThrowException("Test") dog.createdAt.is.getTime must_== dog2.createdAt.is.getTime oldUpdate.getTime must_!= dog2.updatedAt.is.getTime @@ -359,11 +359,11 @@ class MapperSpec extends Specification with BeforeExample { } "Save flag results in update rather than insert" in { - val elwood = SampleModel.find(By(SampleModel.firstName, "Elwood")).open_! + val elwood = SampleModel.find(By(SampleModel.firstName, "Elwood")).openOrThrowException("Test") elwood.firstName.is must_== "Elwood" elwood.firstName("Frog").save - val frog = SampleModel.find(By(SampleModel.firstName, "Frog")).open_! + val frog = SampleModel.find(By(SampleModel.firstName, "Frog")).openOrThrowException("Test") frog.firstName.is must_== "Frog" SampleModel.findAll().length must_== 4 diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/MongoMetaRecord.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/MongoMetaRecord.scala index a064fe51dd..f577ef8ffb 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/MongoMetaRecord.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/MongoMetaRecord.scala @@ -328,7 +328,7 @@ trait MongoMetaRecord[BaseRecord <: MongoRecord[BaseRecord]] .map(field => (field.name, fieldDbValue(field))) .partition(pair => pair._2.isDefined) - val fieldsToSet = fullFields.map(pair => (pair._1, pair._2.open_!)) // these are all Full + val fieldsToSet = fullFields.map(pair => (pair._1, pair._2.openOrThrowException("these are all Full"))) val fieldsToUnset: List[String] = otherFields.filter( pair => pair._2 match { diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordExamplesSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordExamplesSpec.scala index e993d8746c..b37590664e 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordExamplesSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordExamplesSpec.scala @@ -387,7 +387,7 @@ class MongoRecordExamplesSpec extends Specification with MongoTestKit { // fetch a refdoc val refFromFetch = md1.refdoc.obj refFromFetch.isDefined must_== true - refFromFetch.open_!.id must_== ref1.id + refFromFetch.openOrThrowException("we know this is Full").id must_== ref1.id // query for a single doc with a JObject query val md1a = MainDoc.find(("name") -> "md1") diff --git a/persistence/proto/src/main/scala/net/liftweb/proto/Crudify.scala b/persistence/proto/src/main/scala/net/liftweb/proto/Crudify.scala index ba47ef22ff..8f71c61ba2 100644 --- a/persistence/proto/src/main/scala/net/liftweb/proto/Crudify.scala +++ b/persistence/proto/src/main/scala/net/liftweb/proto/Crudify.scala @@ -238,7 +238,7 @@ trait Crudify { Full(NamedPF(name) { case RewriteRequest(pp , _, _) if hasParamFor(pp, viewPath) => (RewriteResponse(viewPath), - findForParam(pp.wholePath.last).open_!) + findForParam(pp.wholePath.last).openOrThrowException("legacy code, it was open_!")) }) override def calcTemplate = Full(viewTemplate) @@ -285,7 +285,7 @@ trait Crudify { Full(NamedPF(name) { case RewriteRequest(pp , _, _) if hasParamFor(pp, editPath) => (RewriteResponse(editPath), - findForParam(pp.wholePath.last).open_!) + findForParam(pp.wholePath.last).openOrThrowException("legacy code, it was open_!")) }) override def calcTemplate = Full(editTemplate) @@ -415,7 +415,7 @@ trait Crudify { Full(NamedPF(name) { case RewriteRequest(pp , _, _) if hasParamFor(pp, deletePath) => (RewriteResponse(deletePath), - findForParam(pp.wholePath.last).open_!) + findForParam(pp.wholePath.last).openOrThrowException("legacy code, it was open_!")) }) override def calcTemplate = Full(deleteTemplate) diff --git a/persistence/proto/src/main/scala/net/liftweb/proto/ProtoUser.scala b/persistence/proto/src/main/scala/net/liftweb/proto/ProtoUser.scala index f0177eaaaa..d9a846c6c5 100644 --- a/persistence/proto/src/main/scala/net/liftweb/proto/ProtoUser.scala +++ b/persistence/proto/src/main/scala/net/liftweb/proto/ProtoUser.scala @@ -578,7 +578,7 @@ trait ProtoUser { def logUserIn(who: TheUserType, postLogin: () => Nothing): Nothing = { if (destroySessionOnLogin) { - S.session.open_!.destroySessionAndContinueInNewSession(() => { + S.session.openOrThrowException("we have a session here").destroySessionAndContinueInNewSession(() => { logUserIn(who) postLogin() }) @@ -1008,7 +1008,7 @@ trait ProtoUser { } def changePassword = { - val user = currentUser.open_! // we can do this because the logged in test has happened + val user = currentUser.openOrThrowException("we can do this because the logged in test has happened") var oldPassword = "" var newPassword: List[String] = Nil @@ -1060,7 +1060,7 @@ trait ProtoUser { def edit = { val theUser: TheUserType = - mutateUserOnEdit(currentUser.open_!) // we know we're logged in + mutateUserOnEdit(currentUser.openOrThrowException("we know we're logged in")) val theName = editPath.mkString("") diff --git a/web/testkit/src/main/scala/net/liftweb/http/testing/TestRunner.scala b/web/testkit/src/main/scala/net/liftweb/http/testing/TestRunner.scala index 692ce5f489..31955826c8 100644 --- a/web/testkit/src/main/scala/net/liftweb/http/testing/TestRunner.scala +++ b/web/testkit/src/main/scala/net/liftweb/http/testing/TestRunner.scala @@ -162,7 +162,7 @@ case class TestResults(res: List[Tracker]) { val append = (failedTests, failedAsserts) match { case (ft,fa) if ft.length == 0 && fa.length == 0 => "" case (ft, fa) => - "\n"+ft.length+" Failed Tests:\n"+ft.map(v => v.name+" "+v.exception.open_!.getMessage+" \n"+ + "\n"+ft.length+" Failed Tests:\n"+ft.map(v => v.name+" "+v.exception.openOrThrowException("This should be safe").getMessage+" \n"+ v.trace.map(st => " "+st.toString).mkString("\n")).mkString("\n") } diff --git a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Comet.scala b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Comet.scala index 2efb1bc625..c846cbcedc 100644 --- a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Comet.scala +++ b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Comet.scala @@ -93,9 +93,7 @@ object Comet extends DispatchSnippet with LazyLoggable { c.buildSpan(when, response.inSpan) case e => - if (c.cometRenderTimeoutHandler().isDefined) { - c.cometRenderTimeoutHandler().open_! - } else { + c.cometRenderTimeoutHandler().openOr{ throw new CometTimeoutException("type: "+theType+" name: "+name) } }}} openOr { diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala index 67e84c36b8..6345cdd05d 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala @@ -574,7 +574,7 @@ trait AbstractScreen extends Factory { override def name: String = underlying.map(_.name) openOr "N/A" - override def default = underlying.open_!.get + override def default = underlying.openOrThrowException("legacy code").get override implicit def manifest: Manifest[ValueType] = man @@ -586,11 +586,11 @@ trait AbstractScreen extends Factory { case AFilter(f) => f }.toList - override def is = underlying.open_!.get + override def is = underlying.openOrThrowException("Legacy code").get - override def get = underlying.open_!.get + override def get = underlying.openOrThrowException("Legacy code").get - override def set(v: T) = underlying.open_!.set(setFilter.foldLeft(v)((v, f) => f(v))) + override def set(v: T) = underlying.openOrThrowException("Legacy code").set(setFilter.foldLeft(v)((v, f) => f(v))) override def uniqueFieldId: Box[String] = paramFieldId or underlying.flatMap(_.uniqueFieldId) or super.uniqueFieldId diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala index e83c767a6c..d3653d0548 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala @@ -267,10 +267,10 @@ class LiftServlet extends Loggable { LiftRules.statelessDispatch.toList).map(_.apply() match { case Full(a) => Full(LiftRules.convertResponse((a, Nil, S.responseCookies, req))) case r => r - }); + }) tmpStatelessHolder.isDefined }) { - val f = tmpStatelessHolder.open_! + val f = tmpStatelessHolder.openOrThrowException("This is a full box here, checked on previous line") f match { case Full(v) => Full(v) case Empty => LiftRules.notFoundOrIgnore(req, Empty) @@ -283,7 +283,7 @@ class LiftServlet extends Loggable { def doSession(r2: Req, s2: LiftSession, continue: Box[() => Nothing]): () => Box[LiftResponse] = { try { S.init(r2, s2) { - dispatchStatefulRequest(S.request.open_!, liftSession, r2, continue) + dispatchStatefulRequest(S.request.openOrThrowException("I'm pretty sure this is a full box here"), liftSession, r2, continue) } } catch { case cre: ContinueResponseException => diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index ba48f0cb55..fc58d45941 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -136,8 +136,8 @@ object LiftSession { } }).map { case uc: UnitConstructor => uc.makeOne - case pc: PConstructor => pc.makeOne(pp.open_!.v) // open_! okay - case psc: PAndSessionConstructor => psc.makeOne(pp.open_!.v, session) + case pc: PConstructor => pc.makeOne(pp.openOrThrowException("It's ok").v) + case psc: PAndSessionConstructor => psc.makeOne(pp.openOrThrowException("It's ok").v, session) } } diff --git a/web/webkit/src/main/scala/net/liftweb/http/Req.scala b/web/webkit/src/main/scala/net/liftweb/http/Req.scala index 109641ae56..9d725ededc 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Req.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Req.scala @@ -552,7 +552,7 @@ object Req { !updated.startsWith("https://") && !updated.startsWith("https://") && !updated.startsWith("#")) - rewrite.open_!.apply(updated) else updated) + rewrite.openOrThrowException("legacy code").apply(updated) else updated) } /** diff --git a/web/webkit/src/main/scala/net/liftweb/http/S.scala b/web/webkit/src/main/scala/net/liftweb/http/S.scala index 57bb59cdbd..721716c917 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/S.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/S.scala @@ -2471,9 +2471,9 @@ for { val future: LAFuture[Any] = new LAFuture updateFunctionMap(name, new S.ProxyFuncHolder(value) { - override def apply(in: List[String]): Any = future.get(5000).open_! + override def apply(in: List[String]): Any = future.get(5000).openOrThrowException("legacy code") - override def apply(in: FileParamHolder): Any = future.get(5000).open_! + override def apply(in: FileParamHolder): Any = future.get(5000).openOrThrowException("legacy code") }) future @@ -2489,7 +2489,7 @@ for { override def apply(in: List[String]): Any = { val ns = fixShot() if (ns) { - theFuture.get(5000).open_! + theFuture.get(5000).openOrThrowException("legacy code") } else { val future = theFuture try { @@ -2506,7 +2506,7 @@ for { val ns = fixShot() if (ns) { - theFuture.get(5000).open_! + theFuture.get(5000).openOrThrowException("legacy code") } else { val future = theFuture try { @@ -2771,7 +2771,7 @@ for { def doRender(session: LiftSession): NodeSeq = session.processSurroundAndInclude("external render", xhtml) - if (inS.value) doRender(session.open_!) + if (inS.value) doRender(session.openOrThrowException("legacy code")) else { val req = Req(httpRequest, LiftRules.statelessRewrite.toList, Nil, @@ -2992,7 +2992,7 @@ for { def idMessages(f: => List[(NodeSeq, Box[String])]): List[(String, List[NodeSeq])] = { val res = new HashMap[String, List[NodeSeq]] f filter (_._2.isEmpty == false) foreach (_ match { - case (node, id) => val key = id open_!; res += (key -> (res.getOrElseUpdate(key, Nil) ::: List(node))) + case (node, id) => val key = id openOrThrowException("legacy code"); res += (key -> (res.getOrElseUpdate(key, Nil) ::: List(node))) }) res toList diff --git a/web/webkit/src/main/scala/net/liftweb/http/Templates.scala b/web/webkit/src/main/scala/net/liftweb/http/Templates.scala index 8f72cfb568..01776bacf7 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Templates.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Templates.scala @@ -179,7 +179,7 @@ object Templates { resolver(key) } else { val lrCache = LiftRules.templateCache - val cache = if (lrCache.isDefined) lrCache.open_! else NoCache + val cache = if (lrCache.isDefined) lrCache.openOrThrowException("passes isDefined") else NoCache val parserFunction: InputStream => Box[NodeSeq] = S.htmlProperties.htmlParser @@ -231,7 +231,7 @@ object Templates { } if (xmlb.isDefined) { found = true - ret = (cache(key) = xmlb.open_!) + ret = (cache(key) = xmlb.openOrThrowException("passes isDefined")) } else if (xmlb.isInstanceOf[Failure] && (Props.devMode | Props.testMode)) { val msg = xmlb.asInstanceOf[Failure].msg diff --git a/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPRequestServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPRequestServlet.scala index bf158e310d..c31dbe6d09 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPRequestServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPRequestServlet.scala @@ -158,9 +158,9 @@ class HTTPRequestServlet(val req: HttpServletRequest, val provider: HTTPProvider def resumeInfo : Option[(Req, LiftResponse)] = asyncProvider.flatMap(_.resumeInfo) - def suspend(timeout: Long): RetryState.Value = asyncProvider.open_!.suspend(timeout) // open_! is bad, but presumably, the suspendResume support was checked + def suspend(timeout: Long): RetryState.Value = asyncProvider.openOrThrowException("open_! is bad, but presumably, the suspendResume support was checked").suspend(timeout) - def resume(what: (Req, LiftResponse)): Boolean = asyncProvider.open_!.resume(what) + def resume(what: (Req, LiftResponse)): Boolean = asyncProvider.openOrThrowException("open_! is bad, but presumably, the suspendResume support was checked").resume(what) lazy val suspendResumeSupport_? = { LiftRules.asyncProviderMeta. diff --git a/web/webkit/src/main/scala/net/liftweb/http/rest/RestHelper.scala b/web/webkit/src/main/scala/net/liftweb/http/rest/RestHelper.scala index ea236987e9..874832e2e3 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/rest/RestHelper.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/rest/RestHelper.scala @@ -336,7 +336,7 @@ trait RestHelper extends LiftRules.DispatchPF { () => { pf(r).box match { case Full(resp) => - val selType = selection(r).open_! // Full because pass isDefinedAt + val selType = selection(r).openOrThrowException("Full because pass isDefinedAt") if (cvt.isDefinedAt((selType, resp, r))) Full(cvt((selType, resp, r))) else emptyToResp(ParamFailure("Unabled to convert the message", diff --git a/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala index 8773c9f95b..0b871f2f57 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala @@ -41,7 +41,7 @@ object ReqSpec extends Specification { val uac = new UserAgentCalculator { def userAgent = Full("Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-HK) AppleWebKit/533.18.1 (KHTML, like Gecko) Version/5.0.2 Safari/533.18.5") } - uac.safariVersion.open_! must_== 5 + uac.safariVersion.openOrThrowException("legacy code") must_== 5 } "Do the right thing with iPhone" in { diff --git a/web/webkit/src/test/scala/net/liftweb/http/SnippetSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/SnippetSpec.scala index 287cf6f88d..d3949c9873 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/SnippetSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/SnippetSpec.scala @@ -95,7 +95,7 @@ object SnippetSpec extends Specification { } } - ret.open_! must ==/( res) + ret.openOrThrowException("legacy code") must ==/( res) } @@ -111,7 +111,7 @@ object SnippetSpec extends Specification { } } - ret.open_! must ==/( res) + ret.openOrThrowException("legacy code") must ==/( res) } "Snippet invocation works class='l:foo'" in { @@ -126,7 +126,7 @@ object SnippetSpec extends Specification { } } - ret.open_! must ==/( res) + ret.openOrThrowException("legacy code") must ==/( res) } "Snippet invocation works class='l:foo' and ? for attr sep" in { @@ -148,7 +148,7 @@ object SnippetSpec extends Specification { } } - ret.open_! must ==/( res) + ret.openOrThrowException("legacy code") must ==/( res) } @@ -171,7 +171,7 @@ object SnippetSpec extends Specification { } } - ret.open_! must ==/( res) + ret.openOrThrowException("legacy code") must ==/( res) } @@ -195,7 +195,7 @@ object SnippetSpec extends Specification { } } - ret.open_! must ==/( res) + ret.openOrThrowException("legacy code") must ==/( res) } @@ -218,7 +218,7 @@ object SnippetSpec extends Specification { } } - ret.open_! must ==/( res) + ret.openOrThrowException("legacy code") must ==/( res) } @@ -235,7 +235,7 @@ object SnippetSpec extends Specification { } } - ret.open_! must ==/( res) + ret.openOrThrowException("legacy code") must ==/( res) } "Snippet invocation fails class='l:bar'" in { @@ -248,7 +248,7 @@ object SnippetSpec extends Specification { } } - (ret.open_! \ "@class").text must_== "snippeterror" + (ret.openOrThrowException("legacy code") \ "@class").text must_== "snippeterror" } object myInfo extends SessionVar("") @@ -280,7 +280,7 @@ object SnippetSpec extends Specification { } } - (ret.open_! \ "@class").text must_== "snippeterror" + (ret.openOrThrowException("legacy code") \ "@class").text must_== "snippeterror" } "Snippet invocation succeeds in normal mode" in { @@ -297,7 +297,7 @@ object SnippetSpec extends Specification { } } - ret.open_! must ==/(res) + ret.openOrThrowException("legacy code") must ==/(res) } "Snippet invocation fails in stateless mode (function table)" in { @@ -313,7 +313,7 @@ object SnippetSpec extends Specification { } } - (ret.open_! \ "@class").text must_== "snippeterror" + (ret.openOrThrowException("legacy code") \ "@class").text must_== "snippeterror" } "Snippet invocation succeeds in normal mode (function table)" in { @@ -330,7 +330,7 @@ object SnippetSpec extends Specification { } } - ret.open_! must ==/(res) + ret.openOrThrowException("legacy code") must ==/(res) } "run string input" in { @@ -366,7 +366,7 @@ object SnippetSpec extends Specification { Moo) } - ret.open_! must ==/ () + ret.openOrThrowException("legacy code") must ==/ () */ pending } @@ -382,7 +382,7 @@ object SnippetSpec extends Specification { ) } - (ret.open_! \ "@name").text.length must be > 0 + (ret.openOrThrowException("legacy code") \ "@name").text.length must be > 0 */ pending } diff --git a/web/webkit/src/test/scala/net/liftweb/sitemap/MenuDSLSpec.scala b/web/webkit/src/test/scala/net/liftweb/sitemap/MenuDSLSpec.scala index 704c0ad6a2..c9b96150d8 100644 --- a/web/webkit/src/test/scala/net/liftweb/sitemap/MenuDSLSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/sitemap/MenuDSLSpec.scala @@ -94,7 +94,7 @@ object MenuDslSpec extends Specification { ) - val complete = SiteMap(menu).kids(0).makeMenuItem(List()).open_! + val complete = SiteMap(menu).kids(0).makeMenuItem(List()).openOrThrowException("legacy code") complete.kids.size must_== 2 complete.kids(0).kids.size must_== 3 diff --git a/web/webkit/src/test/scala/net/liftweb/webapptest/OneShot.scala b/web/webkit/src/test/scala/net/liftweb/webapptest/OneShot.scala index baa0865510..02f30858da 100644 --- a/web/webkit/src/test/scala/net/liftweb/webapptest/OneShot.scala +++ b/web/webkit/src/test/scala/net/liftweb/webapptest/OneShot.scala @@ -64,7 +64,7 @@ object OneShot extends Specification with RequestKit { xml <- resp.xml } yield xml - bx.open_! must ==/ (45).when(jetty.running) + bx.openOrThrowException("legacy code") must ==/ (45).when(jetty.running) } finally { LiftRules.sessionCreator = tmp } @@ -82,7 +82,7 @@ object OneShot extends Specification with RequestKit { xml <- resp2.xml } yield xml - bx.open_! must ==/ (33).when(jetty.running) + bx.openOrThrowException("legacy code") must ==/ (33).when(jetty.running) } finally { LiftRules.sessionCreator = tmp } @@ -102,8 +102,8 @@ object OneShot extends Specification with RequestKit { xml2 <- resp3.xml } yield (xml, xml2) - bx.open_!._1 must ==/ (33).when(jetty.running) - bx.open_!._2 must ==/ (45).when(jetty.running) + bx.openOrThrowException("legacy code")._1 must ==/ (33).when(jetty.running) + bx.openOrThrowException("legacy code")._2 must ==/ (45).when(jetty.running) } finally { LiftRules.sessionCreator = tmp } @@ -124,8 +124,8 @@ object OneShot extends Specification with RequestKit { xml2 <- resp3.xml } yield (xml, xml2) - bx.open_!._1 must ==/(33).when(jetty.running) - bx.open_!._2 must ==/(meow).when(jetty.running) + bx.openOrThrowException("legacy code")._1 must ==/(33).when(jetty.running) + bx.openOrThrowException("legacy code")._2 must ==/(meow).when(jetty.running) } finally { LiftRules.sessionCreator = tmp diff --git a/web/webkit/src/test/scala/net/liftweb/webapptest/ToHeadUsages.scala b/web/webkit/src/test/scala/net/liftweb/webapptest/ToHeadUsages.scala index af08439c09..86dc595ff4 100644 --- a/web/webkit/src/test/scala/net/liftweb/webapptest/ToHeadUsages.scala +++ b/web/webkit/src/test/scala/net/liftweb/webapptest/ToHeadUsages.scala @@ -112,7 +112,7 @@ object ToHeadUsages extends Specification { "pages " should { "Templates should recognize entities" in { - val ns = Templates(List("index")).open_! + val ns = Templates(List("index")).openOrThrowException("legacy code") val str = AltXML.toXML(ns(0), false, false, false) val idx = str.indexOf("—") @@ -120,7 +120,7 @@ object ToHeadUsages extends Specification { } "Templates should not recognize entities" in { - val ns = Templates(List("index")).open_! + val ns = Templates(List("index")).openOrThrowException("legacy code") val str = AltXML.toXML(ns(0), false, true, false) val idx = str.indexOf("—") diff --git a/web/wizard/src/test/scala/net/liftweb/wizard/WizardSpec.scala b/web/wizard/src/test/scala/net/liftweb/wizard/WizardSpec.scala index a967dda940..978a941cb2 100644 --- a/web/wizard/src/test/scala/net/liftweb/wizard/WizardSpec.scala +++ b/web/wizard/src/test/scala/net/liftweb/wizard/WizardSpec.scala @@ -75,28 +75,28 @@ object WizardSpec extends Specification { "A wizard must transition from first screen to second screen" in { S.initIfUninitted(session) { - MyWizard.currentScreen.open_! must_== MyWizard.nameAndAge + MyWizard.currentScreen.openOrThrowException("legacy code") must_== MyWizard.nameAndAge MyWizard.nextScreen - MyWizard.currentScreen.open_! must_== MyWizard.nameAndAge + MyWizard.currentScreen.openOrThrowException("legacy code") must_== MyWizard.nameAndAge MyWizard.nameAndAge.name.set("David") MyWizard.nameAndAge.age.set(14) MyWizard.nextScreen - MyWizard.currentScreen.open_! must_== MyWizard.parentName + MyWizard.currentScreen.openOrThrowException("legacy code") must_== MyWizard.parentName MyWizard.prevScreen - MyWizard.currentScreen.open_! must_== MyWizard.nameAndAge + MyWizard.currentScreen.openOrThrowException("legacy code") must_== MyWizard.nameAndAge MyWizard.nameAndAge.age.set(45) MyWizard.nextScreen - MyWizard.currentScreen.open_! must_== MyWizard.favoritePet + MyWizard.currentScreen.openOrThrowException("legacy code") must_== MyWizard.favoritePet S.clearCurrentNotices @@ -112,24 +112,24 @@ object WizardSpec extends Specification { "A wizard must be able to snapshot itself" in { val ss = S.initIfUninitted(session) { - MyWizard.currentScreen.open_! must_== MyWizard.nameAndAge + MyWizard.currentScreen.openOrThrowException("legacy code") must_== MyWizard.nameAndAge MyWizard.nextScreen - MyWizard.currentScreen.open_! must_== MyWizard.nameAndAge + MyWizard.currentScreen.openOrThrowException("legacy code") must_== MyWizard.nameAndAge MyWizard.nameAndAge.name.set("David") MyWizard.nameAndAge.age.set(14) MyWizard.nextScreen - MyWizard.currentScreen.open_! must_== MyWizard.parentName + MyWizard.currentScreen.openOrThrowException("legacy code") must_== MyWizard.parentName MyWizard.createSnapshot } S.initIfUninitted(session) { - MyWizard.currentScreen.open_! must_== MyWizard.nameAndAge + MyWizard.currentScreen.openOrThrowException("legacy code") must_== MyWizard.nameAndAge } S.initIfUninitted(session) { @@ -137,13 +137,13 @@ object WizardSpec extends Specification { MyWizard.prevScreen - MyWizard.currentScreen.open_! must_== MyWizard.nameAndAge + MyWizard.currentScreen.openOrThrowException("legacy code") must_== MyWizard.nameAndAge MyWizard.nameAndAge.age.set(45) MyWizard.nextScreen - MyWizard.currentScreen.open_! must_== MyWizard.favoritePet + MyWizard.currentScreen.openOrThrowException("legacy code") must_== MyWizard.favoritePet S.clearCurrentNotices From fd7ce12f73962fe215702a9a5de862212543720a Mon Sep 17 00:00:00 2001 From: Dave Briccetti Date: Sat, 17 Nov 2012 12:59:37 -0800 Subject: [PATCH 0243/1949] Make some minor comment improvements to SHtml.scala and Menu.scala. --- .../scala/net/liftweb/builtin/snippet/Menu.scala | 10 +++++----- .../src/main/scala/net/liftweb/http/SHtml.scala | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Menu.scala b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Menu.scala index a1c1b31a2b..47a6895c2b 100644 --- a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Menu.scala +++ b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Menu.scala @@ -242,24 +242,24 @@ object Menu extends DispatchSnippet { * automatically set the title for your page based on your SiteMap:

    * *
    -   * ...
    +   * ⋮
        * <head>
        *   <title><lift:Menu.title /></title>
        * </head>
    -   * ...
    +   * ⋮
        * 
    *

    HTML5 does not support tags inside the <title> tag, * so you must do: *

    * *
    -   *    * <head>
    +   * <head>
        *   <title class="lift:Menu.title"e;>The page named %*% is being displayed</title>
        * </head>
        * 
    *

    - * And Lift will substitute the title at the %*% marker, alternative, Lift - * will append the Menu.title to the contents of the <title> tag. + * And Lift will substitute the title at the %*% marker if the marker exists, otherwise + * append the title to the contents of the <title> tag. *

    */ def title(text: NodeSeq): NodeSeq = { diff --git a/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala b/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala index 7d2dd25aeb..dbf36d1646 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala @@ -610,7 +610,7 @@ trait SHtml { } /** - * This function does not really submit a JSON request to server instead json is a function + * This function does not really submit a JSON request to the server. Instead, json is a function * that allows you to build a more complex JsCmd based on the JsExp JE.JsRaw("this.value"). * This function is called by the overloaded version of jsonText. * @@ -630,7 +630,7 @@ trait SHtml { /** - * This function does not really submit a JSON request to server instead json is a function + * This function does not really submit a JSON request to the server. Instead, json is a function * that allows you to build a more complex JsCmd based on the JsExp JE.JsRaw("this.value"). * This function is called by the overloaded version of jsonText. * @@ -690,7 +690,7 @@ trait SHtml { } /** - * This function does not really submit a JSON request to server instead json is a function + * This function does not really submit a JSON request to the server. Instead, json is a function * that allows you to build a more complex JsCmd based on the JsExp JE.JsRaw("this.value"). * This function is called by the overloaded version of jsonTextarea. * @@ -1181,7 +1181,7 @@ trait SHtml { } /** - * execute the function when the form is submitted. + * Execute the function when the form is submitted. * This method returns a function that can be applied to * form fields (input, button, textarea, select) and the * function is executed when the form containing the field is submitted. @@ -1190,7 +1190,7 @@ trait SHtml { onSubmitImpl(func: AFuncHolder) /** - * execute the String function when the form is submitted. + * Execute the String function when the form is submitted. * This method returns a function that can be applied to * form fields (input, button, textarea, select) and the * function is executed when the form containing the field is submitted. @@ -1200,7 +1200,7 @@ trait SHtml { } /** - * execute the List[String] function when the form is submitted. + * Execute the List[String] function when the form is submitted. * This method returns a function that can be applied to * form fields (input, button, textarea, select) and the * function is executed when the form containing the field is submitted. From a3b34ce68ad47240c350cd73f0b97a9e10d124b0 Mon Sep 17 00:00:00 2001 From: Dave Briccetti Date: Sat, 17 Nov 2012 17:45:23 -0800 Subject: [PATCH 0244/1949] Make some minor comment improvements to LiftScreen.scala. --- .../scala/net/liftweb/http/LiftScreen.scala | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala index 6345cdd05d..94a831a41a 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala @@ -89,11 +89,11 @@ trait AbstractScreen extends Factory { def screenBottom: Box[Elem] = Empty - // an implicit coversion so we don't have to put Full around every Elem + // An implicit conversion so we don't have to put Full around every Elem protected implicit def elemInABox(in: Elem): Box[Elem] = Box !! in /** - * The name of the screen. Override this to change the screen name + * The name of the screen. Override this to change the screen name. */ def screenName: String = "Screen" @@ -448,7 +448,7 @@ trait AbstractScreen extends Factory { underlying.toForm.map(ns => SHtml.ElemAttr.applyToAllElems(ns, formElemAttrs)) /** - * Give the current state of things, should the this field be shown + * Given the current state of things, should this field be shown */ override def show_? = newShow map (_()) openOr underlying.show_? @@ -552,7 +552,7 @@ trait AbstractScreen extends Factory { .map(ns => SHtml.ElemAttr.applyToAllElems(ns, formElemAttrs)) /** - * Give the current state of things, should the this field be shown + * Given the current state of things, should this field be shown */ override def show_? = newShow map (_()) openOr (underlying.map(_.show_?) openOr false) @@ -650,7 +650,7 @@ trait AbstractScreen extends Factory { /** * A validation helper. Make sure the string is at least a particular - * length and generate a validation issue if not + * length and generate a validation issue if not. */ protected def valMinLen(len: => Int, msg: => String): String => List[FieldError] = s => s match { @@ -661,7 +661,7 @@ trait AbstractScreen extends Factory { /** * A validation helper. Make sure the string is no more than a particular - * length and generate a validation issue if not + * length and generate a validation issue if not. */ protected def valMaxLen(len: => Int, msg: => String): String => List[FieldError] = s => s match { @@ -1283,8 +1283,8 @@ trait ScreenWizardRendered { } /** - * Should all instances of this Wizard or Screen unless - * they are explicitly set to Ajax + * Should all instances of this Wizard or Screen default to Ajax + * when not explicitly set */ protected def defaultToAjax_? : Boolean = false @@ -1564,7 +1564,7 @@ trait LiftScreen extends AbstractScreen with StatefulSnippet with ScreenWizardRe } /** - * What additional attributes should be put on the + * What additional attributes should be put on */ protected def formAttrs: MetaData = scala.xml.Null From 0c56fcbdf5611d782a748453357a25ec8c6b2d2a Mon Sep 17 00:00:00 2001 From: Dave Briccetti Date: Sun, 18 Nov 2012 00:37:05 -0800 Subject: [PATCH 0245/1949] Make some minor comment improvements to various Scala files --- .../scala/net/liftweb/util/BaseField.scala | 8 +++--- .../scala/net/liftweb/proto/Crudify.scala | 28 +++++++++---------- .../scala/net/liftweb/proto/ProtoUser.scala | 2 +- .../scala/net/liftweb/record/MetaRecord.scala | 24 ++++++++-------- .../scala/net/liftweb/record/Record.scala | 6 ++-- .../scala/net/liftweb/http/LiftScreen.scala | 12 ++++---- 6 files changed, 39 insertions(+), 41 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/BaseField.scala b/core/util/src/main/scala/net/liftweb/util/BaseField.scala index ba5622a138..a949f39ddd 100644 --- a/core/util/src/main/scala/net/liftweb/util/BaseField.scala +++ b/core/util/src/main/scala/net/liftweb/util/BaseField.scala @@ -28,7 +28,7 @@ trait FieldIdentifier { } /** - * Associate a FieldIdentifier with an NodeSeq + * Associate a FieldIdentifier with a NodeSeq */ case class FieldError(field: FieldIdentifier, msg: NodeSeq) { override def toString = field.uniqueFieldId + " : " + msg @@ -114,7 +114,7 @@ trait SettableField extends ReadableField with SettableValueHolder { def toForm: Box[NodeSeq] /** - * Give the current state of things, should the this field be shown + * Given the current state of things, should this field be shown */ def show_? = true } @@ -174,7 +174,7 @@ trait StringValidators { /** * A validation helper. Make sure the string is at least a particular - * length and generate a validation issue if not + * length and generate a validation issue if not. */ def valMinLen(len: Int, msg: => String)(value: ValueType): List[FieldError] = valueTypeToBoxString(value) match { @@ -185,7 +185,7 @@ trait StringValidators { /** * A validation helper. Make sure the string is no more than a particular - * length and generate a validation issue if not + * length and generate a validation issue if not. */ def valMaxLen(len: Int, msg: => String)(value: ValueType): List[FieldError] = valueTypeToBoxString(value) match { diff --git a/persistence/proto/src/main/scala/net/liftweb/proto/Crudify.scala b/persistence/proto/src/main/scala/net/liftweb/proto/Crudify.scala index 8f71c61ba2..73099df434 100644 --- a/persistence/proto/src/main/scala/net/liftweb/proto/Crudify.scala +++ b/persistence/proto/src/main/scala/net/liftweb/proto/Crudify.scala @@ -104,7 +104,7 @@ trait Crudify { lazy val DeleteItem = calcDeleteItem /** - * What's the prefix for this CRUD. Typically the table name + * What's the prefix for this CRUD. Typically the table name. */ def calcPrefix: List[String] @@ -140,7 +140,7 @@ trait Crudify { def fieldsForDisplay: List[FieldPointerType] /** - * The list of fields to present on a form form editting + * The list of fields to present on a form form editing */ def fieldsForEditing: List[FieldPointerType] = fieldsForDisplay @@ -320,8 +320,8 @@ trait Crudify { def editErrorClass = "edit_error_class" /** - * The core template for editting. Does not include any - * page wrapping + * The core template for editing. Does not include any + * page wrapping. */ protected def _editTemplate = { @@ -453,7 +453,7 @@ trait Crudify { /** * The core template for deleting. Does not include any - * page wrapping + * page wrapping. */ def _deleteTemplate = @@ -483,7 +483,7 @@ trait Crudify { /** * This is the template that's used to render the page after the - * optional wrapping of the template in the page wrapper + * optional wrapping of the template in the page wrapper. */ def createTemplate(): NodeSeq = pageWrapper(_createTemplate) @@ -492,7 +492,7 @@ trait Crudify { /** * The core template for creating. Does not include any - * page wrapping + * page wrapping. */ def _createTemplate = @@ -530,7 +530,7 @@ trait Crudify { /** * The core template for viewing. Does not include any - * page wrapping + * page wrapping. */ def _viewTemplate = @@ -620,7 +620,7 @@ trait Crudify { /** * Given a range, find the records. Your implementation of this - * method should enforce ordering (e.g., on primary key) + * method should enforce ordering (e.g., on primary key). */ def findForList(start: Long, count: Int): List[TheCrudType] @@ -639,7 +639,7 @@ trait Crudify { /** * This method defines how many rows are displayed per page. By * default, it's hard coded at 20, but you can make it session specific - * or change the default by overriding this method + * or change the default by overriding this method. */ protected def rowsPerPage: Int = 20 @@ -748,14 +748,14 @@ trait Crudify { def referer: String = S.referer openOr listPathString /** - * As the field names are being displayed for editting, this method + * As the field names are being displayed for editing, this method * is called with the XHTML that will be displayed as the field name - * an a flag indicating that the field is required (or not). You + * and a flag indicating whether the field is required. You * can wrap the fieldName in a span with a css class indicating that * the field is required or otherwise do something to update the field - * name indiciating to the user that the field is required. By default + * name indicating to the user that the field is required. By default * the method wraps the fieldName in a span with the class attribute set - * to "required_field" + * to "required_field". */ def wrapNameInRequired(fieldName: NodeSeq, required: Boolean): NodeSeq = { if (required) { diff --git a/persistence/proto/src/main/scala/net/liftweb/proto/ProtoUser.scala b/persistence/proto/src/main/scala/net/liftweb/proto/ProtoUser.scala index d9a846c6c5..b982be212e 100644 --- a/persistence/proto/src/main/scala/net/liftweb/proto/ProtoUser.scala +++ b/persistence/proto/src/main/scala/net/liftweb/proto/ProtoUser.scala @@ -1049,7 +1049,7 @@ trait ProtoUser { /** * If there's any mutation to do to the user on retrieval for - * editting, override this method and mutate the user. This can + * editing, override this method and mutate the user. This can * be used to pull query parameters from the request and assign * certain fields. Issue #722 * diff --git a/persistence/record/src/main/scala/net/liftweb/record/MetaRecord.scala b/persistence/record/src/main/scala/net/liftweb/record/MetaRecord.scala index 680204b446..e599149995 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/MetaRecord.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/MetaRecord.scala @@ -54,7 +54,7 @@ trait MetaRecord[BaseRecord <: Record[BaseRecord]] { * * <lift:field_label name="firstName"/> - the label for firstName field will be rendered here * <lift:field name="firstName"/> - the firstName field will be rendered here (typically an input field) - * <lift:field_msg name="firstName"/> - the will be rendered here hafing the id given by + * <lift:field_msg name="firstName"/> - the will be rendered here having the id given by * uniqueFieldId of the firstName field. * * @@ -171,8 +171,8 @@ trait MetaRecord[BaseRecord <: Record[BaseRecord]] { protected def instantiateRecord: BaseRecord = rootClass.newInstance.asInstanceOf[BaseRecord] /** - * Creates a new record setting the value of the fields from the original object but - * apply the new value for the specific field + * Creates a new record, setting the value of the fields from the original object but + * applying the new value for the specific field * * @param - original the initial record * @param - field the new mutated field @@ -206,13 +206,13 @@ trait MetaRecord[BaseRecord <: Record[BaseRecord]] { /** * Validates the inst Record by calling validators for each field * - * @pram inst - the Record tobe validated + * @param inst - the Record to be validated * @return a List of FieldError. If this list is empty you can assume that record was validated successfully */ def validate(inst: BaseRecord): List[FieldError] = { foreachCallback(inst, _.beforeValidation) try{ - fieldList.flatMap(_.field(inst).validate) + fieldList.flatMap(_.field(inst).validate) } finally { foreachCallback(inst, _.afterValidation) } @@ -233,7 +233,7 @@ trait MetaRecord[BaseRecord <: Record[BaseRecord]] { } /** - * Retuns the JSON representation of inst record, converts asJValue to JsObj + * Returns the JSON representation of inst record, converts asJValue to JsObj * * @return a JsObj */ @@ -384,22 +384,22 @@ trait MetaRecord[BaseRecord <: Record[BaseRecord]] { } /** - * Prepend a DispatchPF function to LiftRules.dispatch. If the partial function id defined for a give Req + * Prepend a DispatchPF function to LiftRules.dispatch. If the partial function is defined for a give Req * it will construct a new Record based on the HTTP query string parameters * and will pass this Record to the function returned by func parameter. * - * @param func - a PartialFunction for associating a request with a user provided function and the proper Record + * @param func - a PartialFunction for associating a request with a user-provided function and the proper Record */ def prependDispatch(func: PartialFunction[Req, BaseRecord => Box[LiftResponse]])= { LiftRules.dispatch.prepend (makeFunc(func)) } /** - * Append a DispatchPF function to LiftRules.dispatch. If the partial function id defined for a give Req + * Append a DispatchPF function to LiftRules.dispatch. If the partial function is defined for a give Req * it will construct a new Record based on the HTTP query string parameters * and will pass this Record to the function returned by func parameter. * - * @param func - a PartialFunction for associating a request with a user provided function and the proper Record + * @param func - a PartialFunction for associating a request with a user-provided function and the proper Record */ def appendDispatch(func: PartialFunction[Req, BaseRecord => Box[LiftResponse]])= { LiftRules.dispatch.append (makeFunc(func)) @@ -462,7 +462,7 @@ trait MetaRecord[BaseRecord <: Record[BaseRecord]] { } /** - * Defined the order of the fields in this record + * Defines the order of the fields in this record * * @return a List of Field */ @@ -478,7 +478,7 @@ trait MetaRecord[BaseRecord <: Record[BaseRecord]] { def metaFields() : List[Field[_, BaseRecord]] = fieldList.map(_.metaField) /** - * Obtain the fields for a particlar Record or subclass instance by passing + * Obtain the fields for a particular Record or subclass instance by passing * the instance itself. * (added 14th August 2009, Tim Perrett) */ diff --git a/persistence/record/src/main/scala/net/liftweb/record/Record.scala b/persistence/record/src/main/scala/net/liftweb/record/Record.scala index 63fca284ea..376deef58a 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/Record.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/Record.scala @@ -54,7 +54,7 @@ trait Record[MyType <: Record[MyType]] extends FieldContainer { } /** - * Returns the HTML representation ofthis Record + * Returns the HTML representation of this Record */ def toXHtml: NodeSeq = { meta.toXHtml(this) @@ -72,7 +72,7 @@ trait Record[MyType <: Record[MyType]] extends FieldContainer { } /** - * Retuns the JSON representation of this record + * Returns the JSON representation of this record * * @return a JsObj */ @@ -84,7 +84,7 @@ trait Record[MyType <: Record[MyType]] extends FieldContainer { def saveTheRecord(): Box[MyType] = throw new BackingStoreException("Raw Records don't save themselves") /** - * Retuns the JSON representation of this record, converts asJValue to JsObj + * Returns the JSON representation of this record, converts asJValue to JsObj * * @return a JsObj */ diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala index 94a831a41a..b6da30d469 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala @@ -39,7 +39,7 @@ trait AbstractScreen extends Factory { @volatile private[this] var _fieldList: List[() => FieldContainer] = Nil /** - * any additional parameters that need to be put in the on the form (e.g., mime type) + * Any additional parameters that need to be put on the form (e.g., mime type) */ def additionalAttributes: MetaData = if (hasUploadField) new UnprefixedAttribute("enctype", Text("multipart/form-data"), Null) else Null @@ -350,9 +350,9 @@ trait AbstractScreen extends Factory { * the returned FieldBuilder to convert it into a field * * @param name - the name of the field. This is a call-by-name parameter, so you can dynamically calculate - * the name of the fiels (e.g., localize its name) + * the name of the field (e.g., localize its name) * @param default - the default value of the field - * @param validate - any validation functions + * @param stuff - any filter or validation functions */ protected def builder[T](name: => String, default: => T, stuff: FilterOrValidate[T]*)(implicit man: Manifest[T]): FieldBuilder[T] = new FieldBuilder[T](name, default, man, Empty, @@ -851,7 +851,7 @@ trait AbstractScreen extends Factory { /** - * Create a textarea Field with 80 columns and 5 rows + * Create a textarea field with 80 columns and 5 rows * * @param name the name of the field (call-by-name) * @param defaultValue the starting value of the field (call-by-name) @@ -865,7 +865,7 @@ trait AbstractScreen extends Factory { /** - * Create a textarea Field + * Create a textarea field * * @param name the name of the field (call-by-name) * @param defaultValue the starting value of the field (call-by-name) @@ -963,9 +963,7 @@ trait AbstractScreen extends Factory { * @param name the name of the field (call-by-name) * @param default the starting value of the field (call-by-name) * @param choices the possible choices for the select - * * @param stuff - a list of filters and validations for the field - * @param f a PairStringPromoter (a wrapper around a function) that converts T => display String * * @return a newly minted Field{type ValueType = String} */ From 4116252a8bef43bc347bcb1febf99661530331d0 Mon Sep 17 00:00:00 2001 From: Dave Briccetti Date: Sun, 18 Nov 2012 12:38:13 -0800 Subject: [PATCH 0246/1949] Make some minor comment improvements, and remove an unused import --- .../src/main/scala/net/liftweb/record/Field.scala | 4 ++-- .../scala/net/liftweb/http/StatefulSnippet.scala | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/persistence/record/src/main/scala/net/liftweb/record/Field.scala b/persistence/record/src/main/scala/net/liftweb/record/Field.scala index 0845cd6868..0d01a29b6e 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/Field.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/Field.scala @@ -153,10 +153,10 @@ trait OwnedField[OwnerType <: Record[OwnerType]] extends BaseField { trait TypedField[ThisType] extends BaseField { /* - * Unless overriden, MyType is equal to ThisType. Available for + * Unless overridden, MyType is equal to ThisType. Available for * backwards compatibility */ - type MyType = ThisType // For backwards compatability + type MyType = ThisType // For backwards compatibility type ValidationFunction = ValueType => List[FieldError] diff --git a/web/webkit/src/main/scala/net/liftweb/http/StatefulSnippet.scala b/web/webkit/src/main/scala/net/liftweb/http/StatefulSnippet.scala index 6ecd2a2821..cfa391ea0b 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/StatefulSnippet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/StatefulSnippet.scala @@ -21,7 +21,7 @@ import net.liftweb.common._ import net.liftweb._ import util._ import Helpers._ -import xml.{Text, NodeSeq, Elem} +import xml.{NodeSeq, Elem} /** * The same StatefulSnippet instance is used across a given page rendering. @@ -81,10 +81,10 @@ trait StatefulSnippet extends DispatchSnippet { /** * create an anchor tag around a body * - * @to - the target + * @param to - the target * @param func - the function to invoke when the link is clicked * @param body - the NodeSeq to wrap in the anchor tag - * @attrs - the (optional) attributes for the HTML element + * @param attrs - the (optional) attributes for the HTML element */ def link(to: String, func: () => Any, body: NodeSeq, attrs: SHtml.ElemAttr*): Elem = SHtml.link(to, () => { registerThisSnippet(); func() }, body, attrs: _*) @@ -128,7 +128,7 @@ trait StatefulSnippet extends DispatchSnippet { */ trait RenderDispatch { /** - * The pre-defined dispatch + * The predefined dispatch */ def dispatch: PartialFunction[String, NodeSeq => NodeSeq] = Map("render" -> render _) @@ -145,7 +145,7 @@ trait RenderDispatch { */ trait RenderFuncDispatch { /** - * The pre-defined dispatch + * The predefined dispatch */ def dispatch: PartialFunction[String, NodeSeq => NodeSeq] = Map("render" -> render) @@ -217,7 +217,7 @@ trait SimpleStatelessBehavior extends StatelessBehavior { } /** - * A "default" implementation of StatelessBehavior. Just ignore everything and return a zero-length Text. + * A "default" implementation of StatelessBehavior. Just ignore everything and return an empty NodeSeq. */ trait BlankStatelessBehavior extends StatelessBehavior { def statelessDispatch: PartialFunction[String, NodeSeq => NodeSeq] = { From 3f59efc0cbe28720e23f8b55f9241e27aeb43c2b Mon Sep 17 00:00:00 2001 From: Dave Briccetti Date: Sun, 18 Nov 2012 18:22:04 -0800 Subject: [PATCH 0247/1949] Make a comment correction --- core/util/src/main/scala/net/liftweb/util/BaseField.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/util/src/main/scala/net/liftweb/util/BaseField.scala b/core/util/src/main/scala/net/liftweb/util/BaseField.scala index a949f39ddd..78cc64da8d 100644 --- a/core/util/src/main/scala/net/liftweb/util/BaseField.scala +++ b/core/util/src/main/scala/net/liftweb/util/BaseField.scala @@ -21,7 +21,7 @@ import common._ import xml.NodeSeq /** - * Defines the association of this reference with an markup tag ID + * Defines the association of this reference with a markup tag ID */ trait FieldIdentifier { def uniqueFieldId: Box[String] = Empty From c8f7985bba2a95bcfa0408bf75e940dc30ef832b Mon Sep 17 00:00:00 2001 From: Francis Rhys-Jones Date: Mon, 19 Nov 2012 18:43:46 +0000 Subject: [PATCH 0248/1949] Added the ability to parse json primitives at the top level --- .../src/main/scala/net/liftweb/json/JsonParser.scala | 9 ++++++--- .../src/test/scala/net/liftweb/json/JsonParserSpec.scala | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala index 2a39238b65..162f26280f 100644 --- a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala +++ b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala @@ -169,13 +169,16 @@ object JsonParser { } def newValue(v: JValue) { - vals.peek(classOf[JValue]) match { - case f: JField => + vals.peekOption match { + case Some(f: JField) => vals.pop(classOf[JField]) val newField = JField(f.name, v) val obj = vals.peek(classOf[JObject]) vals.replace(JObject(newField :: obj.obj)) - case a: JArray => vals.replace(JArray(v :: a.arr)) + case Some(a: JArray) => vals.replace(JArray(v :: a.arr)) + case None => + vals.push(v) + root = Some(v) case _ => p.fail("expected field or array") } } diff --git a/core/json/src/test/scala/net/liftweb/json/JsonParserSpec.scala b/core/json/src/test/scala/net/liftweb/json/JsonParserSpec.scala index b1a7867c32..de1a6d3421 100644 --- a/core/json/src/test/scala/net/liftweb/json/JsonParserSpec.scala +++ b/core/json/src/test/scala/net/liftweb/json/JsonParserSpec.scala @@ -31,7 +31,7 @@ object JsonParserSpec extends Specification with JValueGen with ScalaCheck { "Any valid json can be parsed" in { val parsing = (json: JValue) => { parse(Printer.pretty(render(json))); true } - check(forAll(parsing)) + check(forAll(genJValue)(parsing)) } "Buffer size does not change parsing result" in { From ed4c5c65365072c1ce7831e61b12225ca7094091 Mon Sep 17 00:00:00 2001 From: Dave Briccetti Date: Tue, 20 Nov 2012 02:02:17 -0800 Subject: [PATCH 0249/1949] Add a doc comment to mapper.MappedDate.toLong making it clear that the value returned is in units of seconds, not milliseconds --- .../mapper/src/main/scala/net/liftweb/mapper/MappedDate.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/persistence/mapper/src/main/scala/net/liftweb/mapper/MappedDate.scala b/persistence/mapper/src/main/scala/net/liftweb/mapper/MappedDate.scala index 62e5438080..55bf1fb1ce 100644 --- a/persistence/mapper/src/main/scala/net/liftweb/mapper/MappedDate.scala +++ b/persistence/mapper/src/main/scala/net/liftweb/mapper/MappedDate.scala @@ -64,7 +64,7 @@ abstract class MappedDate[T<:Mapper[T]](val fieldOwner: T) extends MappedField[D def dbFieldClass = classOf[Date] - + /** Returns the date as the number of seconds (not milliseconds) since January 1, 1970 */ def toLong: Long = is match { case null => 0L case d: Date => d.getTime / 1000L From 1bcc92f6b7e1c9cdff506c512b44e59b9b74307e Mon Sep 17 00:00:00 2001 From: Francis Rhys-Jones Date: Tue, 20 Nov 2012 10:32:48 +0000 Subject: [PATCH 0250/1949] Removed boxing while parsing primitives --- .../scala/net/liftweb/json/JsonParser.scala | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala index 162f26280f..5f179409f8 100644 --- a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala +++ b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala @@ -169,17 +169,19 @@ object JsonParser { } def newValue(v: JValue) { - vals.peekOption match { - case Some(f: JField) => - vals.pop(classOf[JField]) - val newField = JField(f.name, v) - val obj = vals.peek(classOf[JObject]) - vals.replace(JObject(newField :: obj.obj)) - case Some(a: JArray) => vals.replace(JArray(v :: a.arr)) - case None => - vals.push(v) - root = Some(v) - case _ => p.fail("expected field or array") + if(!vals.isEmpty) + vals.peek(classOf[JValue]) match { + case f: JField => + vals.pop(classOf[JField]) + val newField = JField(f.name, v) + val obj = vals.peek(classOf[JObject]) + vals.replace(JObject(newField :: obj.obj)) + case a: JArray => vals.replace(JArray(v :: a.arr)) + case _ => p.fail("expected field or array") + } + else { + vals.push(v) + root = Some(v) } } @@ -220,6 +222,7 @@ object JsonParser { } def peekOption = if (stack isEmpty) None else Some(stack.peek) + def isEmpty = stack.isEmpty } class Parser(buf: Buffer) { From 25e0227c5bbe3cc5e2b8612f7468684f5c03f26e Mon Sep 17 00:00:00 2001 From: Francis Rhys-Jones Date: Tue, 20 Nov 2012 10:52:21 +0000 Subject: [PATCH 0251/1949] Added details to contributors.md --- contributors.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/contributors.md b/contributors.md index e62bedf398..04d6a16591 100644 --- a/contributors.md +++ b/contributors.md @@ -59,3 +59,9 @@ Marko Elezović ### Email: ### marko at element dot hr + +### Name: ### +Francis Rhys-Jones + +### Email: ### +francis.rhys-jones at guardian dot co dot uk From f8889da4e194bd091e8d5de55f9aeaef57073620 Mon Sep 17 00:00:00 2001 From: Dave Briccetti Date: Tue, 20 Nov 2012 10:18:28 -0800 Subject: [PATCH 0252/1949] Make some minor comment improvements to Req.scala --- web/webkit/src/main/scala/net/liftweb/http/Req.scala | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/Req.scala b/web/webkit/src/main/scala/net/liftweb/http/Req.scala index 9d725ededc..bb2c356c2d 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Req.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Req.scala @@ -757,8 +757,8 @@ class Req(val path: ParsePath, _addlParams) /** - * Build a new Req, except it has a different path. - * Useful for creating Reqs with sub-paths + * Build a new Req, the same except with a different path. + * Useful for creating Reqs with sub-paths. */ def withNewPath(newPath: ParsePath): Req = { val outer = this @@ -787,7 +787,7 @@ class Req(val path: ParsePath, override lazy val accepts: Box[String] = outer.accepts /** - * What is the content type in order of preference by the requestor + * What is the content type in order of preference by the requester * calculated via the Accept header */ override lazy val weightedAccept: List[ContentType] = @@ -1058,7 +1058,7 @@ class Req(val path: ParsePath, /** - * Computer the Not Found via a Template + * Compute the Not Found via a Template */ private def notFoundViaTemplate(path: ParsePath): LiftResponse = { this.initIfUnitted { @@ -1189,7 +1189,7 @@ class Req(val path: ParsePath, } /** - * What is the content type in order of preference by the requestor + * What is the content type in order of preference by the requester * calculated via the Accept header */ lazy val weightedAccept: List[ContentType] = accepts match { @@ -1255,7 +1255,7 @@ final case class RewriteResponse(path: ParsePath, /** * Maintains the context of resolving the URL when cookies are disabled from container. It maintains * low coupling such as code within request processing is not aware of the actual response that - * ancodes the URL. + * encodes the URL. */ object RewriteResponse { def apply(path: List[String], params: Map[String, String]) = new RewriteResponse(ParsePath(path, "", true, false), params, false) From d72d585b532e487743f28cb470d8399e82f1c885 Mon Sep 17 00:00:00 2001 From: Dave Briccetti Date: Tue, 20 Nov 2012 23:22:05 -0800 Subject: [PATCH 0253/1949] Fixed spelling error --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0865753fb8..d44201d511 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Lift is the most powerful, most secure web framework available today. There are Lift applications are: - Secure -- Lift apps are resistant to common vulnerabilities including many of the OWASP Top 10 -- Developer centeric -- Lift apps are fast to build, concise and easy to maintain +- Developer centric -- Lift apps are fast to build, concise and easy to maintain - Scalable -- Lift apps are high performance and scale in the real world to handle insane traffic levels - Interactive like a desktop app -- Lift's Comet support is unparalled and Lift's ajax support is super-easy and very secure From b72bff7485deabb55e0aecb3ed3c21eebda13bbc Mon Sep 17 00:00:00 2001 From: Dave Briccetti Date: Tue, 20 Nov 2012 23:50:12 -0800 Subject: [PATCH 0254/1949] =?UTF-8?q?Fix=20some=20spelling=20and=20grammat?= =?UTF-8?q?ical=20errors:=20=E2=80=9Ccheck=20out=E2=80=9D=20is=20a=20phras?= =?UTF-8?q?al=20verb,=20so=20is=20two=20words.=20=E2=80=9CCheckout,?= =?UTF-8?q?=E2=80=9D=20a=20noun=20or=20adjective,=20is=20one=20word.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d44201d511..e89f05ce06 100644 --- a/README.md +++ b/README.md @@ -74,11 +74,11 @@ This repository, `framework`, contains the following components: #### core -Core elements used by Lift projects. If you wish to reuse some of Lift's helpers and constructs, such as `Box`, this compenent may be all you need. However, a web application will most likely require one or more of Lift's components. +Core elements used by Lift projects. If you wish to reuse some of Lift's helpers and constructs, such as `Box`, this component may be all you need. However, a web application will most likely require one or more of Lift's components. #### web -This component included all of Lift's core HTTP and web handling. Including `lift-webkit` in your build process should be sufficient for basic applications and will include `lift-core` as a transative dependency. +This component includes all of Lift's core HTTP and web handling. Including `lift-webkit` in your build process should be sufficient for basic applications and will include `lift-core` as a transitive dependency. #### persistence @@ -102,7 +102,7 @@ The [examples](https://github.com/lift/examples) repository contains the source If you simply want to use Lift in your project, add Lift as a dependency to your build system or [download the JAR files directly](www.liftweb.net/download). -If you wish to build Lift from source, checkout this repository and use the included `liftsh` script to build some or all of the components you want. +If you wish to build Lift from source, check out this repository and use the included `liftsh` script to build some or all of the components you want. git clone https://github.com/lift/framework.git cd framework @@ -122,11 +122,11 @@ The Lift Google Group is the official place for support and is an active, friend ### Wiki -The Lift wiki is hosted on Assembla and can be found at [http://www.assembla.com/spaces/liftweb/wiki/](http://www.assembla.com/spaces/liftweb/wiki/). Anyone is welcome to contributed to the wiki; you must create an account and watch the Lift project in order to create or edit wiki pages. +The Lift wiki is hosted on Assembla and can be found at [http://www.assembla.com/spaces/liftweb/wiki/](http://www.assembla.com/spaces/liftweb/wiki/). Anyone is welcome to contribute to the wiki; you must create an account and watch the Lift project in order to create or edit wiki pages. ### ScalaDocs -The ScalaDocs for each release of Lift, in additional to the actual JARs, are available on ScalaTools. You can access the source code-based documentation for releases via the site's homepage or by navigating directly to the URL for the specific release. For instance, the Lift 2.4 release can be accessed at [http://scala-tools.org/mvnsites/liftweb-2.4/](http://scala-tools.org/mvnsites/liftweb-2.4/). +The ScalaDocs for each release of Lift, in additional to the actual JARs, are available on ScalaTools. You can access the source code–based documentation for releases via the site's homepage or by navigating directly to the URL for the specific release. For instance, the Lift 2.4 release can be accessed at [http://scala-tools.org/mvnsites/liftweb-2.4/](http://scala-tools.org/mvnsites/liftweb-2.4/). ## License From 0769d97a3ace668f164bee7ca819f2c6b65fa2b1 Mon Sep 17 00:00:00 2001 From: Dave Briccetti Date: Thu, 22 Nov 2012 21:15:53 -0800 Subject: [PATCH 0255/1949] Fix spelling errors in LiftRules.scala --- .../scala/net/liftweb/http/LiftRules.scala | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 4d4a147f37..521124895f 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -192,7 +192,7 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { /** * Use this to apply various effects to the notices. The user function receives the NoticeType - * and the id of the element containing the specific notice. Thus it is the function's responsability to form + * and the id of the element containing the specific notice. Thus it is the function's responsibility to form * the javascript code for the visual effects. This is applicable for both ajax and non ajax contexts. * For notices associated with ID's the user type will receive an Empty notice type. That's because the effect * is applied on the real estate holding the notices for this ID. Typically this contains a single message. @@ -201,7 +201,7 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { /** - * Holds user functions that willbe executed very early in the request processing. The functions' + * Holds user functions that will be executed very early in the request processing. The functions' * result will be ignored. */ val early = RulesSeq[(HTTPRequest) => Any] @@ -216,7 +216,7 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { * Defines the resources that are protected by authentication and authorization. If this function * is not defined for the input data, the resource is considered unprotected ergo no authentication * is performed. If this function is defined and returns a Full box, it means that this resource - * is protected by authentication,and authenticated subjed must be assigned to the role returned by + * is protected by authentication, and authenticated subjed must be assigned to the role returned by * this function or to a role that is child-of this role. If this function returns Empty it means that * this resource is protected by authentication but no authorization is performed meaning that roles are * not verified. @@ -224,7 +224,7 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { val httpAuthProtectedResource = RulesSeq[HttpAuthProtectedResourcePF] /** - * The HTTP authentication mechanism that ift will perform. See LiftRules.protectedResource + * The HTTP authentication mechanism that Lift will perform. See LiftRules.protectedResource */ @volatile var authentication: HttpAuthentication = NoAuthentication @@ -250,7 +250,7 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { @volatile var getLiftSession: (Req) => LiftSession = (req) => _getLiftSession(req) /** - * Attached an ID entity for resource URI specified in + * Attaches an ID entity for resource URI specified in * link or script tags. This allows controlling browser * resource caching. By default this just adds a query string * parameter unique per application lifetime. More complex @@ -386,7 +386,7 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { /** - * Holds user functions that are executed after the response was sent to client. The functions' result + * Holds user functions that are executed after the response is sent to client. The functions' result * will be ignored. */ val afterSend = RulesSeq[(BasicResponse, HTTPResponse, List[(String, String)], Box[Req]) => Any] @@ -512,11 +512,11 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { /** * Allows user adding additional Lift tags (the tags must be prefixed by lift namespace such as ). - * Each LiftTagPF function will be called with the folowing parameters: + * Each LiftTagPF function will be called with the following parameters: *
        *  - Element label,
        *  - The Element itselft,
    -   *  - The attrbutes
    +   *  - The attributes
        *  - The child nodes
        *  - The page name
        * 
    @@ -729,7 +729,7 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { @volatile var ajaxRetryCount: Box[Int] = Empty /** - * The JavaScript to execute at the begining of an + * The JavaScript to execute at the beginning of an * Ajax request (for example, showing the spinning working thingy) */ @volatile var ajaxStart: Box[() => JsCmd] = Empty @@ -738,7 +738,7 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { /** * Set the Ajax end JavaScript function. The - * Java calable alternative to assigning the var ajaxStart + * Java-callable alternative to assigning the var ajaxStart */ def setAjaxStart(f: Func0[JsCmd]): Unit = { ajaxStart = Full(f: () => JsCmd) @@ -761,7 +761,7 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { /** * Set the Ajax end JavaScript function. The - * Java calable alternative to assigning the var ajaxEnd + * Java-callable alternative to assigning the var ajaxEnd */ def setAjaxEnd(f: Func0[JsCmd]): Unit = { ajaxEnd = Full(f: () => JsCmd) @@ -951,7 +951,7 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { /** * Update the function here that calculates particular paths to - * exclused from context path rewriting + * excluded from context path rewriting */ val excludePathFromContextPathRewriting: FactoryMaker[String => Boolean] = new FactoryMaker(() => ((s: String) => false)) {} @@ -1039,9 +1039,9 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { /** * Add functionality around all of the HTTP request/response cycle. * This is an optimal place to get a database connection. Note that whatever - * is loaned at the begining of the request will not be returned until the end + * is loaned at the beginning of the request will not be returned until the end * of the request. It's super-important to (1) not do anything related - * to state or touch the request objects or anything else at the begining or + * to state or touch the request objects or anything else at the beginning or * end of the loan wrapper phase; (2) make sure that your code does not throw * exceptions as exceptions can cause major problems. */ @@ -1104,7 +1104,7 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { * In Dev mode and Test mode, return a non-200 response code * if there is an error on the page (one that would result in * the red box with the error message being displayed). This - * helps in automate testing + * helps in testing automation. */ @volatile var devModeFailureResponseCodeOverride: Box[Int] = Empty @@ -1125,7 +1125,7 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { private var otherPackages: List[String] = Nil /** - * Used by Lift to construct full pacakge names fromthe packages provided to addToPackages function + * Used by Lift to construct full package names from the packages provided to addToPackages function */ def buildPackage(end: String) = otherPackages.map(_ + "." + end) @@ -1138,7 +1138,7 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { } /** - * Tells Lift where to find Snippets,Views, Comet Actors and Lift ORM Model object + * Tells Lift where to find Snippets, Views, Comet Actors and Lift ORM Model object */ def addToPackages(what: Package) { if (doneBoot) throw new IllegalStateException("Cannot modify after boot."); @@ -1365,7 +1365,7 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { * be sent to the browser depending on the current RunMode (development, etc.) * * By default it returns an XhtmlResponse containing a predefined markup. You can overwrite this by calling - * LiftRules.exceptionHandler.prepend(...). If you are calling append then your code will not be calle since + * LiftRules.exceptionHandler.prepend(...). If you are calling append then your code will not be called since * a default implementation is already appended. * */ @@ -1483,12 +1483,12 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { val onEndServicing = RulesSeq[(Req, Box[LiftResponse]) => Unit] /** - * Tells Lift if the Comet JavaScript shoukd be included. By default it is set to true. + * Tells Lift if the Comet JavaScript should be included. By default it is set to true. */ @volatile var autoIncludeComet: LiftSession => Boolean = session => true /** - * Tells Lift if the Ajax JavaScript shoukd be included. By default it is set to true. + * Tells Lift if the Ajax JavaScript should be included. By default it is set to true. */ @deprecated("Use autoIncludeAjaxCalc", "2.4") @volatile var autoIncludeAjax: LiftSession => Boolean = session => autoIncludeAjaxCalc.vend().apply(session) @@ -1518,13 +1518,13 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { /** * By default lift uses a garbage-collection mechanism of removing unused bound functions from LiftSesssion. - * Setting this to false will disable this mechanims and there will be no Ajax polling requests attempted. + * Setting this to false will disable this mechanisms and there will be no Ajax polling requests attempted. */ @volatile var enableLiftGC = true; /** * If Lift garbage collection is enabled, functions that are not seen in the page for this period of time - * (given in milliseonds) will be discarded, hence eligibe for garbage collection. + * (given in milliseconds) will be discarded, hence eligible for garbage collection. * The default value is 10 minutes. */ @volatile var unusedFunctionsLifeTime: Long = 10 minutes @@ -1554,7 +1554,7 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { /** * If this is Full, comet updates (partialUpdates or reRenders) are * wrapped in a try/catch statement. The provided JsCmd is the body of - * the catch statement. Within that JsCmd, the varibale "e" refers to the + * the catch statement. Within that JsCmd, the variable "e" refers to the * caught exception. * * In development mode, this defaults to Full and the command within @@ -1587,7 +1587,7 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { ) /** - * Holds the last update time of the Ajax request. Based on this server mayreturn HTTP 304 status + * Holds the last update time of the Ajax request. Based on this server may return HTTP 304 status * indicating the client to used the cached information. */ @volatile var ajaxScriptUpdateTime: LiftSession => Long = session => { @@ -1596,7 +1596,7 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { } /** - * Determins the path parts and suffix from given path parts + * Determines the path parts and suffix from given path parts */ val suffixSplitters = RulesSeq[SplitSuffixPF].append { case parts => @@ -1661,7 +1661,7 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { private[http] def withMimeHeaders[T](map: Map[String, List[String]])(f: => T): T = _mimeHeaders.doWith(Full(map))(f) /** - * Holds the last update time of the Comet request. Based on this server mayreturn HTTP 304 status + * Holds the last update time of the Comet request. Based on this server may return HTTP 304 status * indicating the client to used the cached information. */ @volatile var cometScriptUpdateTime: LiftSession => Long = session => { From 4deabb32123b942d12798cbef82380f4decad80e Mon Sep 17 00:00:00 2001 From: Jeppe Nejsum Madsen Date: Tue, 20 Nov 2012 16:58:59 +0100 Subject: [PATCH 0256/1949] In DevMode, show alert if ajax call succeeds but the returned javascript contains an error --- .../scala/net/liftweb/http/js/ScriptRenderer.scala | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala b/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala index ff9fd0b970..44223afc8f 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala @@ -20,6 +20,7 @@ package js import net.liftweb.http._ import net.liftweb.common._ +import net.liftweb.util.Props /** * the default mechanisms for doing Ajax and Comet in Lift @@ -150,9 +151,18 @@ object ScriptRenderer { var failureFunc = function() { liftAjax.lift_ajaxInProcess = null; - var cnt = aboutToSend.retryCnt; + var cnt = aboutToSend.retryCnt;""" + + (if (!Props.devMode) "" else + """ + if (arguments.length == 3 && arguments[1] == 'parsererror') { + alert('The server call succeeded, but the returned Javascript contains an error: '+ + arguments[2] + '\n(This message is displayed in development mode only)'); + } else + """) + + + """ if (cnt < liftAjax.lift_ajaxRetryCount) { - aboutToSend.retryCnt = cnt + 1; + aboutToSend.retryCnt = cnt + 1; var now = (new Date()).getTime(); aboutToSend.when = now + (1000 * Math.pow(2, cnt)); queue.push(aboutToSend); From a737707bc8b12f23b152c73a335af7223a12ef65 Mon Sep 17 00:00:00 2001 From: Jeppe Nejsum Madsen Date: Wed, 21 Nov 2012 15:13:49 +0100 Subject: [PATCH 0257/1949] Added lift_logError to liftAjax and made the actual logging method configurable using LiftRules. Now defaults to using the javascript console if available or alert otherwise --- .../main/scala/net/liftweb/http/LiftRules.scala | 16 ++++++++++++++++ .../net/liftweb/http/js/ScriptRenderer.scala | 15 +++++++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 521124895f..c8385c4907 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -753,6 +753,22 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { () => (for (r <- S.request) yield r.isIE6 || r.isIE7 || r.isIE8) openOr true + /** + * The JavaScript to execute to log a message on the client side when + * liftAjax.lift_logError is called. + * + * If Empty no logging is performed + * The default when running in DevMode is to call lift_defaultLogError which + * will use JavaScript console if available or alert otherwise. + * + * To always use alert set: + * + * LiftRules.jsLogFunc = Full(v => JE.Call("alert",v).cmd) + */ + @volatile var jsLogFunc: Box[JsVar => JsCmd] = + if (Props.devMode) Full(v => JE.Call("liftAjax.lift_defaultLogError", v)) + else Empty + /** * The JavaScript to execute at the end of an * Ajax request (for example, removing the spinning working thingy) diff --git a/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala b/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala index 44223afc8f..241f2b91ec 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala @@ -21,6 +21,7 @@ package js import net.liftweb.http._ import net.liftweb.common._ import net.liftweb.util.Props +import net.liftweb.http.js.JE.JsVar /** * the default mechanisms for doing Ajax and Comet in Lift @@ -70,6 +71,17 @@ object ScriptRenderer { lift_uriSuffix: undefined, + lift_logError: function(msg) { + """ + (LiftRules.jsLogFunc.map(_(JsVar("msg")).toJsCmd) openOr "") + """ + }, + + lift_defaultLogError: function(msg) { + if (console && typeof console.error == 'function') + console.error(msg); + else + alert(msg); + }, + lift_ajaxQueueSort: function() { liftAjax.lift_ajaxQueue.sort(function (a, b) {return a.when - b.when;}); }, @@ -155,8 +167,7 @@ object ScriptRenderer { (if (!Props.devMode) "" else """ if (arguments.length == 3 && arguments[1] == 'parsererror') { - alert('The server call succeeded, but the returned Javascript contains an error: '+ - arguments[2] + '\n(This message is displayed in development mode only)'); + liftAjax.lift_logError('The server call succeeded, but the returned Javascript contains an error: '+arguments[2]) } else """) + From 10e2b9fb1d87273a3b316fdfa722f54cf1606974 Mon Sep 17 00:00:00 2001 From: Dave Briccetti Date: Tue, 27 Nov 2012 12:07:30 -0800 Subject: [PATCH 0258/1949] Make some minor comment improvements to S.scala. --- .../src/main/scala/net/liftweb/http/S.scala | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/S.scala b/web/webkit/src/main/scala/net/liftweb/http/S.scala index 721716c917..8bcb1d9217 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/S.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/S.scala @@ -2876,42 +2876,42 @@ for { def error(id: String, n: String) {error(id, Text(n))} /** - * Sets an NOTICE notice as plain text + * Sets a NOTICE notice as plain text */ def notice(n: String) {notice(Text(n))} /** - * Sets an NOTICE notice as an XML sequence + * Sets a NOTICE notice as an XML sequence */ def notice(n: NodeSeq) {p_notice.is += ((NoticeType.Notice, n, Empty))} /** - * Sets an NOTICE notice as and XML sequence and associates it with an id + * Sets a NOTICE notice as and XML sequence and associates it with an id */ def notice(id: String, n: NodeSeq) {p_notice.is += ((NoticeType.Notice, n, Full(id)))} /** - * Sets an NOTICE notice as plai text and associates it with an id + * Sets a NOTICE notice as plai text and associates it with an id */ def notice(id: String, n: String) {notice(id, Text(n))} /** - * Sets an WARNING notice as plain text + * Sets a WARNING notice as plain text */ def warning(n: String) {warning(Text(n))} /** - * Sets an WARNING notice as an XML sequence + * Sets a WARNING notice as an XML sequence */ def warning(n: NodeSeq) {p_notice += ((NoticeType.Warning, n, Empty))} /** - * Sets an WARNING notice as an XML sequence and associates it with an id + * Sets a WARNING notice as an XML sequence and associates it with an id */ def warning(id: String, n: NodeSeq) {p_notice += ((NoticeType.Warning, n, Full(id)))} /** - * Sets an WARNING notice as plain text and associates it with an id + * Sets a WARNING notice as plain text and associates it with an id */ def warning(id: String, n: String) {warning(id, Text(n))} From 6d4f64f186c11792815538263de04f9a7e2b5f0f Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 5 Dec 2012 00:15:56 -0500 Subject: [PATCH 0259/1949] Change header defs in LiftResponse classes to vals. We do this because LiftResponses are typically not serialized to InMemoryResponses until after the S context has been uninitialized. Since all the default headers implementations use S.getHeaders, they were losing all headers, as S.getHeaders always returns Nil when the S context is uninitialized. Amongst other things, this led e.g. XmlResponse to never include a proper Content-Type header in its response. --- .../src/main/scala/net/liftweb/http/LiftResponse.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftResponse.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftResponse.scala index ff922b23c2..9054882b9a 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftResponse.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftResponse.scala @@ -49,7 +49,7 @@ case class CreatedResponse(xml: Node, mime: String, addlHeaders: List[(String, S def code = 201 - def headers: List[(String, String)] = S.getHeaders(("Content-Type" -> mime) :: addlHeaders) + val headers: List[(String, String)] = S.getHeaders(("Content-Type" -> mime) :: addlHeaders) def cookies: List[HTTPCookie] = Nil @@ -674,7 +674,7 @@ case class XmlMimeResponse(xml: Node, mime: String, addlHeaders: List[(String, S def code = 200 - def headers: List[(String, String)] = S.getHeaders(("Content-Type" -> mime) :: addlHeaders) + val headers: List[(String, String)] = S.getHeaders(("Content-Type" -> mime) :: addlHeaders) def cookies: List[HTTPCookie] = Nil @@ -685,7 +685,7 @@ class XmlResponse(val xml: Node, val code: Int, val mime: String, val cookies: L val addlHeaders: List[(String, String)] = XmlResponse.addlHeaders) extends XmlNodeResponse { def docType = Empty - def headers: List[(String, String)] = S.getHeaders(("Content-Type" -> mime) :: addlHeaders) + val headers: List[(String, String)] = S.getHeaders(("Content-Type" -> mime) :: addlHeaders) def out: Node = xml } @@ -758,7 +758,7 @@ case class AtomResponse(xml: Node, addlHeaders: List[(String, String)] = XmlResp def code = 200 - def headers: List[(String, String)] = S.getHeaders(("Content-Type" -> "application/atom+xml; charset=utf-8") :: addlHeaders) + val headers: List[(String, String)] = S.getHeaders(("Content-Type" -> "application/atom+xml; charset=utf-8") :: addlHeaders) def cookies: List[HTTPCookie] = Nil @@ -773,7 +773,7 @@ case class OpenSearchResponse(xml: Node, addlHeaders: List[(String, String)] = X def code = 200 - def headers: List[(String, String)] = S.getHeaders(("Content-Type" -> "application/opensearchdescription+xml; charset=utf-8") :: + val headers: List[(String, String)] = S.getHeaders(("Content-Type" -> "application/opensearchdescription+xml; charset=utf-8") :: addlHeaders) def cookies: List[HTTPCookie] = Nil From 3240326025efb4848badb38c199b7c8e05281155 Mon Sep 17 00:00:00 2001 From: Jeppe Nejsum Madsen Date: Thu, 22 Nov 2012 12:11:45 +0100 Subject: [PATCH 0260/1949] Reprocess failed ajax request. When an ajax request fails the client will resend the request. With this patch, if the failure is caused by the server (http code >500) the retry-request will actually be processed again instead of being satisfied by the previously computed response (which was a failure) --- .../scala/net/liftweb/http/LiftServlet.scala | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala index d3653d0548..ebb83d5d93 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala @@ -28,6 +28,18 @@ import auth._ import provider._ import json.JsonAST.JValue +/** + * Wrap a LiftResponse and cache the result to avoid computing the actual response + * more than once + */ +private [http] case class CachedResponse(wrapped: LiftResponse) extends LiftResponse { + private val _cachedResponse = wrapped.toResponse + + def toResponse = _cachedResponse + + // Should we retry request processing + def failed_? = _cachedResponse.code >= 500 && _cachedResponse.code < 600 +} class LiftServlet extends Loggable { private var servletContext: HTTPContext = null @@ -619,8 +631,20 @@ class LiftServlet extends Loggable { val ret:Box[LiftResponse] = nextAction match { case Left(future) => - val result = runAjax(liftSession, requestState) - future.satisfy(result) + val result = runAjax(liftSession, requestState) map CachedResponse + + result foreach {resp => + if (resp.failed_?) { + // The request failed. The client will retry it, so + // remove it from the list of current Ajax requests that + // needs to be satisfied so we process the next request + // from scratch + liftSession.withAjaxRequests { currentAjaxRequests => + currentAjaxRequests.remove(RenderVersion.get) + } + } + else future.satisfy(result) + } result From ca2bdb80686593fd9cac24c056b586cd04496f11 Mon Sep 17 00:00:00 2001 From: Jeppe Nejsum Madsen Date: Mon, 3 Dec 2012 15:33:52 +0100 Subject: [PATCH 0261/1949] Make sure to satisfy any pending future even if the request failed --- .../scala/net/liftweb/http/LiftServlet.scala | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala index ebb83d5d93..deb8e81131 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala @@ -633,19 +633,17 @@ class LiftServlet extends Loggable { case Left(future) => val result = runAjax(liftSession, requestState) map CachedResponse - result foreach {resp => - if (resp.failed_?) { - // The request failed. The client will retry it, so - // remove it from the list of current Ajax requests that - // needs to be satisfied so we process the next request - // from scratch - liftSession.withAjaxRequests { currentAjaxRequests => - currentAjaxRequests.remove(RenderVersion.get) - } + if (result.exists(_.failed_?)) { + // The request failed. The client will retry it, so + // remove it from the list of current Ajax requests that + // needs to be satisfied so we re-process the next request + // from scratch + liftSession.withAjaxRequests { currentAjaxRequests => + currentAjaxRequests.remove(RenderVersion.get) } - else future.satisfy(result) } + future.satisfy(result) result case Right(future) => From 73471f8d63b909c836d57c01acfc61932d9fc1ec Mon Sep 17 00:00:00 2001 From: Tim Nelson Date: Fri, 30 Nov 2012 14:10:05 -0600 Subject: [PATCH 0262/1949] JObjectField doesn't serialize to BSON --- .../mongodb/record/field/JObjectField.scala | 32 ++++++++++------- .../net/liftweb/mongodb/record/Fixtures.scala | 6 ++-- .../mongodb/record/MongoFieldSpec.scala | 24 ++++++++++--- .../mongodb/record/MongoRecordSpec.scala | 36 +++++++++++++++++-- 4 files changed, 78 insertions(+), 20 deletions(-) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala index d7f9797aa4..8e2f69cac2 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala @@ -23,16 +23,18 @@ import common._ import http.js.JE._ import json._ import util.Helpers.tryo -import net.liftweb.record.{Field, FieldHelpers, MandatoryTypedField, Record} +import net.liftweb.record.{Field, FieldHelpers, MandatoryTypedField} import scala.xml.NodeSeq -class JObjectField[OwnerType <: Record[OwnerType]](rec: OwnerType) extends Field[JObject, OwnerType] with MandatoryTypedField[JObject] { +import com.mongodb._ - def asJs = asJValue match { - case JNothing => JsNull - case jv => JsRaw(compact(render(jv))) - } +abstract class JObjectField[OwnerType <: BsonRecord[OwnerType]](rec: OwnerType) +extends Field[JObject, OwnerType] +with MandatoryTypedField[JObject] +with MongoFieldFlavor[JObject] { + + def owner = rec def asJValue = valueBox openOr (JNothing: JValue) @@ -45,14 +47,15 @@ class JObjectField[OwnerType <: Record[OwnerType]](rec: OwnerType) extends Field def defaultValue = JObject(List()) def setFromAny(in: Any): Box[JObject] = in match { - case jv: JObject => Full(set(jv)) - case Some(jv: JObject) => Full(set(jv)) - case Full(jv: JObject) => Full(set(jv)) + case dbo: DBObject => setBox(setFromDBObject(dbo)) + case jv: JObject => setBox(Full(jv)) + case Some(jv: JObject) => setBox(Full(jv)) + case Full(jv: JObject) => setBox(Full(jv)) case seq: Seq[_] if !seq.isEmpty => seq.map(setFromAny).apply(0) case (s: String) :: _ => setFromString(s) - case null => Full(set(null)) + case null => setBox(Full(null)) case s: String => setFromString(s) - case None | Empty | Failure(_, _, _) => Full(set(null)) + case None | Empty | Failure(_, _, _) => setBox(Full(null)) case o => setFromString(o.toString) } @@ -64,5 +67,10 @@ class JObjectField[OwnerType <: Record[OwnerType]](rec: OwnerType) extends Field def toForm: Box[NodeSeq] = Empty - def owner = rec + def asDBObject: DBObject = valueBox + .map { v => JObjectParser.parse(v)(owner.meta.formats) } + .openOr(new BasicDBObject) + + def setFromDBObject(obj: DBObject): Box[JObject] = + Full(JObjectParser.serialize(obj)(owner.meta.formats).asInstanceOf[JObject]) } diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala index bc15fdf0c8..e7450b1a35 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala @@ -469,10 +469,12 @@ object RefFieldTestRecord extends RefFieldTestRecord with MongoMetaRecord[RefFie } -class JObjectFieldTestRecord private () extends Record[JObjectFieldTestRecord] { +class JObjectFieldTestRecord private () extends MongoRecord[JObjectFieldTestRecord] with ObjectIdPk[JObjectFieldTestRecord] { def meta = JObjectFieldTestRecord object mandatoryJObjectField extends JObjectField(this) } -object JObjectFieldTestRecord extends JObjectFieldTestRecord with MetaRecord[JObjectFieldTestRecord] +object JObjectFieldTestRecord extends JObjectFieldTestRecord with MongoMetaRecord[JObjectFieldTestRecord] { + override def formats = allFormats +} diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala index a1edf42d24..fbec632c01 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala @@ -39,6 +39,8 @@ import xml.{Elem, NodeSeq, Text} import util.{Helpers, FieldError} import Helpers._ +import org.bson.types.ObjectId + /** * Systems under specification for MongoField. */ @@ -508,11 +510,25 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundExample } "get set from JValue" in { - val fromJson = JObjectFieldTestRecord.fromJValue(json) + val rec = JObjectFieldTestRecord.createRecord + val recFromJson = rec.mandatoryJObjectField.setFromJValue(json) + + recFromJson.isDefined must_== true + recFromJson foreach { r => + r must_== json + } + success + } + "get set from JValue after BSON roundtrip" in { + val joftrJson: JObject = ("_id" -> ("$oid" -> ObjectId.get.toString)) ~ ("mandatoryJObjectField" -> ("minutes" -> 59)) + val fromJsonBox = JObjectFieldTestRecord.fromJValue(joftrJson) + + fromJsonBox.isDefined must_== true - fromJson.isDefined must_== true - fromJson foreach { r => - r.asJValue must_== json + fromJsonBox foreach { fromJson => + //Convert the test record, make a DBObject out of it, and make a record from that DBObject + val fromBson = JObjectFieldTestRecord.fromDBObject(fromJson.asDBObject) + fromBson.asJValue must_== fromJson.asJValue } success } diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala index bd2b8f2965..415fee7460 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala @@ -334,6 +334,11 @@ class MongoRecordSpec extends Specification with MongoTestKit { JField("legacyOptionalBsonRecordListField", JArray(List())) )) + // JObjectField + val joftrFieldJObject: JObject = ("minutes" -> 59) + val joftr = JObjectFieldTestRecord.createRecord.mandatoryJObjectField(joftrFieldJObject) + val joftrJson: JValue = ("_id" -> ("$oid" -> joftr.id.toString)) ~ ("mandatoryJObjectField" -> ("minutes" -> 59)) + "save and retrieve 'standard' type fields" in { checkMongoIsRunning @@ -407,6 +412,15 @@ class MongoRecordSpec extends Specification with MongoTestKit { srtrFromDb.toList map { tr => tr mustEqual srtr } + + joftr.save + + val joftrFromDb = JObjectFieldTestRecord.find(joftr.id.is) + joftrFromDb.isDefined must_== true + joftrFromDb foreach { tr => + tr must_== joftr + } + success } "save and retrieve Mongo type fields with default values" in { @@ -454,6 +468,16 @@ class MongoRecordSpec extends Specification with MongoTestKit { srtrFromDb.toList map { tr => tr mustEqual srtrDef } + + val joftrDef = JObjectFieldTestRecord.createRecord + joftrDef.save + + val joftrFromDb = JObjectFieldTestRecord.find(joftrDef.id.value) + joftrFromDb.isDefined must_== true + joftrFromDb foreach { tr => + tr mustEqual joftrDef + } + success } "convert Mongo type fields to JValue" in { @@ -471,6 +495,8 @@ class MongoRecordSpec extends Specification with MongoTestKit { srtrAsJValue \\ "legacyOptionalBsonRecordField" mustEqual srtrJson \\ "legacyOptionalBsonRecordField" srtrAsJValue \\ "mandatoryBsonRecordListField" mustEqual srtrJson \\ "mandatoryBsonRecordListField" srtrAsJValue \\ "legacyOptionalBsonRecordListField" mustEqual srtrJson \\ "legacyOptionalBsonRecordListField" + + joftr.asJValue mustEqual joftrJson } "get set from json string using lift-json parser" in { @@ -497,6 +523,12 @@ class MongoRecordSpec extends Specification with MongoTestKit { mtrFromJson.toList map { tr => tr mustEqual mtr } + + val joftrFromJson = JObjectFieldTestRecord.fromJsonString(compact(render(joftrJson))) + joftrFromJson.isDefined must_== true + joftrFromJson.toList map { tr => + tr mustEqual joftr + } } "handle null" in { @@ -799,7 +831,6 @@ class MongoRecordSpec extends Specification with MongoTestKit { } } - /* save throws an exception here but not above ??? "update dirty fields for a PatternFieldTestRecord" in { val pftrd = PatternFieldTestRecord.createRecord.save @@ -816,7 +847,8 @@ class MongoRecordSpec extends Specification with MongoTestKit { rec must_== pftrd rec.dirtyFields.length must_== 0 } - }*/ + success + } "update dirty fields for a ListTestRecord" in { val ltr = ListTestRecord.createRecord.save From 58c6d684921640b5a5e38d77866f9099200d809c Mon Sep 17 00:00:00 2001 From: Tim Nelson Date: Fri, 30 Nov 2012 14:15:53 -0600 Subject: [PATCH 0263/1949] Removed abstract from JObjectField --- .../scala/net/liftweb/mongodb/record/field/JObjectField.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala index 8e2f69cac2..3a62172e6b 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala @@ -29,7 +29,7 @@ import scala.xml.NodeSeq import com.mongodb._ -abstract class JObjectField[OwnerType <: BsonRecord[OwnerType]](rec: OwnerType) +class JObjectField[OwnerType <: BsonRecord[OwnerType]](rec: OwnerType) extends Field[JObject, OwnerType] with MandatoryTypedField[JObject] with MongoFieldFlavor[JObject] { From 8bb1982b4d3b9ff5cdc291748fd63fcc9dc638eb Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Thu, 6 Dec 2012 23:10:18 -0500 Subject: [PATCH 0264/1949] * Added comments to LiftRules.onBeginServicing and earlyInStateful * Fixed a lift-json test that was failing at random. --- .../src/test/scala/net/liftweb/json/JsonParserSpec.scala | 2 +- .../src/main/scala/net/liftweb/http/LiftRules.scala | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/core/json/src/test/scala/net/liftweb/json/JsonParserSpec.scala b/core/json/src/test/scala/net/liftweb/json/JsonParserSpec.scala index de1a6d3421..b1a7867c32 100644 --- a/core/json/src/test/scala/net/liftweb/json/JsonParserSpec.scala +++ b/core/json/src/test/scala/net/liftweb/json/JsonParserSpec.scala @@ -31,7 +31,7 @@ object JsonParserSpec extends Specification with JValueGen with ScalaCheck { "Any valid json can be parsed" in { val parsing = (json: JValue) => { parse(Printer.pretty(render(json))); true } - check(forAll(genJValue)(parsing)) + check(forAll(parsing)) } "Buffer size does not change parsing result" in { diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index c8385c4907..8d17e406cf 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -1260,6 +1260,10 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { /** * Execute certain functions early in a Stateful Request + * This is called early in a stateful request (one that's not serviced by a stateless REST request and + * one that's not marked as a stateless HTML page request). + * @dpp strongly recommends that everything that you do related to user state is done with earlyInStateful, + * instead of using onBeginServicing. */ val earlyInStateful = RulesSeq[Box[Req] => Unit] @@ -1486,6 +1490,10 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { /** * Holds user function hooks when the request is about to be processed + * It's legacy from when Lift was a lot more Rails-like. It's called literally at the very + * beginning of the servicing of the HTTP request. + * The S scope is not available nor is the DB connection available in onBeginServicing. + * We recommend using earlyInStateful. */ val onBeginServicing = RulesSeq[Req => Unit] From b135d9af9bb78a9770469779654ce1ae54b258a1 Mon Sep 17 00:00:00 2001 From: Dave Briccetti Date: Fri, 7 Dec 2012 20:36:53 -0800 Subject: [PATCH 0265/1949] Make some minor comment improvements to LiftActor.scala. --- .../main/scala/net/liftweb/actor/LiftActor.scala | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/core/actor/src/main/scala/net/liftweb/actor/LiftActor.scala b/core/actor/src/main/scala/net/liftweb/actor/LiftActor.scala index 9c030e1c29..4055a560c9 100644 --- a/core/actor/src/main/scala/net/liftweb/actor/LiftActor.scala +++ b/core/actor/src/main/scala/net/liftweb/actor/LiftActor.scala @@ -36,9 +36,9 @@ object LAScheduler extends Loggable { @volatile var maxThreadPoolSize = threadPoolSize * 25 /** - * If it's Full, then create a ArrayBlockingQueue - * otherwith create a LinkedBlockingQueue. Default - * to Full(200000) + * If it's Full, then create an ArrayBlockingQueue, + * otherwise create a LinkedBlockingQueue. Default + * to Full(200000). */ @volatile var blockingQueueSize: Box[Int] = Full(200000) @@ -181,9 +181,9 @@ trait SpecializedLiftActor[T] extends SimpleActor[T] { } /** - * This method inserts the message at the head of the mailbox + * This method inserts the message at the head of the mailbox. * It's protected because this functionality may or may not want - * to be exposed' + * to be exposed. */ protected def insertMsgAtHeadOfQueue_!(msg: T): Unit = { val toDo: () => Unit = baseMailbox.synchronized { @@ -218,7 +218,7 @@ trait SpecializedLiftActor[T] extends SimpleActor[T] { /** * You can wrap calls around the evaluation of the mailbox. This allows you to set up - * the environment + * the environment. */ protected def around[R](f: => R): R = aroundLoans match { case Nil => f @@ -354,7 +354,7 @@ with ForwardableActor[Any, Any] { /** * Send a message to the Actor and get an LAFuture * that will contain the reply (if any) from the message. - * This method calls !< and is here for Java compatibility + * This method calls !< and is here for Java compatibility. */ def sendAndGetFuture(msg: Any): LAFuture[Any] = this !< msg From 5fa976aa25cfb1a0f27c057ae2ccf11f48894401 Mon Sep 17 00:00:00 2001 From: Dave Briccetti Date: Fri, 7 Dec 2012 23:16:14 -0800 Subject: [PATCH 0266/1949] Make some minor comment improvements to CometActor.scala. --- .../scala/net/liftweb/http/CometActor.scala | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala b/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala index 6ad032fd93..6b6c88a3f2 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala @@ -34,8 +34,8 @@ import java.util.Locale /** * An actor that monitors other actors that are linked with it. If a watched - * actor terminates,this actor captures the Exit messag, executes failureFuncs - * and resurects the actor. + * actor terminates, this actor captures the Exit message, executes failureFuncs + * and resurrects the actor. */ object ActorWatcher extends scala.actors.Actor with Loggable { @@ -118,7 +118,7 @@ trait StatefulComet extends CometActor { protected var state: State = emptyState /** - * If there's some ThreadLocal variable that needs to be setup up + * If there's some ThreadLocal variable that needs to be set up * before processing the state deltas, set it up here. */ protected def setupLocalState[T](f: => T): T = f @@ -159,7 +159,7 @@ object AddAListener { * This is a message class for use with ListenerManager and CometListener * instances. The use of the shouldUpdate function is deprecated, and * should instead be handled by the message processing partial functions - * on the CometListner instances themselves. + * on the CometListener instances themselves. * * @see CometListener * @see ListenerManager @@ -209,7 +209,7 @@ object ListenerManager { * * override def mediumPriority = { * case Tick => { - * updateListeneres(Tick) + * updateListeners(Tick) * ActorPing.schedule(this, Tick, 1000L) * } * } @@ -337,7 +337,7 @@ trait CometListenee extends CometListener { /** * A LiftActorJ with ListenerManager. Subclass this class - * to get a Java-useable LiftActorJ with ListenerManager + * to get a Java-usable LiftActorJ with ListenerManager */ abstract class LiftActorJWithListenerManager extends LiftActorJ with ListenerManager { protected override def messageHandler: PartialFunction[Any, Unit] = @@ -368,7 +368,7 @@ trait CometListener extends CometActor { /** * Override this in order to selectively update listeners based on the given message. - * This method has been deprecated because it's executed in a seperate context from + * This method has been deprecated because it's executed in a separate context from * the session's context. This causes problems. Accept/reject logic should be done * in the partial function that handles the message. */ @@ -699,7 +699,7 @@ trait CometActor extends LiftActor with LiftCometActor with BindHelpers { /** * Override this method to deal with JSON sent from the browser via the sendJson function. This * is based on the Lift JSON package rather than the handleJson stuff based on the older util.JsonParser. This - * is the prefered mechanism. If you use the jsonSend call, you will get a JObject(JField("command", cmd), JField("param", params)) + * is the preferred mechanism. If you use the jsonSend call, you will get a JObject(JField("command", cmd), JField("param", params)) */ def receiveJson: PartialFunction[JsonAST.JValue, JsCmd] = Map() @@ -723,7 +723,7 @@ trait CometActor extends LiftActor with LiftCometActor with BindHelpers { def autoIncludeJsonCode: Boolean = false /** - * Creates the span element acting as the real estate for commet rendering. + * Creates the span element acting as the real estate for comet rendering. */ def buildSpan(time: Long, xml: NodeSeq): NodeSeq = { Elem(parentTag.prefix, parentTag.label, parentTag.attributes, @@ -811,7 +811,7 @@ trait CometActor extends LiftActor with LiftCometActor with BindHelpers { /** * We have to cache fixedRender and only change it if - * the tempalte changes or we get a reRender(true) + * the template changes or we get a reRender(true) */ private def internalFixedRender: Box[NodeSeq] = if (!cacheFixedRender) { @@ -824,9 +824,9 @@ trait CometActor extends LiftActor with LiftCometActor with BindHelpers { /** * By default, we do not cache the value of fixedRender. If it's - * expensive to recompute it each time there's a convertion + * expensive to recompute it each time there's a conversion * of something to a RenderOut, override this method if you - * want to cache fixedRender + * want to cache fixedRender. */ protected def cacheFixedRender = false @@ -1027,7 +1027,7 @@ trait CometActor extends LiftActor with LiftCometActor with BindHelpers { * There are implicit conversions for a bunch of stuff to * RenderOut (including NodeSeq). Thus, if you don't declare the return * turn to be something other than RenderOut and return something that's - * coersable into RenderOut, the compiler "does the right thing"(tm) for you. + * coercible into RenderOut, the compiler "does the right thing"(tm) for you. *
    * There are implicit conversions for NodeSeq, so you can return a pile of * XML right here. There's an implicit conversion for NodeSeq => NodeSeq, @@ -1038,7 +1038,7 @@ trait CometActor extends LiftActor with LiftCometActor with BindHelpers { * Note that the render method will be called each time a new browser tab * is opened to the comet component or the comet component is otherwise * accessed during a full page load (this is true if a partialUpdate - * has occured.) You may want to look at the fixedRender method which is + * has occurred.) You may want to look at the fixedRender method which is * only called once and sets up a stable rendering state. */ def render: RenderOut @@ -1197,10 +1197,10 @@ trait CometActor extends LiftActor with LiftCometActor with BindHelpers { /** * Compose the Message Handler function. By default, - * composes highPriority orElse mediumePriority orElse internalHandler orElse + * composes highPriority orElse mediumPriority orElse internalHandler orElse * lowPriority orElse internalHandler. But you can change how * the handler works if doing stuff in highPriority, mediumPriority and - * lowPriority is not enough + * lowPriority is not enough. */ protected def composeFunction: PartialFunction[Any, Unit] = composeFunction_i @@ -1250,7 +1250,7 @@ trait CometActor extends LiftActor with LiftCometActor with BindHelpers { * Convert a NodeSeq => NodeSeq to a RenderOut. The render method * returns a RenderOut. This method implicitly (in Scala) or explicitly * (in Java) will convert a NodeSeq => NodeSeq to a RenderOut. This - * is helpful if you use Lift's CSS Seletor Transforms to define + * is helpful if you use Lift's CSS Selector Transforms to define * rendering. */ protected implicit def nsToNsFuncToRenderOut(f: NodeSeq => NodeSeq) = From a4396526c4d0147ceeed8bb5a8cc1f3ae42b44de Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 8 Dec 2012 11:21:29 -0500 Subject: [PATCH 0267/1949] Added CONTRIBUTING.md. --- CONTRIBUTING.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..4aab8ab95d --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,22 @@ +The policy on issues is: +* Non-committers should discuss issues on the Lift mailing list before opening + a issue. +* All non-committer issues should have a link back to the mailing list + discussion that led to opening the issue so the whole world can see why the + issue came into being. +* All non-committer issues should be set to no milestone, no assignment and + normal (or lower) priority unless a committer explicitly asked for assignment + (that means they agreed to do the work). +* A committer may open a issue at whatever priority for whatever milestone and + assign it to himself. +* There should be very little discussion about issue in the issuing system. + The discussion should take place on the Lift list so the discussion is + available to a broader audience. + +We will accept pull requests into the Lift codebase if the pull requests meet +the following criteria: +* The request represents one or more of the following: + * Documentation including ScalaDoc comments in code + * Example code + * Small changes, enhancements, or bug fixes to Lift’s code +* The request includes a signature at the bottom of the /contributors.md file. From a6d6de020ec85aba95a7bff52658741113e1ae4a Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 8 Dec 2012 11:22:50 -0500 Subject: [PATCH 0268/1949] Add title to contribution guidelines. --- CONTRIBUTING.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4aab8ab95d..f4f18b9ef9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,3 +1,6 @@ +Ticket and Pull Request Guidelines +================================== + The policy on issues is: * Non-committers should discuss issues on the Lift mailing list before opening a issue. From 1359d9e5057ae5863c332ac099854123cdb4d272 Mon Sep 17 00:00:00 2001 From: Jeppe Nejsum Madsen Date: Mon, 10 Dec 2012 10:32:05 +0100 Subject: [PATCH 0269/1949] Update version of joda time, slf4j & logback --- project/Build.scala | 4 ++-- project/Dependencies.scala | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index aee0a723f9..3f4db29760 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -63,14 +63,14 @@ object BuildDef extends Build { coreProject("json-ext") .dependsOn(common, json) .settings(description := "Extentions to JSON Library", - libraryDependencies ++= Seq(commons_codec, joda_time)) + libraryDependencies ++= Seq(commons_codec, joda_time, joda_convert)) lazy val util = coreProject("util") .dependsOn(actor, json) .settings(description := "Utilities Library", parallelExecution in Test := false, - libraryDependencies <++= scalaVersion {sv => Seq(scala_compiler(sv), joda_time, commons_codec, javamail, log4j, htmlparser)}) + libraryDependencies <++= scalaVersion {sv => Seq(scala_compiler(sv), joda_time, joda_convert, commons_codec, javamail, log4j, htmlparser)}) // Web Projects diff --git a/project/Dependencies.scala b/project/Dependencies.scala index da360e8f2c..17d6547477 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -27,7 +27,7 @@ object Dependencies { lazy val CVMapping29 = crossMapped("2.9.1-1" -> "2.9.2", "2.9.1" -> "2.9.2") lazy val CVMappingAll = crossMapped("2.9.2" -> "2.9.1", "2.9.1-1" -> "2.9.1") - lazy val slf4jVersion = "1.6.4" + lazy val slf4jVersion = "1.7.2" lazy val scalazGroup = defaultOrMapped("org.scalaz") lazy val scalazVersion = defaultOrMapped("6.0.4", "2.9.0" -> "6.0.RC2") @@ -39,7 +39,8 @@ object Dependencies { lazy val commons_httpclient = "commons-httpclient" % "commons-httpclient" % "3.1" lazy val dispatch_http = "net.databinder" % "dispatch-http" % "0.7.8" cross CVMappingAll lazy val javamail = "javax.mail" % "mail" % "1.4.4" - lazy val joda_time = "joda-time" % "joda-time" % "1.6.2" // TODO: 2.1 + lazy val joda_time = "joda-time" % "joda-time" % "2.1" + lazy val joda_convert = "org.joda" % "joda-convert" % "1.2" lazy val htmlparser = "nu.validator.htmlparser" % "htmlparser" % "1.4" lazy val mongo_java_driver = "org.mongodb" % "mongo-java-driver" % "2.7.3" lazy val paranamer = "com.thoughtworks.paranamer" % "paranamer" % "2.4.1" @@ -57,7 +58,7 @@ object Dependencies { // Provided scope: // Scope provided by container, available only in compile and test classpath, non-transitive by default. - lazy val logback = "ch.qos.logback" % "logback-classic" % "1.0.1" % "provided" + lazy val logback = "ch.qos.logback" % "logback-classic" % "1.0.9" % "provided" lazy val log4j = "log4j" % "log4j" % "1.2.16" % "provided" lazy val slf4j_log4j12 = "org.slf4j" % "slf4j-log4j12" % slf4jVersion % "provided" lazy val persistence_api = "javax.persistence" % "persistence-api" % "1.0" % "provided" From ffda969bd34d7cfa73f62ada74078390dc1470d8 Mon Sep 17 00:00:00 2001 From: Jeppe Nejsum Madsen Date: Mon, 10 Dec 2012 10:37:24 +0100 Subject: [PATCH 0270/1949] Make mapper specs pass even if system locale is da_DK :-) --- .../mapper/src/test/scala/net/liftweb/mapper/MapperSpec.scala | 4 ++-- .../src/test/scala/net/liftweb/mapper/MapperSpecsModel.scala | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/persistence/mapper/src/test/scala/net/liftweb/mapper/MapperSpec.scala b/persistence/mapper/src/test/scala/net/liftweb/mapper/MapperSpec.scala index 670d92b56e..0c963f14d8 100644 --- a/persistence/mapper/src/test/scala/net/liftweb/mapper/MapperSpec.scala +++ b/persistence/mapper/src/test/scala/net/liftweb/mapper/MapperSpec.scala @@ -99,8 +99,8 @@ class MapperSpec extends Specification with BeforeExample { SampleModel.firstName.displayName must_== "DEFAULT:SampleModel.firstName" LiftRules.localeCalculator = (request: Box[HTTPRequest]) => request.flatMap(_.locale) - .openOr(new Locale("da", "DK")) - SampleModel.firstName.displayName must_== "da_DK:SampleModel.firstName" + .openOr(new Locale("xx", "YY")) + SampleModel.firstName.displayName must_== "xx_YY:SampleModel.firstName" LiftRules.localeCalculator = localeCalculator success diff --git a/persistence/mapper/src/test/scala/net/liftweb/mapper/MapperSpecsModel.scala b/persistence/mapper/src/test/scala/net/liftweb/mapper/MapperSpecsModel.scala index b77f24bffa..8217fa1215 100644 --- a/persistence/mapper/src/test/scala/net/liftweb/mapper/MapperSpecsModel.scala +++ b/persistence/mapper/src/test/scala/net/liftweb/mapper/MapperSpecsModel.scala @@ -50,7 +50,7 @@ object MapperSpecsModel { val mapperName = bm.dbName val displayName = name match { case "firstName" if l == Locale.getDefault() => "DEFAULT:" + mapperName + "." + name - case "firstName" if l == new Locale("da", "DK") => "da_DK:" + mapperName + "." + name + case "firstName" if l == new Locale("xx", "YY") => "xx_YY:" + mapperName + "." + name case _ => name } displayName From 6a03486258cd0fd5f6591f1a1dc632285e2ac613 Mon Sep 17 00:00:00 2001 From: Tim Nelson Date: Tue, 11 Dec 2012 10:06:37 -0600 Subject: [PATCH 0271/1949] Upgrade mongo-java-driver to v2.10.1 --- project/Dependencies.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 17d6547477..1532e13d64 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -42,7 +42,7 @@ object Dependencies { lazy val joda_time = "joda-time" % "joda-time" % "2.1" lazy val joda_convert = "org.joda" % "joda-convert" % "1.2" lazy val htmlparser = "nu.validator.htmlparser" % "htmlparser" % "1.4" - lazy val mongo_java_driver = "org.mongodb" % "mongo-java-driver" % "2.7.3" + lazy val mongo_java_driver = "org.mongodb" % "mongo-java-driver" % "2.10.1" lazy val paranamer = "com.thoughtworks.paranamer" % "paranamer" % "2.4.1" lazy val scalajpa = "org.scala-libs" % "scalajpa" % "1.4" cross CVMapping29 lazy val scalap: ModuleMap = "org.scala-lang" % "scalap" % _ From 66b20ad9f817bc751e62bca67dddd4ec6cf2fad4 Mon Sep 17 00:00:00 2001 From: Greg Flanagan Date: Mon, 3 Dec 2012 20:50:09 -0800 Subject: [PATCH 0272/1949] Adding ajaxEditableSelect to SHtml. Allows users to dynamically add new selections into an ajaxSelect. --- contributors.md | 6 ++++ .../main/scala/net/liftweb/http/SHtml.scala | 32 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/contributors.md b/contributors.md index 04d6a16591..15aafd90fc 100644 --- a/contributors.md +++ b/contributors.md @@ -65,3 +65,9 @@ Francis Rhys-Jones ### Email: ### francis.rhys-jones at guardian dot co dot uk + +### Name: ### +Gregory Flanagan + +### Email: ### +gregmflanagan at gmail dot com diff --git a/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala b/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala index dbf36d1646..282315f732 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala @@ -935,6 +935,38 @@ trait SHtml { attrs: _*) } + /** + * Create a select box based on the list with a default value and the function + * to be executed on form submission with an additional selection that + * transforms select into an ajaxText allowing the user to add a new select option + * Warning: id attribute in attrs will get clobbered + * + * @param options -- a list of value and text pairs (value, text to display) + * @param default -- the default value (or Empty if no default value) + * @param func -- the function to execute when a new selection is made + */ + def ajaxEditableSelect(opts: Seq[(String, String)], deflt: Box[String], + f: String => JsCmd, attrs: ElemAttr*): Elem = { + + val id = nextFuncName + val textOpt = nextFuncName + val idAttr = Seq(ElemAttr.pairToBasic("id", id)) + val options = opts :+ (textOpt , "New Element") + var _options = options + + def addId(elem: Elem) = (idAttr.foldLeft(elem)(_ % _)) + + lazy val func: (String) => JsCmd = (select: String) => { + def text(in: String): JsCmd = { + _options = (in, in) +: _options + Replace(id, addId({ajaxSelect(_options, Some(in), func, attrs: _*)})) + } + if (select == textOpt) Replace(id, addId({ajaxText("", text(_), attrs: _*)})) & Focus(id) else f(select) + } + + addId({ajaxSelect(options, deflt, func, attrs: _*)}) + } + def ajaxSelect(opts: Seq[(String, String)], deflt: Box[String], func: String => JsCmd, attrs: ElemAttr*): Elem = ajaxSelect_*(opts, deflt, Empty, SFuncHolder(func), attrs: _*) From d35132a3cf0add70761f54ac8c251c87c71f6c0c Mon Sep 17 00:00:00 2001 From: Greg Flanagan Date: Tue, 4 Dec 2012 21:35:59 -0800 Subject: [PATCH 0273/1949] don't clobber a user given id attribute --- .../src/main/scala/net/liftweb/http/SHtml.scala | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala b/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala index 282315f732..a7d629a08f 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala @@ -939,7 +939,6 @@ trait SHtml { * Create a select box based on the list with a default value and the function * to be executed on form submission with an additional selection that * transforms select into an ajaxText allowing the user to add a new select option - * Warning: id attribute in attrs will get clobbered * * @param options -- a list of value and text pairs (value, text to display) * @param default -- the default value (or Empty if no default value) @@ -948,23 +947,21 @@ trait SHtml { def ajaxEditableSelect(opts: Seq[(String, String)], deflt: Box[String], f: String => JsCmd, attrs: ElemAttr*): Elem = { - val id = nextFuncName + val id = attrs.collectFirst { case BasicElemAttr(name, value) if name == "id" => value } getOrElse nextFuncName + val attributes = if(attrs.contains(BasicElemAttr("id", id))) attrs else BasicElemAttr("id", id) +: attrs val textOpt = nextFuncName - val idAttr = Seq(ElemAttr.pairToBasic("id", id)) - val options = opts :+ (textOpt , "New Element") + val options = opts :+ (textOpt , "New Element") var _options = options - def addId(elem: Elem) = (idAttr.foldLeft(elem)(_ % _)) - lazy val func: (String) => JsCmd = (select: String) => { def text(in: String): JsCmd = { _options = (in, in) +: _options - Replace(id, addId({ajaxSelect(_options, Some(in), func, attrs: _*)})) + Replace(id, ajaxSelect(_options, Some(in), func, attributes: _*)) } - if (select == textOpt) Replace(id, addId({ajaxText("", text(_), attrs: _*)})) & Focus(id) else f(select) + if (select == textOpt) Replace(id, ajaxText("", text(_), attributes: _*)) & Focus(id) else f(select) } - addId({ajaxSelect(options, deflt, func, attrs: _*)}) + ajaxSelect(options, deflt, func, attributes: _*) } def ajaxSelect(opts: Seq[(String, String)], deflt: Box[String], From 0ffc21c762e024ba92eb2c8d30aa9c758baf92bb Mon Sep 17 00:00:00 2001 From: Jeppe Nejsum Madsen Date: Tue, 11 Dec 2012 11:00:04 +0100 Subject: [PATCH 0274/1949] Memoize the value of a resource lookup for the duration of the request. Closes #1372 --- .../src/main/scala/net/liftweb/http/S.scala | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/S.scala b/web/webkit/src/main/scala/net/liftweb/http/S.scala index 8bcb1d9217..980ad8e254 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/S.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/S.scala @@ -933,18 +933,10 @@ trait S extends HasParams with Loggable { * If you do not define an entry for a particular key, we fall back to using * Lift's core entries. * - * We cache the values in modes other than DevMode - * * @see LiftRules.resourceNames * @see LiftRules.resourceBundleFactories */ - def _resourceBundles: List[ResourceBundle] = resourceBundles(locale) ++ liftCoreResourceBundle.toList - - private lazy val cachedResourceBundles = _resourceBundles - - private def resourceBundles: List[ResourceBundle] = - if (Props.devMode) _resourceBundles else cachedResourceBundles - + def resourceBundles: List[ResourceBundle] = resourceBundles(locale) ++ liftCoreResourceBundle.toList def resourceBundles(loc: Locale): List[ResourceBundle] = { _resBundle.box match { @@ -985,6 +977,9 @@ trait S extends HasParams with Loggable { */ def liftCoreResourceBundle: Box[ResourceBundle] = _liftCoreResBundle.is + + private object resourceValueCache extends TransientRequestMemoize[(String, Locale), String] + /** * Get a localized string or return the original string. * We first try your own bundle resources, if that fails, we try @@ -996,7 +991,7 @@ trait S extends HasParams with Loggable { * * @see # resourceBundles */ - def ?(str: String): String = ?!(str, resourceBundles) + def ?(str: String): String = resourceValueCache.get(str -> locale, ?!(str, resourceBundles)) /** * Get a localized string or return the original string. @@ -1011,7 +1006,8 @@ trait S extends HasParams with Loggable { * * @see # resourceBundles */ - def ?(str: String, locale: Locale): String = ?!(str, resourceBundles(locale)) + def ?(str: String, locale: Locale): String = resourceValueCache.get(str -> locale, ?!(str, resourceBundles(locale))) + /** * Attempt to localize and then format the given string. This uses the String.format method * to format the localized string. From 666bc55018b6bdcaffc45c5914d7da4d75c2055d Mon Sep 17 00:00:00 2001 From: Jeppe Nejsum Madsen Date: Wed, 12 Dec 2012 12:48:31 +0100 Subject: [PATCH 0275/1949] Avoid exceptions in default error handler. The toString method on Req will throw if it contains files that are too large for the configured limits --- web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 8d17e406cf..2957892575 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -1391,11 +1391,11 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { */ val exceptionHandler = RulesSeq[ExceptionHandlerPF].append { case (Props.RunModes.Development, r, e) => - logger.error("Exception being returned to browser when processing " + r.uri.toString + ": " + showException(e)) + logger.error("Exception being returned to browser when processing " + r.uri.toString, e) XhtmlResponse(( Exception occured while processing {r.uri}
    {showException(e)}
    ), S.htmlProperties.docType, List("Content-Type" -> "text/html; charset=utf-8"), Nil, 500, S.ieMode) case (_, r, e) => - logger.error("Exception being returned to browser when processing " + r, e) + logger.error("Exception being returned to browser when processing " + r.uri.toString, e) XhtmlResponse(( Something unexpected happened while serving the page at {r.uri} ), S.htmlProperties.docType, List("Content-Type" -> "text/html; charset=utf-8"), Nil, 500, S.ieMode) } From 3f9ea57833f8015f544079dde1cbef441e238192 Mon Sep 17 00:00:00 2001 From: Jeppe Nejsum Madsen Date: Wed, 12 Dec 2012 13:41:01 +0100 Subject: [PATCH 0276/1949] Improve error handling in case exceptionHandlers throw. Now any errors that occurs in exceptionHandlers are ignored (but logged), instead returning an empty response --- .../scala/net/liftweb/http/LiftServlet.scala | 8 +--- .../scala/net/liftweb/http/LiftSession.scala | 2 +- .../src/main/scala/net/liftweb/http/S.scala | 39 ++++++++++++------- 3 files changed, 27 insertions(+), 22 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala index deb8e81131..4806b49181 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala @@ -310,11 +310,7 @@ class LiftServlet extends Loggable { } } catch { case foc: LiftFlowOfControlException => throw foc - case e: Exception if !e.getClass.getName.endsWith("RetryRequest") => { - val bundle = (Props.mode, req, e) - S.assertExceptionThrown() - NamedPF.applyBox(bundle, LiftRules.exceptionHandler.toList) - } + case e: Exception if !e.getClass.getName.endsWith("RetryRequest") => S.runExceptionHandlers(req, e) } tryo { @@ -535,7 +531,7 @@ class LiftServlet extends Loggable { } } catch { case foc: LiftFlowOfControlException => throw foc - case e => S.assertExceptionThrown() ; NamedPF.applyBox((Props.mode, requestState, e), LiftRules.exceptionHandler.toList); + case e => S.runExceptionHandlers(requestState, e) } } diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index fc58d45941..d1bb00662a 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -1228,7 +1228,7 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, case e: LiftFlowOfControlException => throw e - case e => S.assertExceptionThrown() ; NamedPF.applyBox((Props.mode, request, e), LiftRules.exceptionHandler.toList); + case e => S.runExceptionHandlers(request, e) } diff --git a/web/webkit/src/main/scala/net/liftweb/http/S.scala b/web/webkit/src/main/scala/net/liftweb/http/S.scala index 980ad8e254..70b11f6924 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/S.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/S.scala @@ -1113,20 +1113,31 @@ trait S extends HasParams with Loggable { */ def uri: String = request.map(_.uri).openOr("/") -/** -* Returns the query string for the current request -*/ -def queryString: Box[String] = -for { - req <- request - queryString <- req.request.queryString -} yield queryString - + /** + * Returns the query string for the current request + */ + def queryString: Box[String] = for { + req <- request + queryString <- req.request.queryString + } yield queryString + + + def uriAndQueryString: Box[String] = for { + req <- this.request + } yield req.uri + (queryString.map(s => "?"+s) openOr "") -def uriAndQueryString: Box[String] = -for { - req <- this.request -} yield req.uri + (queryString.map(s => "?"+s) openOr "") + /** + * Run any configured exception handlers and make sure errors in + * the handlers are ignored + */ + def runExceptionHandlers(req: Req, orig: Throwable): Box[LiftResponse] = { + S.assertExceptionThrown() + tryo{(t: Throwable) => + logger.error("An error occurred while running error handlers", t) + logger.error("Original error causing error handlers to be run", orig)} { + NamedPF.applyBox((Props.mode, req, orig), LiftRules.exceptionHandler.toList); + } openOr Empty + } private object _skipXmlHeader extends TransientRequestVar(false) @@ -2523,8 +2534,6 @@ for { } } - private def booster(lst: List[String], func: String => Any): Unit = lst.foreach(v => func(v)) - /** * Decorates an URL with jsessionid parameter in case cookies are disabled from the container. Also * it appends general purpose parameters defined by LiftRules.urlDecorate From 47b8ec01c76274451e33fc629acc2659e8340e7c Mon Sep 17 00:00:00 2001 From: Jeppe Nejsum Madsen Date: Wed, 12 Dec 2012 17:19:51 +0100 Subject: [PATCH 0277/1949] Return a real error message & 500 status in case the error processing fails --- web/webkit/src/main/scala/net/liftweb/http/S.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/S.scala b/web/webkit/src/main/scala/net/liftweb/http/S.scala index 70b11f6924..983efb6809 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/S.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/S.scala @@ -1136,7 +1136,7 @@ trait S extends HasParams with Loggable { logger.error("An error occurred while running error handlers", t) logger.error("Original error causing error handlers to be run", orig)} { NamedPF.applyBox((Props.mode, req, orig), LiftRules.exceptionHandler.toList); - } openOr Empty + } openOr Full(PlainTextResponse("An error has occurred while processing an error using the functions in LiftRules.exceptionHandler. Check the log for details.", 500)) } private object _skipXmlHeader extends TransientRequestVar(false) From 03fde40e1f66b970c42a5b3ef230f8417f535418 Mon Sep 17 00:00:00 2001 From: Joni Freeman Date: Mon, 17 Dec 2012 19:30:16 +0200 Subject: [PATCH 0278/1949] Fix parsing when buffer is exhausted --- .../main/scala/net/liftweb/json/JsonParser.scala | 16 ++++++++++------ .../scala/net/liftweb/json/JsonParserSpec.scala | 2 +- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala index 5f179409f8..8bb2853e53 100644 --- a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala +++ b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala @@ -169,7 +169,7 @@ object JsonParser { } def newValue(v: JValue) { - if(!vals.isEmpty) + if (!vals.isEmpty) vals.peek(classOf[JValue]) match { case f: JField => vals.pop(classOf[JField]) @@ -253,7 +253,9 @@ object JsonParser { s.append(first) while (wasInt) { val c = buf.next - if (c == '.' || c == 'e' || c == 'E') { + if (c == EOF) { + wasInt = false + } else if (c == '.' || c == 'e' || c == 'E') { doubleVal = true s.append(c) } else if (!(Character.isDigit(c) || c == '.' || c == 'e' || c == 'E' || c == '-')) { @@ -345,7 +347,7 @@ object JsonParser { def back = cur = cur-1 def next: Char = { - if (cur == offset && read < 0) { + if (cur >= offset && read < 0) { if (eofIsFailure) throw new ParseException("unexpected eof", null) else EOF } else { val c = segment(cur) @@ -398,9 +400,11 @@ object JsonParser { } val length = in.read(segment, offset, segment.length-offset) - cur = offset - offset += length - length + if (length != -1) { + cur = offset + offset += length + length + } else -1 } } diff --git a/core/json/src/test/scala/net/liftweb/json/JsonParserSpec.scala b/core/json/src/test/scala/net/liftweb/json/JsonParserSpec.scala index b1a7867c32..de1a6d3421 100644 --- a/core/json/src/test/scala/net/liftweb/json/JsonParserSpec.scala +++ b/core/json/src/test/scala/net/liftweb/json/JsonParserSpec.scala @@ -31,7 +31,7 @@ object JsonParserSpec extends Specification with JValueGen with ScalaCheck { "Any valid json can be parsed" in { val parsing = (json: JValue) => { parse(Printer.pretty(render(json))); true } - check(forAll(parsing)) + check(forAll(genJValue)(parsing)) } "Buffer size does not change parsing result" in { From 4b3e1299e5ee7a32fb1f9239797270b19d72a474 Mon Sep 17 00:00:00 2001 From: Ali Salim Rashid Date: Fri, 14 Dec 2012 15:42:30 +0300 Subject: [PATCH 0279/1949] Remove lift-couchdb from framework. It is now a lift module. --- contributors.md | 6 + .../net/liftweb/couchdb/CouchRecord.scala | 252 ------------- .../scala/net/liftweb/couchdb/Database.scala | 254 -------------- .../net/liftweb/couchdb/DispatchJSON.scala | 69 ---- .../net/liftweb/couchdb/DocumentHelpers.scala | 108 ------ .../net/liftweb/couchdb/JSONRecord.scala | 281 --------------- .../liftweb/couchdb/CouchDatabaseSpec.scala | 92 ----- .../liftweb/couchdb/CouchDocumentSpec.scala | 117 ------- .../net/liftweb/couchdb/CouchQuerySpec.scala | 197 ----------- .../net/liftweb/couchdb/CouchRecordSpec.scala | 330 ------------------ .../net/liftweb/couchdb/JsonRecordSpec.scala | 171 --------- project/Build.scala | 8 +- project/Dependencies.scala | 1 - 13 files changed, 7 insertions(+), 1879 deletions(-) delete mode 100644 persistence/couchdb/src/main/scala/net/liftweb/couchdb/CouchRecord.scala delete mode 100644 persistence/couchdb/src/main/scala/net/liftweb/couchdb/Database.scala delete mode 100644 persistence/couchdb/src/main/scala/net/liftweb/couchdb/DispatchJSON.scala delete mode 100644 persistence/couchdb/src/main/scala/net/liftweb/couchdb/DocumentHelpers.scala delete mode 100644 persistence/couchdb/src/main/scala/net/liftweb/couchdb/JSONRecord.scala delete mode 100644 persistence/couchdb/src/test/scala/net/liftweb/couchdb/CouchDatabaseSpec.scala delete mode 100644 persistence/couchdb/src/test/scala/net/liftweb/couchdb/CouchDocumentSpec.scala delete mode 100644 persistence/couchdb/src/test/scala/net/liftweb/couchdb/CouchQuerySpec.scala delete mode 100644 persistence/couchdb/src/test/scala/net/liftweb/couchdb/CouchRecordSpec.scala delete mode 100644 persistence/couchdb/src/test/scala/net/liftweb/couchdb/JsonRecordSpec.scala diff --git a/contributors.md b/contributors.md index 15aafd90fc..d5940ca270 100644 --- a/contributors.md +++ b/contributors.md @@ -71,3 +71,9 @@ Gregory Flanagan ### Email: ### gregmflanagan at gmail dot com + +### Name: ### +Ali Salim Rashid + +### Email: ### +a.rashid at zantekk dot com \ No newline at end of file diff --git a/persistence/couchdb/src/main/scala/net/liftweb/couchdb/CouchRecord.scala b/persistence/couchdb/src/main/scala/net/liftweb/couchdb/CouchRecord.scala deleted file mode 100644 index eff437ba21..0000000000 --- a/persistence/couchdb/src/main/scala/net/liftweb/couchdb/CouchRecord.scala +++ /dev/null @@ -1,252 +0,0 @@ -/* - * Copyright 2010-2011 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 couchdb - -import scala.collection.immutable.Map -import dispatch.{Handler, Http} -import net.liftweb.common.{Box, Empty, Failure, Full} -import Box.{box2Iterable, option2Box} -import net.liftweb.json.JsonAST.{JField, JString, JValue} -import net.liftweb.record.field.OptionalStringField -import net.liftweb.util.ControlHelpers.tryo -import DocumentHelpers.jobjectToJObjectExtension - -object CouchDB { - /** Default database instance of the application */ - var defaultDatabase: Database = new Database("default") -} - -/** Base trait of records that can be stored in CouchDB */ -trait CouchRecord[MyType <: CouchRecord[MyType]] extends JSONRecord[MyType] { - self: MyType => - - /** Refine meta to require a CouchMetaRecord */ - def meta: CouchMetaRecord[MyType] - - /** The mandatory _id field */ - object id extends OptionalStringField(this, 100) with JSONField { - override def jsonName = Full("_id") - } - - /** The mandatory _rev field */ - object rev extends OptionalStringField(this, 100) with JSONField { - override def jsonName = Full("_rev") - } - - /** By default include a "type" field with value calculated by the typeName method */ - override def fixedAdditionalJFields: List[JField] = JField("type", JString(typeName))::Nil - - /** - * The value of the "type" field for this class of record. Only used with default implementation of extraEncodedFields. - * The default definition is the unqualified class name (e.g. the fully-qualified class name with the package name stripped) - */ - def typeName: String = { - val className = getClass.getName - - className.lastIndexOf('.') match { - case -1 => className - case n => className.substring(n+1) - } - } - - /** Save the record instance and return it */ - def save: Box[MyType] = for (ok <- meta.save(this)) yield this - - /** - * Save the instance and return the instance - */ - override def saveTheRecord(): Box[MyType] = this.save - - /** Return whether this instance was saved into the backing store or not */ - def saved_? : Boolean = meta.saved_?(this) - - /** Delete the record instance from the backing store */ - def delete_! : Box[Unit] = canDelete_? match { - case true => runSafe(meta.delete_!(this)) - case false => Failure("can't delete") - } - - /** Can this record instance be deleted (e.g. is it in the backing store in the first place?) */ - def canDelete_? : Boolean = meta.saved_?(this) - - /** The database this record is associated with */ - private var _database: Box[Database] = Empty - - /** Return the database this record is associated with, determining it if necessary */ - def database: Database = _database match { - case Full(database) => database - case _ => - _database = Full(_calcDatabase) - _database.openOrThrowException("We just made this a Full Box") - } - - /** Set the current database (if any) for this record */ - def database_= (database: Database): Unit = _database = Full(database) - - /** Override to calculate which database to use on a per-record basis */ - def calculateDatabase: PartialFunction[MyType, Database] = Map.empty - - /** - * Calculate the database for this record, falling back to the CouchMetaRecord's default database if - * calculateDatabase does not apply to this instance - */ - def _calcDatabase: Database = if (calculateDatabase.isDefinedAt(this)) calculateDatabase(this) else meta.defaultDatabase -} - -/** Base trait of meta records for records that can be stored in CouchDB */ -trait CouchMetaRecord[BaseRecord <: CouchRecord[BaseRecord]] extends JSONMetaRecord[BaseRecord] { - self: BaseRecord => - - /** Get the default Database to use, if the record's calculateDatabase function does not provide one. Defaults to CouchDB.defaultDatabase */ - def defaultDatabase: Database = CouchDB.defaultDatabase - - /** Get an Http instance to use when accessing CouchDB */ - def http: Http = Http - - /** Save the record instance in the backing store */ - def save(inst: BaseRecord): Box[Unit] = { - foreachCallback(inst, _.beforeSave) - try { - for (newDoc <- http(inst.database store inst.asJValue)) yield { - inst.id.setBox(newDoc._id) - inst.rev.setBox(newDoc._rev) - () - } - } finally { - foreachCallback(inst, _.afterSave) - } - } - - /** Was the record instance saved in the backing store? */ - def saved_? (inst: BaseRecord): Boolean = inst.id.valueBox.isDefined && inst.rev.valueBox.isDefined - - /** Delete the instance from the backing store. Only works if the instance is the current revision in the database. */ - def delete_! (inst: BaseRecord): Box[Unit] = { - foreachCallback(inst, _.beforeDelete) - try { - for { - id <- inst.id.valueBox - rev <- inst.rev.valueBox - deletedOk <- tryo(http(inst.database(id) @@ rev delete)) - } yield { - inst.id.setBox(Empty) - inst.rev.setBox(Empty) - () - } - } finally { - foreachCallback(inst, _.afterDelete) - } - } - - /** Query a single document by _id from the default database */ - def fetch(id: String): Box[BaseRecord] = fetchFrom(defaultDatabase, id) - - /** Query a single document by _id from the given database */ - def fetchFrom(database: Database, id: String): Box[BaseRecord] = tryo(http(database(id) fetch)).flatMap(fromJValue) - - /** Query a series of documents by _id from the default database */ - def fetchMany(ids: String*): Box[Seq[BaseRecord]] = fetchManyFrom(defaultDatabase, ids: _*) - - /** Query a series of documents by _id from the given database */ - def fetchManyFrom(database: Database, ids: String*): Box[Seq[BaseRecord]] = - for (resultBox <- tryo(http(database(ids) query)); result <- resultBox) - yield result.rows.flatMap(_.doc.flatMap(fromJValue)) - - /** - * Query records from the default database by document id. includeDocs is always on for this type of query. - * Filter refines the query (e.g. by key), see Queryable. - * Note that this is probably not very useful, as there is no way to constrain the documents retrieved by type - */ - def all(filter: AllDocs => AllDocs): Box[Seq[BaseRecord]] = allIn(defaultDatabase, filter) - - /** - * Query records from the given database by document id. includeDocs is always on for this type of query. - * Filter refines the query (e.g. by key), see Queryable. - * Note that this is probably not very useful, as there is no way to constrain the documents retrieved by type - */ - def allIn(database: Database, filter: AllDocs => AllDocs): Box[Seq[BaseRecord]] = - for (resultBox <- tryo(http(filter(database.all.includeDocs) query)); result <- resultBox) - yield result.rows.flatMap { row => row.doc.flatMap(fromJValue) } - - /** Query using a view in the default database. */ - def queryView(design: String, view: String): Box[Seq[BaseRecord]] = - queryViewFrom(defaultDatabase, design, view, identity) - - /** Query using a view in the default database. Filter refines the query (e.g. by key), see Queryable. */ - def queryView(design: String, view: String, filter: View => View): Box[Seq[BaseRecord]] = - queryViewFrom(defaultDatabase, design, view, filter) - - /** Query using a view in the given database. Filter refines the query (e.g. by key), see Queryable. */ - def queryViewFrom(database: Database, design: String, view: String, filter: View => View): Box[Seq[BaseRecord]] = - queryViewProjectionFrom(database, design, view, filter, _.value) - - /** - * Query using a view in the default database, returning records created from the documents returned with the view. - * If used against a reduce view, make sure to use dontReduce in the filter, otherwise CouchDB will signal an error. - * includeDocs are always on for this type of query. - */ - def queryViewDocs(design: String, view: String): Box[Seq[BaseRecord]] = - queryViewDocsFrom(defaultDatabase, design, view, identity) - - /** - * Query using a view in the default database, returning records created from the documents returned with the view. - * If used against a reduce view, make sure to use dontReduce in the filter, otherwise CouchDB will signal an error. - * Filter refines the query (e.g. by key), see Queryable. - * includeDocs are always on for this type of query. - */ - def queryViewDocs(design: String, view: String, filter: View => View): Box[Seq[BaseRecord]] = - queryViewDocsFrom(defaultDatabase, design, view, filter) - - /** - * Query using a view in the given database, returning records created from the documents returned with the view. - * If used against a reduce view, make sure to use dontReduce in the filter, otherwise CouchDB will signal an error. - * Filter refines the query (e.g. by key), see Queryable. - * includeDocs are always on for this type of query. - */ - def queryViewDocsFrom(database: Database, design: String, view: String, filter: View => View): Box[Seq[BaseRecord]] = - queryViewProjectionFrom(database, design, view, v => filter(v.includeDocs), _.doc) - - /** - * Query using a view in the default database, using some projection function that converts each QueryRow into a JSON document to read - * as the record. - */ - def queryViewProjection(design: String, view: String, project: QueryRow => Box[JValue]): Box[Seq[BaseRecord]] = - queryViewProjectionFrom(defaultDatabase, design, view, identity, project) - - /** - * Query using a view in the default database, using some projection function that converts each QueryRow into a JSON document to read - * as the record. Filter refines the query (e.g. by key), see Queryable. - */ - def queryViewProjection(design: String, view: String, filter: View => View, project: QueryRow => Box[JValue]): Box[Seq[BaseRecord]] = - queryViewProjectionFrom(defaultDatabase, design, view, filter, project) - - /** - * Query using a view in the given database, using some projection function that converts each QueryRow into a JSON document to read - * as the record. - */ - def queryViewProjectionFrom(database: Database, design: String, view: String, - filter: View => View, project: QueryRow => Box[JValue]): Box[Seq[BaseRecord]] = - for (resultBox <- tryo(http(filter(database.design(design).view(view)) query)); result <- resultBox) - yield result.rows.flatMap(row => project(row).flatMap(fromJValue)) - - /** Perform the given action with loose parsing turned on */ - def looseParsing[A](f: => A): A = - JSONMetaRecord.overrideIgnoreExtraJSONFields.doWith(true) { - JSONMetaRecord.overrideNeedAllJSONFields.doWith(false) { - f - } - } -} - diff --git a/persistence/couchdb/src/main/scala/net/liftweb/couchdb/Database.scala b/persistence/couchdb/src/main/scala/net/liftweb/couchdb/Database.scala deleted file mode 100644 index 3f773ba7ac..0000000000 --- a/persistence/couchdb/src/main/scala/net/liftweb/couchdb/Database.scala +++ /dev/null @@ -1,254 +0,0 @@ -/* - * Copyright 2010-2011 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 couchdb - -import scala.collection.{Map => MapTrait} -import scala.collection.immutable.Map -import scala.collection.mutable.ArrayBuffer -import scala.reflect.Manifest -import dispatch.{:/, Handler, Http, Request, StatusCode} -import net.liftweb.common.{Box, Empty, Failure, Full} -import net.liftweb.json.{DefaultFormats, Formats} -import net.liftweb.json.Extraction.{decompose, extract} -import net.liftweb.json.Implicits.string2jvalue -import net.liftweb.json.JsonAST.{JArray, JBool, JField, JInt, JObject, JString, JValue, render} -import net.liftweb.json.JsonDSL.pair2jvalue -import net.liftweb.json.Printer.compact -import net.liftweb.util.ControlHelpers.tryo -import DocumentHelpers.{jobjectToJObjectExtension, updateIdAndRev} -import DispatchJSON.requestToJSONRequest - -/** Helper functions */ -private[couchdb] object DatabaseHelpers { - /** Handles the JSON result of an update action by parsing out id and rev, updating the given original object with the values and returning it */ - def handleUpdateResult(original: JObject)(json: JValue): Box[JObject] = - for { - obj <- Full(json).asA[JObject] ?~ ("update result is not a JObject: " + json) - ok <- Full(json \ "ok" ).asA[JBool].filter(_.value) ?~ ("ok not present in reply or not true: "+json) - id <- Full(json \ "id" ).asA[JString].map(_.s) ?~ ("id not present or not a string: " + json) - rev <- Full(json \ "rev").asA[JString].map(_.s) ?~ ("rev not present or not a string: " + json) - } yield updateIdAndRev(original, id, rev) -} - -import DatabaseHelpers._ - -/** Single element Map implementation */ -object SingleElementMap { - /** Implicitly convert a pair to a single element map */ - implicit def pairToSingleElementMap[A, B](pair: (A, B)): MapTrait[A, B] = Map(pair) -} - -import SingleElementMap.pairToSingleElementMap - -/** Trait that adds a "fetch" method for getting a JObject from CouchDB */ -trait FetchableAsJObject { - self: Request => - - /** Fetch the document as a JObject */ - def fetch: Handler[JObject] = this ># (_.asInstanceOf[JObject]) -} - -/** Trait of requests that represent a document in a Couch database */ -trait Document extends Request with FetchableAsJObject { - /** Refine to a particular revision of the document. Only GET-style requests should be used with the resulting path */ - def at(rev: String): DocumentRevision = new Request(this < rev)) with DocumentRevision { } - - /** Alias for at */ - def @@ (rev: String): DocumentRevision = at(rev) - - /** Refine to a particular revision of the document by getting _rev from a given JObject. */ - def at(doc: JObject): DocumentRevision = at(doc._rev.openOrThrowException("legacy code")) - - /** Alias for at */ - def @@ (doc: JObject): DocumentRevision = at(doc) - - /** Store a new version of the document, returning the document with _id and _rev updated */ - def put(doc: JObject): Handler[Box[JObject]] = JSONRequest(this) <<<# doc ># handleUpdateResult(doc) _ - - /** Alias for put */ - def <<<# (doc: JObject): Handler[Box[JObject]] = put(doc) -} - -/** Trait of requests that represent a particular document revision in a Couch database */ -trait DocumentRevision extends Request with FetchableAsJObject { - /** Destroy the document. The document's current revision must be the revision represented by this request */ - def delete: Handler[Unit] = DELETE >| -} - -/** Trait of requests that represent a particular design document */ -trait Design extends Document { - /** Access a particular view by name that can be queried */ - def view(name: String): View = new Request(this / "_view" / name) with View { } -} - -/** Trait of requests that represent a view that can be queried */ -trait View extends Request with Queryable[View] { - protected def newQueryable(req: Request): View = new Request(req) with View { } -} - -/** Trait of requests representing all documents in a Couch database */ -trait AllDocs extends Request with Queryable[AllDocs] { - protected def newQueryable(req: Request): AllDocs = new Request(req) with AllDocs { } -} - -/** Trait of requests that support CouchDB querying. That is, _all_docs and views */ -trait Queryable[SelfType <: Queryable[SelfType]] { - self: Request => - - /** Create a new self-typed instance */ - protected def newQueryable(req: Request): SelfType - - /** Add parameters to the query */ - def withParams(params: MapTrait[String, Any]): SelfType = newQueryable(this <# (QueryResult.read _) - - /** Query for the given key only */ - def key(keyValue: JValue): SelfType = withParams("key" -> compact(render(keyValue))) - - /** Query for the given set of keys */ - def keys(keyValues: JValue*): SelfType = newQueryable(this <<# ("keys" -> JArray(keyValues.toList))) - - /** Restrict the query to only keys greater than or equal to the given key */ - def from(lowValue: JValue): SelfType = withParams("startkey" -> compact(render(lowValue))) - - /** Restrict the query to only keys greater than or equal to the given key, not including any documents that are earlier in the view than the given docid */ - def from(lowValue: JValue, docid: String): SelfType = withParams(Map("startkey" -> compact(render(lowValue)), "startkey_docid" -> docid)) - - /** Restrict the query to only keys less than or equal to the given key */ - def to(highValue: JValue): SelfType = withParams("endkey" -> compact(render(highValue))) - - /** Restrict the query to only keys less than or equal to the given key, not including any documents that are later in the view than the given docid */ - def to(highValue: JValue, docid: String): SelfType = withParams(Map("endkey" -> compact(render(highValue)), "endkey_docid" -> docid)) - - /** Limit the query to the given number of results */ - def limit(i: Int): SelfType = withParams("limit" -> i) - - /** Specify that stale view data is okay. Used for optimization -- some other query must keep the view fresh. */ - def staleOk: SelfType = withParams("stale" -> "ok") - - /** Specify a descending sort. Note that the reversal is applied before key filtering, so you must reverse your from(...) and to(...) values. */ - def descending: SelfType = withParams("descending" -> "true") - - /** Group results (see http://wiki.apache.org/couchdb/Introduction_to_CouchDB_views) */ - def group: SelfType = withParams("group" -> "true") - - /** Group results at the given level (see http://wiki.apache.org/couchdb/Introduction_to_CouchDB_views) */ - def group(level: Int): SelfType = withParams(Map("group" -> "true") + ("group_level" -> level)) - - /** Specify that reduction should not occur */ - def dontReduce: SelfType = withParams("reduce" -> "false") - - /** Include the associated document with each result */ - def includeDocs: SelfType = withParams("include_docs" -> "true") - - /** - * Query a range matching the given key prefix. Equivalent to composing from(prefix) and to(prefix with {} appended), - * e.g. from=["foobar"]&to=["foobar",{}] - */ - def arrayRange(prefix: List[JValue]): SelfType = from(JArray(prefix)) to(JArray(prefix ::: (JObject(Nil)::Nil))) -} - -/** Specialization of dispatch's Request that provides Couch specific functionality */ -class Database(couch: Request, database: String) extends Request(couch / database) { - /** Construct a Database request using host and port */ - def this(hostname: String, port: Int, database: String) = this(:/(hostname, port), database) - - /** Construct a Database request to a default installation of CouchDB on localhost (port 5984) */ - def this(database: String) = this("127.0.0.1", 5984, database) - - /** Create the database iff it doesn't already exist */ - def createIfNotCreated(http: Http): Unit = - try { - http(info) - () - } catch { - case StatusCode(404, _) => http(create) - } - - /** Attempt to create the database (PUT) */ - def create: Handler[Unit] = this <<< "" >| - - /** Retrieve information about the database (GET) */ - def info: Handler[DatabaseInfo] = { - implicit val f: Formats = DefaultFormats - this ># (extract[DatabaseInfo] _) - } - - /** Destroy the database (DELETE) */ - def delete: Handler[Unit] = DELETE >| - - /** Access all documents in the database with a queryable interface */ - def all: AllDocs = new Request(this / "_all_docs") with AllDocs { } - - /** Access a particular document in the database by ID. */ - def apply(id: String): Document = new Request(this / id) with Document { } - - /** Access a particular document in the database with _id from a given JObject */ - def apply(doc: JObject): Document = this(doc._id.openOrThrowException("legacy code").s) - - /** Access a series of documents by ID. */ - def apply(ids: Seq[String]): AllDocs = all.includeDocs.keys(ids.map(JString): _*) - - /** Access a particular design document in the database by name */ - def design(name: String): Design = new Request(this / "_design" / name) with Design { } - - /** Store a document in the database, generating a new unique ID for it and returning the document with _id and _rev updated */ - def post(doc: JObject): Handler[Box[JObject]] = JSONRequest(this) <<# doc ># handleUpdateResult(doc) _ - - /** Alias for post */ - def <<# (doc: JObject): Handler[Box[JObject]] = post(doc) - - /** Inserts or updates a document in the database, using the standard _id field to do so. Returns the updated document. */ - def store(doc: JObject): Handler[Box[JObject]] = - doc._id match { - case Full(id) => this(id.s) <<<# doc - case _ => this <<# doc - } -} - -/** Case class that holds information about a couch database, as retrieved using GET /database */ -case class DatabaseInfo(db_name: String, doc_count: Int, doc_del_count: Int, update_seq: BigInt, compact_running: Boolean, disk_size: BigInt) - -/** Result of a CouchDB query, possibly containing some summary information (if Couch provided it) such as total rows, and the results themselves */ -case class QueryResults(totalRows: Box[BigInt], offset: Box[BigInt], rows: Seq[QueryRow]) - -/** Single result of a CouchDB query */ -case class QueryRow(id: Box[String], key: JValue, value: Box[JValue], doc: Box[JObject], error: Box[JString]) - -object QueryResult { - /** Read JSON into a QueryResults instance that holds the rows along with metadata about the query */ - def read(json: JValue): Box[QueryResults] = - for { - obj <- Full(json).asA[JObject] ?~ ("query JSON is not a JObject: " + json) - jsonRows <- obj.get[JArray]("rows").map(_.arr) ?~ ("rows not found or wrong type in " + json) - rows <- ((Full(new ArrayBuffer): Box[ArrayBuffer[QueryRow]]) /: jsonRows)((prev, cur) => { - prev flatMap { - buf => readRow(cur).flatMap { res => buf += res; prev } - } - }) - } yield { - QueryResults(obj.get[JInt]("total_rows").map(_.num), obj.get[JInt]("offset").map(_.num), rows) - } - - /** Read JSON into a QueryRow */ - private def readRow(json: JValue): Box[QueryRow] = - for { - obj <- Full(json).asA[JObject] ?~ ("row not a JObject: " + json) - key <- obj.get[JValue]("key") ?~ ("key not found or wrong type in " + json) - } yield QueryRow(obj.get[JString]("id").map(_.s), key, obj.get[JValue]("value"), obj.get[JObject]("doc"), obj.get[JString]("error")) -} - diff --git a/persistence/couchdb/src/main/scala/net/liftweb/couchdb/DispatchJSON.scala b/persistence/couchdb/src/main/scala/net/liftweb/couchdb/DispatchJSON.scala deleted file mode 100644 index 86aaa84477..0000000000 --- a/persistence/couchdb/src/main/scala/net/liftweb/couchdb/DispatchJSON.scala +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2010-2011 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 couchdb - -import dispatch.{Handler, Request} -import net.liftweb.json.JsonAST.{JValue, render} -import net.liftweb.json.{JsonParser, Printer} -import org.apache.http.client.methods.{HttpPost, HttpPut} -import org.apache.http.entity.StringEntity -import org.apache.http.params.HttpProtocolParams - -object DispatchJSON { - /** Implicitly convert a string (representing a URL) to a JSONRequest, which has operators for sending and receiving Lift JSON JValues */ - implicit def stringToJSONRequest(url: String): JSONRequest = JSONRequest(new Request(url)) - - /** Implicitly convert a request to a JSONRequest, which has operators for sending and receiving Lift JSON JValues */ - implicit def requestToJSONRequest(req: Request): JSONRequest = JSONRequest(req) -} - -/** Wrapper for a Dispatch Request that has operators for sending and receiving Lift JSON JValues */ -case class JSONRequest(req: Request) { - /** Handle the response by converting it into a Lift JSON JValue */ - def handleJSON[T](f: JValue => T): Handler[T] = req >- { s => f(JsonParser.parse(s)) } - - /** Alias for handleJSON */ - def ># [T](f: JValue => T): Handler[T] = handleJSON(f) - - /** PUT a JValue rendered as compact JSON to the resource referenced by the request */ - def put(jvalue: JValue): Request = req.next { - val m = new HttpPut - m.setEntity(jvalueToStringEntity(jvalue)) - HttpProtocolParams.setUseExpectContinue(m.getParams, false) - Request.mimic(m) _ - } - - /** Alias for put */ - def <<<# (jvalue: JValue): Request = put(jvalue) - - /** POST a JValue rendered as compact JSON to the resource referenced by the request */ - def post(jvalue: JValue): Request = req.next { - val m = new HttpPost - m.setEntity(jvalueToStringEntity(jvalue)) - HttpProtocolParams.setUseExpectContinue(m.getParams, false) - Request.mimic(m) _ - } - - /** Alias for post */ - def <<# (jvalue: JValue): Request = post(jvalue) - - /** Convert a JValue into a StringEntity with the application/json content type */ - private def jvalueToStringEntity(in: JValue): StringEntity = { - val entity = new StringEntity(Printer.compact(render(in)), Request.factoryCharset) - entity.setContentType("application/json") - entity - } -} - diff --git a/persistence/couchdb/src/main/scala/net/liftweb/couchdb/DocumentHelpers.scala b/persistence/couchdb/src/main/scala/net/liftweb/couchdb/DocumentHelpers.scala deleted file mode 100644 index 872b7d8a34..0000000000 --- a/persistence/couchdb/src/main/scala/net/liftweb/couchdb/DocumentHelpers.scala +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright 2010-2011 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 couchdb - -import scala.reflect.Manifest -import dispatch.{Http, StatusCode} -import net.liftweb.common.{Box, Failure, Full} -import Box.option2Box -import net.liftweb.json.Implicits.string2jvalue -import net.liftweb.json.JsonAST.{JArray, JDouble, JField, JInt, JObject, JString, JValue} -import net.liftweb.util.ControlHelpers.tryo - -object DocumentHelpers { - /** Force a document into the database, clobbering any existing version. Usually good for design documents. */ - def forceStore(http: Http, database: Database, doc: JObject): Box[JObject] = - tryo(http(database(doc) fetch)) match { - case Full(existingDoc) => - tryo{ - http(database.store(updateIdAndRev( - doc, - existingDoc._id.openOrThrowException("We just got this document, so it has an id"), - existingDoc._rev.openOrThrowException("We just got this document, so it has a rev")))) - }.flatMap(b => b) - - - case Failure(_, Full(StatusCode(404, _)), _) => - tryo(http(database store doc)).flatMap(b => b) - - case failure => failure.asA[JObject] - } - - /** Strip _id and _rev from an object */ - def stripIdAndRev(in: JObject): JObject = - JObject(in.obj.filter { case JField("_id"|"_rev", _) => false; case _ => true }) - - /** Update a JObject with new _id and _rev fields */ - def updateIdAndRev(in: JObject, id: String, rev: String): JObject = - JObject(JField("_id", id) :: JField("_rev", rev) :: - in.obj.filter { case JField("_id"|"_rev", _) => false; case _ => true }) - - /** Implicitly extend JObjects */ - implicit def jobjectToJObjectExtension(obj: JObject): JObjectExtension = - new JObjectExtension(obj) - - /** Extension of JObject that has field accessing functions */ - class JObjectExtension(obj: JObject) { - /** Full(_id) from the named field _id if present, Empty or Failure if not present */ - def _id: Box[String] = get[JString]("_id").map(_.s) - - /** Full(_rev) from the named field _rev if present, Empty or Failure if not present */ - def _rev: Box[String] = get[JString]("_rev").map(_.s) - - /** Full(type) from the named field type if present, Empty or Failure if not present */ - def `type`: Box[String] = get[JString]("type").map(_.s) - - /** true iff the type field is present and equal to the given predicate value */ - def isA(s: String) = `type`.map(_ == s) openOr false - - /** Retrieve a named field from a JObject of the given (AST) value type */ - def get[A <: JValue](name: String)(implicit m: Manifest[A]): Box[A] = - for { - field <- obj.obj.find((field: JField) => field match { - case JField(n, _) if n == name => true - case _ => false - }) ?~ ("No such field " + name) - value <- Full(field.value).asA[A] ?~ ("Field " + field + " is not a " + m) - } yield value - - /** Get a field as a JString and project the inner String out of it */ - def getString(name: String): Box[String] = get[JString](name).map(_.s) - - /** Get a field as a JObject and project the inner list of JFields out of it */ - def getObject(name: String): Box[List[JField]] = get[JObject](name).map(_.obj) - - /** Get a field as a JInt and project the inner BigInt out of it */ - def getInt(name: String): Box[BigInt] = get[JInt](name).map(_.num) - - /** Get a field as a JDouble and project the inner String out of it */ - def getDouble(name: String): Box[Double] = get[JDouble](name).map(_.num) - - /** Get a field as a JArray and project the inner list of JValues out of it */ - def getArray(name: String): Box[List[JValue]] = get[JArray](name).map(_.arr) - - /** Construct a version of the input JObject with the given field removed (if present) */ - def remove(field: String): JObject = - JObject(obj.obj.filter { case JField(key, _) if key != field => true; case _ => false }) - - /** Construct a version of the input JObject with all the given fields removed (if present) */ - def remove(fields: String*): JObject = - JObject(obj.obj.filter { case JField(key, _) if !(fields contains key) => true; case _ => false }) - - /** Clean out the usual couch fields of "_id", "_rev", and "type", if present. */ - def clean: JObject = remove("_id", "_rev", "type") - } -} - diff --git a/persistence/couchdb/src/main/scala/net/liftweb/couchdb/JSONRecord.scala b/persistence/couchdb/src/main/scala/net/liftweb/couchdb/JSONRecord.scala deleted file mode 100644 index be4cc96f92..0000000000 --- a/persistence/couchdb/src/main/scala/net/liftweb/couchdb/JSONRecord.scala +++ /dev/null @@ -1,281 +0,0 @@ -/* - * Copyright 2010-2011 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 couchdb - -import java.math.MathContext -import java.util.Calendar -import scala.collection.immutable.TreeSet -import scala.reflect.Manifest -import scala.xml.NodeSeq -import net.liftweb.common.{Box, Empty, Failure, Full} -import Box.{box2Iterable, option2Box} -import net.liftweb.http.js.{JsExp, JsObj} -import net.liftweb.json.JsonParser -import net.liftweb.json.JsonAST._ -import net.liftweb.record.{Field, MandatoryTypedField, MetaRecord, Record} -import net.liftweb.record.RecordHelpers.jvalueToJsExp -import net.liftweb.record.FieldHelpers.expectedA -import net.liftweb.record.field._ -import net.liftweb.util.ThreadGlobal -import net.liftweb.util.Helpers._ -import java.util.prefs.BackingStoreException - -private[couchdb] object JSONRecordHelpers { - - /** Remove duplicate fields, preferring the first field seen with a given name */ - def dedupe(fields: List[JField]): List[JField] = { - var seen = TreeSet.empty[String] - fields.filter { - case JField(name, _) if seen contains name => false - case JField(name, _) => { seen = seen + name; true } - } - } -} - -import JSONRecordHelpers._ - -/** Specialized Record that can be encoded and decoded from JSON */ -trait JSONRecord[MyType <: JSONRecord[MyType]] extends Record[MyType] { - self: MyType => - - private var _additionalJFields: List[JField] = Nil - - /** Refines meta to require a JSONMetaRecord */ - def meta: JSONMetaRecord[MyType] - - /** Extra fields to add to the encoded object, such as type. Default is none (Nil) */ - def fixedAdditionalJFields: List[JField] = Nil - - /** - * Additional fields that are not represented by Record fields, nor are fixed additional fields. - * Default implementation is for preserving unknown fields across read/write - */ - def additionalJFields: List[JField] = _additionalJFields - - /** - * Handle any additional fields that are not represented by Record fields when decoding from a JObject. - * Default implementation preserves the fields intact and returns them via additionalJFields - */ - def additionalJFields_= (fields: List[JField]): Unit = _additionalJFields = fields - - - - /** - * Save the instance and return the instance - */ - override def saveTheRecord(): Box[MyType] = throw new BackingStoreException("JSON Records don't save themselves") -} - -object JSONMetaRecord { - /** Local override to record parsing that can cause extra fields to be ignored, even if they otherwise would cause a failure */ - object overrideIgnoreExtraJSONFields extends ThreadGlobal[Boolean] - - /** Local override to record parsing that can cause missing fields to be ignored, even if they otherwise would cause a failure */ - object overrideNeedAllJSONFields extends ThreadGlobal[Boolean] -} - -/** Specialized MetaRecord that deals with JSONRecords */ -trait JSONMetaRecord[BaseRecord <: JSONRecord[BaseRecord]] extends MetaRecord[BaseRecord] { - self: BaseRecord => - - /** - * Return the name of the field in the encoded JSON object. If the field implements JSONField and has overridden jsonName then - * that will be used, otherwise the record field name - */ - def jsonName(field: Field[_, BaseRecord]): String = field match { - case (jsonField: JSONField) => jsonField.jsonName openOr field.name - case _ => field.name - } - - /** Whether or not extra fields in a JObject to decode is an error (false) or not (true). The default is true */ - def ignoreExtraJSONFields: Boolean = true - - /** Whether or not missing fields in a JObject to decode is an error (false) or not (true). The default is true */ - def needAllJSONFields: Boolean = true - - override def asJSON(inst: BaseRecord): JsObj = jvalueToJsExp(asJValue).asInstanceOf[JsObj] - - override def setFieldsFromJSON(inst: BaseRecord, json: String): Box[Unit] = - setFieldsFromJValue(inst, JsonParser.parse(json)) - - /** Encode a record instance into a JValue */ - override def asJValue(rec: BaseRecord): JObject = { - val recordJFields = fields(rec).map(f => JField(jsonName(f), f.asJValue)) - JObject(dedupe(recordJFields ++ rec.fixedAdditionalJFields ++ rec.additionalJFields).sortWith(_.name < _.name)) - } - - /** Attempt to decode a JValue, which must be a JObject, into a record instance */ - override def setFieldsFromJValue(rec: BaseRecord, jvalue: JValue): Box[Unit] = { - def fromJFields(jfields: List[JField]): Box[Unit] = { - import JSONMetaRecord._ - - val flds = fields(rec) - lazy val recordFieldNames = TreeSet(flds.map(jsonName): _*) - lazy val jsonFieldNames = TreeSet(jfields.map(_.name): _*) - lazy val optionalFieldNames = TreeSet(flds.filter(_.optional_?).map(jsonName): _*) - lazy val recordFieldsNotInJson = recordFieldNames -- jsonFieldNames -- optionalFieldNames - lazy val jsonFieldsNotInRecord = jsonFieldNames -- recordFieldNames - - // If this record type has been configured to be stricter about fields, check those first - if ((overrideNeedAllJSONFields.box openOr needAllJSONFields) && !recordFieldsNotInJson.isEmpty) { - Failure("The " + recordFieldsNotInJson.mkString(", ") + " field(s) were not found, but are required.") - } else if (!(overrideIgnoreExtraJSONFields.box openOr ignoreExtraJSONFields) && !jsonFieldsNotInRecord.isEmpty) { - Failure("Field(s) " + jsonFieldsNotInRecord.mkString(", ") + " are not recognized.") - } else { - for { - jfield <- jfields - field <- flds if jsonName(field) == jfield.name - } field.setFromJValue(jfield.value) - - rec.additionalJFields = jsonFieldsNotInRecord.toList map { - name => jfields.find(_.name == name).get - } - - Full(()) - } - } - - jvalue match { - case JObject(jfields) => fromJFields(jfields) - case other => expectedA("JObject", other) - } - } -} - -/** Trait for fields with JSON-specific behavior */ -trait JSONField { - /** Return Full(name) to use that name in the encoded JSON object, or Empty to use the same name as in Scala. Defaults to Empty */ - def jsonName: Box[String] = Empty -} - - -/* ****************************************************************************************************************************************** */ - - -/** Field that contains an entire record represented as an inline object value in the final JSON */ -class JSONSubRecordField[OwnerType <: JSONRecord[OwnerType], SubRecordType <: JSONRecord[SubRecordType]] - (rec: OwnerType, valueMeta: JSONMetaRecord[SubRecordType])(implicit subRecordType: Manifest[SubRecordType]) - extends Field[SubRecordType, OwnerType] with MandatoryTypedField[SubRecordType] -{ - def this(rec: OwnerType, value: SubRecordType)(implicit subRecordType: Manifest[SubRecordType]) = { - this(rec, value.meta) - set(value) - } - - def this(rec: OwnerType, valueMeta: JSONMetaRecord[SubRecordType], value: Box[SubRecordType]) - (implicit subRecordType: Manifest[SubRecordType]) = { - this(rec, valueMeta) - setBox(value) - } - - def owner = rec - def asJs = asJValue - def toForm: Box[NodeSeq] = Empty // FIXME - def defaultValue = valueMeta.createRecord - - def setFromString(s: String): Box[SubRecordType] = valueMeta.fromJSON(s) - - def setFromAny(in: Any): Box[SubRecordType] = genericSetFromAny(in) - - def asJValue: JValue = valueBox.map(_.asJValue) openOr (JNothing: JValue) - def setFromJValue(jvalue: JValue): Box[SubRecordType] = jvalue match { - case JNothing|JNull if optional_? => setBox(Empty) - case _ => setBox(valueMeta.fromJValue(jvalue)) - } -} - -/** Field that contains an array of some basic JSON type */ -class JSONBasicArrayField[OwnerType <: JSONRecord[OwnerType], ElemType <: JValue](rec: OwnerType)(implicit elemType: Manifest[ElemType]) - extends Field[List[ElemType], OwnerType] with MandatoryTypedField[List[ElemType]] -{ - def this(rec: OwnerType, value: List[ElemType])(implicit elemType: Manifest[ElemType]) = { this(rec); set(value) } - def this(rec: OwnerType, value: Box[List[ElemType]])(implicit elemType: Manifest[ElemType]) = { this(rec); setBox(value) } - - def owner = rec - def asJs = asJValue - def toForm: Box[NodeSeq] = Empty // FIXME - def defaultValue = Nil - - def setFromString(s: String): Box[List[ElemType]] = - setBox(tryo(JsonParser.parse(s)) flatMap { - case JArray(values) => checkValueTypes(values) - case other => expectedA("JSON string with an array of " + elemType, other) - }) - - def setFromAny(in: Any): Box[List[ElemType]] = genericSetFromAny(in) - - def checkValueTypes(in: List[JValue]): Box[List[ElemType]] = - in.find(!_.isInstanceOf[ElemType]) match { - case Some(erroneousValue) if erroneousValue != null => - Failure("Value in input array is a " + value.getClass.getName + ", should be a " + elemType.toString) - - case Some(erroneousValue) => Failure("Value in input array is null, should be a " + elemType.toString) - case None => Full(in.map(_.asInstanceOf[ElemType])) - } - - def asJValue: JValue = valueBox.map(JArray) openOr (JNothing: JValue) - def setFromJValue(jvalue: JValue): Box[List[ElemType]] = jvalue match { - case JNothing|JNull if optional_? => setBox(Empty) - case JArray(values) => setBox(checkValueTypes(values)) - case other => setBox(expectedA("JArray containing " + elemType.toString, other)) - } -} - -/** Field that contains a homogeneous array of subrecords */ -class JSONSubRecordArrayField[OwnerType <: JSONRecord[OwnerType], SubRecordType <: JSONRecord[SubRecordType]] - (rec: OwnerType, valueMeta: JSONMetaRecord[SubRecordType])(implicit valueType: Manifest[SubRecordType]) - extends Field[List[SubRecordType], OwnerType] with MandatoryTypedField[List[SubRecordType]] -{ - def this(rec: OwnerType, value: List[SubRecordType])(implicit subRecordType: Manifest[SubRecordType]) = { - this(rec, value.head.meta) - set(value) - } - - def this(rec: OwnerType, valueMeta: JSONMetaRecord[SubRecordType], value: Box[List[SubRecordType]]) - (implicit subRecordType: Manifest[SubRecordType]) = { - this(rec, valueMeta) - setBox(value) - } - - def owner = rec - def asJs = asJValue - def toForm: Box[NodeSeq] = Empty // FIXME - def defaultValue = Nil - - def setFromString(s: String): Box[List[SubRecordType]] = - setBox(tryo(JsonParser.parse(s)) flatMap { - case JArray(values) => fromJValues(values) - case other => expectedA("JSON string containing an array of " + valueMeta.getClass.getSuperclass.getName, other) - }) - - def setFromAny(in: Any): Box[List[SubRecordType]] = genericSetFromAny(in) - - private def fromJValues(jvalues: List[JValue]): Box[List[SubRecordType]] = - jvalues - .foldLeft[Box[List[SubRecordType]]](Full(Nil)) { - (prev, cur) => prev.flatMap { - rest => valueMeta.fromJValue(cur).map(_::rest) - } - } - .map(_.reverse) - - def asJValue: JArray = JArray(value.map(_.asJValue)) - def setFromJValue(jvalue: JValue): Box[List[SubRecordType]] = jvalue match { - case JNothing|JNull if optional_? => setBox(Empty) - case JArray(jvalues) => setBox(fromJValues(jvalues)) - case other => setBox(expectedA("JArray containing " + valueMeta.getClass.getSuperclass.getName, other)) - } -} - diff --git a/persistence/couchdb/src/test/scala/net/liftweb/couchdb/CouchDatabaseSpec.scala b/persistence/couchdb/src/test/scala/net/liftweb/couchdb/CouchDatabaseSpec.scala deleted file mode 100644 index 01bddbeecb..0000000000 --- a/persistence/couchdb/src/test/scala/net/liftweb/couchdb/CouchDatabaseSpec.scala +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2010-2011 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 couchdb - -import java.net.ConnectException - -import dispatch.{Http, StatusCode} - -import org.specs2.mutable.Specification - - -/** - * Systems under specification for CouchDatabase. - */ -object CouchDatabaseSpec extends Specification { - "CouchDatabase Specification".title - sequential - - def setup = { - val http = new Http - val database = new Database("test") - (try { http(database delete) } catch { case StatusCode(_, _) => () }) must not(throwA[ConnectException]).orSkip - - (http, database) - } - - def hasCode(i: Int): PartialFunction[Throwable, org.specs2.matcher.MatchResult[Any]] = - { case StatusCode(c, _) => c must_== i } - - "A database" should { - "give 404 when info called and nonexistant" in { - setup - val (http, database) = setup - - http(database info) must throwA[StatusCode].like(hasCode(404)) - } - - "give 404 when deleted but nonexistant" in { - val (http, database) = setup - - http(database delete) must throwA[StatusCode].like(hasCode(404)) - } - - "succeed being created" in { - val (http, database) = setup - - http(database create) must_== () - } - - "give 412 instead of allowing creation when already existant" in { - val (http, database) = setup - - http(database create) must_== () - http(database create) must throwA[StatusCode].like(hasCode(412)) - } - - "have info when created" in { - val (http, database) = setup - - http(database create) must_== () - http(database info).db_name must_== ("test") - } - - "succeed in being deleted" in { - val (http, database) = setup - - http(database create) must_== () - http(database delete) must_== () - } - - "succeed being recreated" in { - val (http, database) = setup - - http(database create) must_== () - http(database delete) must_== () - http(database create) must_== () - } - } -} - diff --git a/persistence/couchdb/src/test/scala/net/liftweb/couchdb/CouchDocumentSpec.scala b/persistence/couchdb/src/test/scala/net/liftweb/couchdb/CouchDocumentSpec.scala deleted file mode 100644 index 6c4b210cb7..0000000000 --- a/persistence/couchdb/src/test/scala/net/liftweb/couchdb/CouchDocumentSpec.scala +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2010-2011 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 couchdb - -import java.net.ConnectException - -import dispatch.{Http, StatusCode} - -import org.specs2.mutable.Specification - -import common._ -import json._ -import JsonDSL._ -import DocumentHelpers._ - - -/** - * Systems under specification for CouchDocument. - */ -object CouchDocumentSpec extends Specification { - "CouchDocument Specification".title - sequential - - def hasCode(i: Int): PartialFunction[Throwable, org.specs2.matcher.MatchResult[Any]] = - { case StatusCode(c, _) => c must_== i } - - def setup = { - val http = new Http - val database = new Database("test") - (try { http(database delete) } catch { case StatusCode(_, _) => () }) must not(throwA[ConnectException]).orSkip - http(database create) - - (http, database) - } - - private final def verifyAndOpen[A](b: Box[A]): A = { - b.isDefined must_== true - b.openOrThrowException("This is a test") - } - - "A document" should { - val testDoc1: JObject = ("name" -> "Alice") ~ ("age" -> 25) - val testDoc2: JObject = ("name" -> "Bob") ~ ("age" -> 30) - - "give 404 on get when nonexistant" in { - val (http, database) = setup - - http(database("testdoc") fetch) must throwA[StatusCode].like(hasCode(404)) - } - - "be insertable" in { - val (http, database) = setup - - val newDoc = verifyAndOpen(http(database post testDoc1)) - val Full(id) = newDoc._id - val Full(rev) = newDoc._rev - compact(render(stripIdAndRev(newDoc))) must_== compact(render(testDoc1)) - val dbDoc = http(database(newDoc) fetch) - compact(render(dbDoc)) must_== compact(render(newDoc)) - } - - "have history" in { - val (http, database) = setup - - val firstDocBox = http(database post testDoc1) - firstDocBox.isDefined must_== true - val Full(firstDoc) = firstDocBox - val Full(id) = firstDoc._id - val Full(rev) = firstDoc._rev - val secondDoc = verifyAndOpen(http(database store updateIdAndRev(testDoc2, id, rev))) - val dbFirstDoc = http(database(id) @@ firstDoc fetch) - val dbSecondDoc = http(database(id) @@ secondDoc fetch) - val dbCurrentDoc = http(database(id) fetch) - - compact(render(dbFirstDoc)) must_== compact(render(firstDoc)) - compact(render(dbSecondDoc)) must_== compact(render(secondDoc)) - compact(render(dbCurrentDoc)) must_== compact(render(secondDoc)) - } - - "be deletable" in { - val (http, database) = setup - - val newDoc = verifyAndOpen(http(database store testDoc1)) - http(database(newDoc) @@ newDoc delete) must_== () - http(database(newDoc) fetch) must throwA[StatusCode].like(hasCode(404)) - } - - "give 404 on delete when nonexistant" in { - val (http, database) = setup - - val newDoc = verifyAndOpen(http(database store testDoc1)) - http(database(newDoc) @@ newDoc delete) must_== () - http(database(newDoc) @@ newDoc delete) must throwA[StatusCode].like(hasCode(404)) - } - - "be force storable" in { - val (http, database) = setup - - val doc = ("_id" -> "test") ~ testDoc1 - stripIdAndRev(verifyAndOpen(forceStore(http, database, doc))) must_== testDoc1 - stripIdAndRev(verifyAndOpen(forceStore(http, database, doc))) must_== testDoc1 - } - } -} - diff --git a/persistence/couchdb/src/test/scala/net/liftweb/couchdb/CouchQuerySpec.scala b/persistence/couchdb/src/test/scala/net/liftweb/couchdb/CouchQuerySpec.scala deleted file mode 100644 index ae09660853..0000000000 --- a/persistence/couchdb/src/test/scala/net/liftweb/couchdb/CouchQuerySpec.scala +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Copyright 2010-2011 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 couchdb - -import java.net.ConnectException - -import dispatch.{Http, StatusCode} - -import common._ -import json._ -import JsonDSL._ -import org.specs2.mutable.Specification -import DocumentHelpers.jobjectToJObjectExtension - - -/** - * Systems under specification for CouchQuery. - */ -object CouchQuerySpec extends Specification { - "CouchQuery Specification".title - sequential - - def setup = { - val http = new Http - val database = new Database("test") - (try { http(database delete) } catch { case StatusCode(_, _) => () }) must not(throwA[ConnectException]).orSkip - http(database create) - - (http, database) - } - - private def verifyAndOpen[A](b: Box[A]): A = { - b.isDefined must_== true - b.openOrThrowException("This is a test") - } - - "Queries" should { - val design: JObject = - ("language" -> "javascript") ~ - ("views" -> (("all_students" -> ("map" -> "function(doc) { if (doc.type == 'student') { emit(null, doc); } }")) ~ - ("students_by_age" -> ("map" -> "function(doc) { if (doc.type == 'student') { emit(doc.age, doc); } }")) ~ - ("students_by_age_and_name" -> ("map" -> "function(doc) { if (doc.type == 'student') { emit([doc.age, doc.name], doc); } }")))) - - val docs: List[JObject] = - (("type" -> "student") ~ ("name" -> "Alice") ~ ("age" -> 10)) :: - (("type" -> "student") ~ ("name" -> "Bob") ~ ("age" -> 11)) :: - (("type" -> "student") ~ ("name" -> "Charlie") ~ ("age" -> 11)) :: - (("type" -> "student") ~ ("name" -> "Donna") ~ ("age" -> 12)) :: - (("type" -> "student") ~ ("name" -> "Eric") ~ ("age" -> 12)) :: - (("type" -> "student") ~ ("name" -> "Fran") ~ ("age" -> 13)) :: - (("type" -> "class") ~ ("name" -> "Astronomy")) :: - (("type" -> "class") ~ ("name" -> "Baking")) :: - (("type" -> "class") ~ ("name" -> "Chemistry")) :: - Nil - - def findStudents(docs: List[JObject]): List[JObject] = docs.filter(_.isA("student")) - - def compareName(a: JObject, b: JObject): Boolean = - (a.getString("name") openOr "design") < (b.getString("name") openOr "design") - - def prep(http: Http, database: Database): (JObject, List[JObject]) = { - val storedDesign = verifyAndOpen(http(database.design("test") put design)) - val storedDocs = docs map { doc => verifyAndOpen(http(database store doc)) } - - (storedDesign, storedDocs) - } - - def sortedAndPrintedRows(docs: Seq[QueryRow]): String = sortedAndPrintedValues(docs.flatMap(_.value.asA[JObject]).toList) - def sortedAndPrintedValues(docs: List[JObject]): String = compact(render(docs.sortWith(compareName))) - - "work with all documents" in { - val (http, database) = setup - val (design, docs) = prep(http, database) - val students = findStudents(docs) - - verifyAndOpen(http(database.all.includeDocs query)) must beLike { - case QueryResults(Full(count), Full(offset), rows) => - count must_== docs.length + 1 // +1 for the design doc - offset must_== 0 - sortedAndPrintedValues(rows.flatMap(_.doc).toList) must_== sortedAndPrintedValues(design::docs) - } - } - - "support multi-document fetch" in { - val (http, database) = setup - val (design, docs) = prep(http, database) - val students = findStudents(docs) - - verifyAndOpen(http(database(List( - students(0)._id.openOrThrowException("This is a test"), - students(3)._id.openOrThrowException("This is a test"), - students(5)._id.openOrThrowException("This is a test")) - ) query)) must beLike { - case QueryResults(Full(count), Full(offset), rows) => - sortedAndPrintedValues(rows.flatMap(_.doc).toList) must_== sortedAndPrintedValues(students(0)::students(3)::students(5)::Nil) - } - } - - "work with views" in { - val (http, database) = setup - val (design, docs) = prep(http, database) - val students = findStudents(docs) - - verifyAndOpen(http(database.design("test").view("all_students") query)) must beLike { - case QueryResults(Full(count), Full(offset), rows) => - count must_== students.length - offset must_== 0 - sortedAndPrintedRows(rows) must_== sortedAndPrintedValues(students) - } - } - - "support minimum key bounds" in { - val (http, database) = setup - val (design, docs) = prep(http, database) - val students = findStudents(docs) - - verifyAndOpen(http(database.design("test").view("students_by_age").from(11) query)) must beLike { - case QueryResults(_, _, rows) => - (rows.flatMap(_.value.asA[JObject]).toList.sortWith(compareName) must_== - students.filter(_.getInt("age").map(_ >= 11).openOrThrowException("This is a test")).sortWith(compareName)) - } - } - - "support maximum key bounds" in { - val (http, database) = setup - val (design, docs) = prep(http, database) - val students = findStudents(docs) - - verifyAndOpen(http(database.design("test").view("students_by_age").to(12) query)) must beLike { - case QueryResults(_, _, rows) => - (rows.flatMap(_.value.asA[JObject]).toList.sortWith(compareName) must_== - students.filter(_.getInt("age").map(_ <= 12).openOrThrowException("This is a test")).sortWith(compareName)) - } - } - - "support key lookup" in { - val (http, database) = setup - val (design, docs) = prep(http, database) - val students = findStudents(docs) - - verifyAndOpen(http(database.design("test").view("students_by_age").key(11) query)) must beLike { - case QueryResults(_, _, rows) => - rows.length must_== 2 - (rows.flatMap(_.value.asA[JObject]).toList.sortWith(compareName) must_== - students.filter(_.getInt("age").map(_ == 11).openOrThrowException("This is a test"))) - } - } - - "support limiting the number of results" in { - val (http, database) = setup - prep(http, database) - - verifyAndOpen(http(database.design("test").view("students_by_age").from(12).limit(2) query)) must beLike { - case QueryResults(_, _, rows) => rows.length must_== 2 - } - } - - "support descending sort" in { - val (http, database) = setup - val (design, docs) = prep(http, database) - val students = findStudents(docs) - - verifyAndOpen(http(database.design("test").view("students_by_age").descending.from(11) query)) must beLike { - case QueryResults(_, _, rows) => - rows.length must_== 3 - (rows.flatMap(_.value.asA[JObject]).toList.sortWith(compareName) must_== - students.filter(_.getInt("age").map(_ <= 11).openOrThrowException("This is a test"))) - } - } - - "preserve query ordering" in { - val (http, database) = setup - val (design, docs) = prep(http, database) - val students = findStudents(docs) - - verifyAndOpen(http(database.design("test").view("students_by_age_and_name").from(JArray(11::Nil)).to(JArray(12::JObject(Nil)::Nil)) query)) must beLike { - case QueryResults(_, _, rows) => - rows.length must_== 4 - (rows.flatMap(_.value.asA[JObject]).toList must_== - students.filter(_.getInt("age").map(age => age >= 11 && age <= 12).openOrThrowException("This is a test"))) - } - } - } -} - diff --git a/persistence/couchdb/src/test/scala/net/liftweb/couchdb/CouchRecordSpec.scala b/persistence/couchdb/src/test/scala/net/liftweb/couchdb/CouchRecordSpec.scala deleted file mode 100644 index b95c8e2c2e..0000000000 --- a/persistence/couchdb/src/test/scala/net/liftweb/couchdb/CouchRecordSpec.scala +++ /dev/null @@ -1,330 +0,0 @@ -/* - * Copyright 2010-2011 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 couchdb - -import java.net.ConnectException - -import dispatch.{Http, StatusCode} - -import org.specs2.mutable.Specification - -import common._ -import json._ -import JsonDSL._ -import record.field.{IntField, StringField} -import DocumentHelpers.stripIdAndRev - - -package couchtestrecords { - class Person private () extends CouchRecord[Person] { - def meta = Person - - object name extends StringField(this, 200) - object age extends IntField(this) - } - - object Person extends Person with CouchMetaRecord[Person] - - class Company private () extends CouchRecord[Company] { - def meta = Company - - object name extends StringField(this, 200) - } - - object Company extends Company with CouchMetaRecord[Company] -} - -/** - * Systems under specification for CouchRecord. - */ -object CouchRecordSpec extends Specification { - "CouchRecord Specification".title - sequential - - import CouchDB.defaultDatabase - import couchtestrecords._ - - val design: JObject = - ("language" -> "javascript") ~ - ("views" -> (("people_by_age" -> ("map" -> "function(doc) { if (doc.type == 'Person') { emit(doc.age, doc); } }")) ~ - ("oldest" -> (("map" -> "function(doc) { if (doc.type == 'Person') { emit(doc.name, doc.age); } }") ~ - ("reduce" -> "function(keys, values) { return Math.max.apply(null, values); }"))))) - - def setup = { - val database = new Database("test") - (try { Http(database delete) } catch { case StatusCode(_, _) => () }) must not(throwA[ConnectException]).orSkip - Http(database create) - Http(database.design("test") put design) - defaultDatabase = database - } - - def assertEqualPerson(a: Person, b: Person) = { - a.name.value must_== b.name.value - a.age.value must_== b.age.value - } - - def assertEqualRows(foundRows: Seq[Person], expectedRows: Seq[Person]) = { - foundRows.length must_== expectedRows.length - for ((found, expected) <- foundRows.toList zip expectedRows.toList) { - found.id.valueBox must_== expected.id.valueBox - found.rev.valueBox must_== expected.rev.valueBox - assertEqualPerson(found, expected) - } - } - - "A couch record" should { - def testRec1: Person = Person.createRecord.name("Alice").age(25) - val testDoc1: JObject = ("age" -> 25) ~ ("name" -> "Alice") ~ ("type" -> "Person") - def testRec2: Person = Person.createRecord.name("Bob").age(30) - val testDoc2: JObject = ("age" -> 30) ~ ("extra1" -> "value1") ~ ("extra2" -> "value2") ~ ("name" -> "Bob") ~ ("type" -> "Person") - def testRec3: Company = Company.createRecord.name("Acme") - - "give emtpy box on get when nonexistant" in { - setup must_== () - - Person.fetch("testdoc").isDefined must_== false - } - - "be insertable" in { - setup - - val newRec = testRec1 - newRec save - - assertEqualPerson(newRec, testRec1) - newRec.saved_? must_== true - newRec.id.valueBox.isDefined must_== true - newRec.rev.valueBox.isDefined must_== true - - val Full(foundRec) = Person.fetch(newRec.id.valueBox.openOrThrowException("This is a test")) - assertEqualPerson(foundRec, testRec1) - foundRec.id.valueBox must_== newRec.id.valueBox - foundRec.rev.valueBox must_== newRec.rev.valueBox - } - - "generate the right JSON" in { - setup - val newRec = testRec1 - newRec save - - val foundDoc = Http(defaultDatabase(newRec.id.valueBox.openOrThrowException("This is a test")) fetch) - compact(render(stripIdAndRev(foundDoc))) must_== compact(render(testDoc1)) - } - - "be deletable" in { - setup - val newRec = testRec1 - newRec.save - - newRec.id.valueBox.isDefined must_== true - val id = newRec.id.valueBox.openOrThrowException("This is a test") - - Person.fetch(id).isDefined must_== true - newRec.delete_!.isDefined must_== true - Person.fetch(id).isDefined must_== false - newRec.delete_!.isDefined must_== false - - newRec.save - Http(defaultDatabase(newRec.id.valueBox.openOrThrowException("This is a test")) @@ newRec.rev.valueBox.openOrThrowException("This is a test") delete) - newRec.delete_!.isDefined must_== false - } - - "be fetchable in bulk" in { - setup - val newRec1, newRec2, newRec3 = testRec1 - - newRec1.save - newRec2.save - newRec3.save - - newRec1.saved_? must_== true - newRec2.saved_? must_== true - newRec3.saved_? must_== true - - val expectedRows = newRec1::newRec3::Nil - - Person.fetchMany(newRec1.id.valueBox.openOrThrowException("This is a test"), - newRec3.id.valueBox.openOrThrowException("This is a test")).map(_.toList) must beLike { - case Full(foundRows) => assertEqualRows(foundRows, expectedRows); 1 must_== 1 - } - } - - "support queries" in { - setup - - val newRec1, newRec3 = testRec1 - val newRec2 = testRec2 - newRec1.save - newRec2.save - newRec3.save - - newRec1.saved_? must_== true - newRec2.saved_? must_== true - newRec3.saved_? must_== true - - val expectedRows = newRec2::Nil - - Person.queryView("test", "people_by_age", _.key(JInt(30))) must beLike { - case Full(foundRows) => assertEqualRows(foundRows, expectedRows); 1 must_== 1 - } - } - - "support queries returning documents" in { - setup - - val newRec1 = testRec1 - val newRec2 = testRec2 - newRec1.save - newRec2.save - - newRec1.saved_? must_== true - newRec2.saved_? must_== true - - val expectedRows = newRec1::newRec2::Nil - - Person.queryViewDocs("test", "oldest", _.dontReduce) must beLike { - case Full(foundRows) => assertEqualRows(foundRows, expectedRows); 1 must_== 1 - } - } - - "support queries returning documents for a non-reducing view" in { - setup - - val newRec1 = testRec1 - val newRec2 = testRec2 - newRec1.save - newRec2.save - - newRec1.saved_? must_== true - newRec2.saved_? must_== true - - val expectedRows = newRec1::newRec2::Nil - - Person.queryViewDocs("test", "people_by_age", identity) must beLike { - case Full(foundRows) => assertEqualRows(foundRows, expectedRows); 1 must_== 1 - } - } - - "support multiple databases for fetching" in { - setup - val database2 = new Database("test2") - (try { Http(database2 delete) } catch { case StatusCode(_, _) => () }) must not(throwA[ConnectException]).orSkip - Http(database2 create) - - val newRec = testRec1 - newRec.database = database2 - newRec.save - - newRec.saved_? must_== true - - val foundRecBox: Box[Person] = newRec.id.valueBox.flatMap{ id => Person.fetchFrom(database2, id)} - foundRecBox.isDefined must_== true - val Full(foundRec) = foundRecBox - assertEqualPerson(foundRec, testRec1) - foundRec.id.valueBox must_== newRec.id.valueBox - foundRec.rev.valueBox must_== newRec.rev.valueBox - - newRec.id.valueBox.flatMap(id => Person.fetch(id)).isDefined must_== false - } - - "support multiple databases for fetching in bulk" in { - setup - val database2 = new Database("test2") - (try { Http(database2 delete) } catch { case StatusCode(_, _) => () }) must not(throwA[ConnectException]).orSkip - Http(database2 create) - - val newRec1, newRec2, newRec3 = testRec1 - newRec1.database = database2 - newRec2.database = database2 - newRec3.database = database2 - newRec1.save - newRec2.save - newRec3.save - - newRec1.saved_? must_== true - newRec2.saved_? must_== true - newRec3.saved_? must_== true - - val expectedRows = newRec1::newRec3::Nil - - Person.fetchManyFrom(database2, - newRec1.id.valueBox.openOrThrowException("This is a test"), - newRec3.id.valueBox.openOrThrowException("This is a test") - ).map(_.toList) must beLike { - case Full(foundRows) => assertEqualRows(foundRows, expectedRows); 1 must_== 1 - } - - Person.fetchMany(newRec1.id.valueBox.openOrThrowException("This is a test"), - newRec3.id.valueBox.openOrThrowException("This is a test")) must beLike { - case Full(seq) => seq.isEmpty must_== true - } - } - - "support multiple databases for queries" in { - setup - val database2 = new Database("test2") - (try { Http(database2 delete) } catch { case StatusCode(_, _) => () }) must not(throwA[ConnectException]).orSkip - Http(database2 create) - Http(database2.design("test") put design) - - val newRec1, newRec3 = testRec1 - val newRec2 = testRec2 - newRec1.database = database2 - newRec2.database = database2 - newRec3.database = database2 - newRec1.save - newRec2.save - newRec3.save - - newRec1.saved_? must_== true - newRec2.saved_? must_== true - newRec3.saved_? must_== true - - val expectedRows = newRec2::Nil - - Person.queryViewFrom(database2, "test", "people_by_age", _.key(JInt(30))) must beLike { - case Full(foundRows) => assertEqualRows(foundRows, expectedRows); 1 must_== 1 - } - - Person.queryView("test", "people_by_age", _.key(JInt(30))) must beLike { case Full(seq) => seq.isEmpty must_== true } - } - - "support multiple databases for queries returning documents" in { - setup - val database2 = new Database("test2") - (try { Http(database2 delete) } catch { case StatusCode(_, _) => () }) must not(throwA[ConnectException]).orSkip - Http(database2 create) - Http(database2.design("test") put design) - - val newRec1 = testRec1 - val newRec2 = testRec2 - newRec1.database = database2 - newRec2.database = database2 - newRec1.save - newRec2.save - - newRec1.saved_? must_== true - newRec2.saved_? must_== true - - val expectedRows = newRec1::newRec2::Nil - - Person.queryViewDocsFrom(database2, "test", "oldest", _.dontReduce) must beLike { - case Full(foundRows) => assertEqualRows(foundRows, expectedRows); 1 must_== 1 - } - - Person.queryViewDocs("test", "oldest", _.dontReduce) must beLike { case Full(seq) => seq.isEmpty must_== true } - } - } -} diff --git a/persistence/couchdb/src/test/scala/net/liftweb/couchdb/JsonRecordSpec.scala b/persistence/couchdb/src/test/scala/net/liftweb/couchdb/JsonRecordSpec.scala deleted file mode 100644 index 756b02b9e6..0000000000 --- a/persistence/couchdb/src/test/scala/net/liftweb/couchdb/JsonRecordSpec.scala +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright 2010-2011 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 couchdb - -import org.specs2.mutable.Specification -import org.specs2.execute.Result - -import common._ -import json._ -import JsonDSL._ -import record.field._ -import DocumentHelpers.jobjectToJObjectExtension - - -package jsontestrecords { - class Person private () extends JSONRecord[Person] { - def meta = Person - - object name extends StringField(this, 200) - object age extends IntField(this) { - override def defaultValue = 0 - } - object favoriteColor extends OptionalStringField(this, 200) - object address extends JSONSubRecordField(this, Address, Empty) { - override def optional_? = true - } - } - - object Person extends Person with JSONMetaRecord[Person] - - class Address extends JSONRecord[Address] { - def meta = Address - - // object country extends CountryField(this) - // object postalCode extends PostalCodeField(this, country) - object country extends StringField(this, 60) - object postalCode extends StringField(this, 10) - object city extends StringField(this, 60) - object street extends StringField(this, 200) - } - - object Address extends Address with JSONMetaRecord[Address] -} - - -/** - * Systems under specification for JsonRecord. - */ -object JsonRecordSpec extends Specification { - "JsonRecord Specification".title - sequential - - import jsontestrecords._ - - def assertEqualPerson(a: Person, b: Person): Result = { - a.name.valueBox must_== b.name.valueBox - a.age.valueBox must_== b.age.valueBox - a.favoriteColor.valueBox must_== b.favoriteColor.valueBox - a.address.valueBox.isEmpty must_== b.address.valueBox.isEmpty - for (aa <- a.address.valueBox; ba <- b.address.valueBox) { - aa.country.valueBox must_== ba.country.valueBox - aa.postalCode.valueBox must_== aa.postalCode.valueBox - aa.city.valueBox must_== aa.city.valueBox - aa.street.valueBox must_== aa.street.valueBox - } - success - } - - "A JSON record" should { - def testRec1: Person = Person.createRecord.name("Alice").age(25) - val testDoc1: JObject = ("age" -> 25) ~ ("name" -> "Alice") - def testRec2: Person = Person.createRecord.name("Bob").age(30) - val testDoc2: JObject = ("age" -> 30) ~ ("extra1" -> "value1") ~ ("extra2" -> "value2") ~ ("favoriteColor" -> "blue") ~ ("name" -> "Bob") - def testRec3: Person = Person.createRecord.name("Charlie").age(0) - val testDoc3: JObject = ("name" -> "Bob") - def testRec4: Person = Person.createRecord.name("Max").age(25).address(Address.createRecord.street("my street").country("France").city("Paris").postalCode("75001")) - val testDoc4: JObject = ("address" -> ("city" -> "Paris") ~ ("country" -> "France") ~ ("postalCode" -> "75001") ~ ("street" -> "my street")) ~ ("age" -> 25) ~ ("name" -> "Max") - - "encode basic records correctly" in { - compact(render(testRec1.asJValue)) must_== compact(render(testDoc1)) - } - - "encode record with subrecord correctly" in { - compact(render(testRec4.asJValue)) must_== compact(render(testDoc4)) - } - - "decode basic records correctly" in { - val recBox = Person.fromJValue(testDoc1) - recBox.isDefined must_== true - val Full(rec) = recBox - assertEqualPerson(rec, testRec1) - } - - "preserve extra fields from JSON" in { - val recBox = Person.fromJValue(testDoc2) - recBox.isDefined must_== true - val Full(rec) = recBox - rec.additionalJFields must_== List(JField("extra1", JString("value1")), - JField("extra2", JString("value2"))) - rec.age.set(1) - - compact(render(rec.asJValue)) must_== compact(render(("age" -> 1) ~ testDoc2.remove("age"))) - } - - "support unset optional fields" in { - val recBox = Person.fromJValue(testDoc1) - recBox.isDefined must_== true - val Full(rec) = recBox - - rec.favoriteColor.value.isDefined must_== false - } - - "support set optional fields" in { - val recBox = Person.fromJValue(testDoc2) - recBox.isDefined must_== true - val Full(rec) = recBox - - rec.favoriteColor.value must_== Some("blue") - } - - "support set subRecord field" in { - val recBox = Person.fromJValue(testDoc4) - recBox.isDefined must_== true - val Full(rec) = recBox - - rec.address.valueBox.flatMap(_.street.valueBox) must_== Full("my street") - } - - "not set missing fields" in { - val rec = Person.createRecord - rec.age.set(123) - JSONMetaRecord.overrideNeedAllJSONFields.doWith(false) { rec.setFieldsFromJValue(testDoc3) } must_== Full(()) - rec.age.value must_== 123 - } - - "honor overrideIgnoreExtraJSONFields == true" in { - val recBox = JSONMetaRecord.overrideIgnoreExtraJSONFields.doWith(true) { Person.fromJValue(testDoc2) } - recBox.isDefined must_== true - } - - "honor overrideIgnoreExtraJSONFields == false" in { - val recBox = JSONMetaRecord.overrideIgnoreExtraJSONFields.doWith(false) { Person.fromJValue(testDoc2) } - recBox.isDefined must_== false - } - - "honor overrideNeedAllJSONFields == true" in { - val recBox = JSONMetaRecord.overrideNeedAllJSONFields.doWith(true) { Person.fromJValue(testDoc3) } - recBox.isDefined must_== false - } - - "honor overrideNeedAllJSONFields == false" in { - val recBox = JSONMetaRecord.overrideNeedAllJSONFields.doWith(false) { Person.fromJValue(testDoc3) } - recBox.isDefined must_== true - } - } -} diff --git a/project/Build.scala b/project/Build.scala index 3f4db29760..01913a34cd 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -106,7 +106,7 @@ object BuildDef extends Build { // Persistence Projects // -------------------- lazy val persistence: Seq[ProjectReference] = - Seq(db, proto, jpa, mapper, record, couchdb, squeryl_record, mongodb, mongodb_record, ldap) + Seq(db, proto, jpa, mapper, record, squeryl_record, mongodb, mongodb_record, ldap) lazy val db = persistenceProject("db") @@ -136,12 +136,6 @@ object BuildDef extends Build { persistenceProject("record") .dependsOn(proto) - lazy val couchdb = - persistenceProject("couchdb") - .dependsOn(record) - .settings(libraryDependencies += dispatch_http, - parallelExecution in Test := false) - lazy val squeryl_record = persistenceProject("squeryl-record") .dependsOn(record, db) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 1532e13d64..0a266a66cd 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -37,7 +37,6 @@ object Dependencies { lazy val commons_codec = "commons-codec" % "commons-codec" % "1.6" lazy val commons_fileupload = "commons-fileupload" % "commons-fileupload" % "1.2.2" lazy val commons_httpclient = "commons-httpclient" % "commons-httpclient" % "3.1" - lazy val dispatch_http = "net.databinder" % "dispatch-http" % "0.7.8" cross CVMappingAll lazy val javamail = "javax.mail" % "mail" % "1.4.4" lazy val joda_time = "joda-time" % "joda-time" % "2.1" lazy val joda_convert = "org.joda" % "joda-convert" % "1.2" From 4b42594c2c2bdce81ae4f1db9d2d31fd5df25241 Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Tue, 18 Dec 2012 23:55:15 -0500 Subject: [PATCH 0280/1949] Fixed #1310 - Deprecate JSONParser --- core/util/src/main/scala/net/liftweb/util/JSONParser.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/core/util/src/main/scala/net/liftweb/util/JSONParser.scala b/core/util/src/main/scala/net/liftweb/util/JSONParser.scala index 36069f2d42..81a1d5239e 100644 --- a/core/util/src/main/scala/net/liftweb/util/JSONParser.scala +++ b/core/util/src/main/scala/net/liftweb/util/JSONParser.scala @@ -19,6 +19,7 @@ package util import scala.util.parsing.combinator.{Parsers, ImplicitConversions} import common._ +@deprecated("Use lift-json instead", "2.5") object JSONParser extends SafeSeqParser with ImplicitConversions { implicit def strToInput(in: String): Input = new scala.util.parsing.input.CharArrayReader(in.toCharArray) type Elem = Char From f2f1e83a170f23a08dac36e1401ce277368a5352 Mon Sep 17 00:00:00 2001 From: Dave Whittaker Date: Fri, 4 Jan 2013 14:05:34 -0500 Subject: [PATCH 0281/1949] Updated squeryl dependency to 0.9.5-6. Closes #1386. --- project/Dependencies.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 0a266a66cd..fa0c1ce6b0 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -48,7 +48,7 @@ object Dependencies { lazy val scala_compiler: ModuleMap = "org.scala-lang" % "scala-compiler" % _ lazy val scalaz_core: ModuleMap = sv => scalazGroup(sv) % "scalaz-core" % scalazVersion(sv) cross CVMappingAll lazy val slf4j_api = "org.slf4j" % "slf4j-api" % slf4jVersion - lazy val squeryl = "org.squeryl" % "squeryl" % "0.9.5-4" cross crossMapped("2.9.1-1" -> "2.9.1", "2.8.2" -> "2.8.1") + lazy val squeryl = "org.squeryl" % "squeryl" % "0.9.5-6" cross CVMapping29 // Aliases lazy val mongo_driver = mongo_java_driver From 3743913154f13e88089f4c1bba601bd66951dc7e Mon Sep 17 00:00:00 2001 From: Naftoli Gugenheim Date: Mon, 12 Nov 2012 05:50:07 -0500 Subject: [PATCH 0282/1949] Make MailerSpec use `eventually` --- .../scala/net/liftweb/util/MailerSpec.scala | 43 ++++++++----------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/core/util/src/test/scala/net/liftweb/util/MailerSpec.scala b/core/util/src/test/scala/net/liftweb/util/MailerSpec.scala index 59ddd77706..85e20cea05 100644 --- a/core/util/src/test/scala/net/liftweb/util/MailerSpec.scala +++ b/core/util/src/test/scala/net/liftweb/util/MailerSpec.scala @@ -30,22 +30,28 @@ import common._ object MailerSpec extends Specification { "Mailer Specification".title sequential - - MyMailer.touch() - import MyMailer._ - - private def doNewMessage(f: => Unit): MimeMessage = { + val myMailer = new Mailer { + @volatile var lastMessage: Box[MimeMessage] = Empty + + testModeSend.default.set((msg: MimeMessage) => { + lastMessage = Full(msg) + }) + + assert(Props.testMode) + } + + import myMailer._ + + private def doNewMessage(send: => Unit): MimeMessage = { lastMessage = Empty - val ignore = f + send - MailerSpec.this.synchronized { - while (lastMessage.isEmpty) { - MailerSpec.this.wait(100) - } - lastMessage.openOrThrowException("Test") + eventually { + lastMessage.isEmpty must_== false } + lastMessage openOrThrowException("Checked") } "A Mailer" should { @@ -100,18 +106,3 @@ object MailerSpec extends Specification { } } } - -object MyMailer extends Mailer { - @volatile var lastMessage: Box[MimeMessage] = Empty - - testModeSend.default.set((msg: MimeMessage) => { - lastMessage = Full(msg) -// MailerSpec.this.notifyAll() - }) - - def touch() { - Props.testMode - Thread.sleep(10) - } // do nothing, but force initialization of this class -} - From 329dcff5365c7459784971a79809e5ca1d1adf65 Mon Sep 17 00:00:00 2001 From: Naftoli Gugenheim Date: Wed, 14 Nov 2012 02:40:07 -0500 Subject: [PATCH 0283/1949] MemoizeSpec: apply(key, default) does not return a Box No idea how this used to pass --- .../src/test/scala/net/liftweb/webapptest/MemoizeSpec.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/webkit/src/test/scala/net/liftweb/webapptest/MemoizeSpec.scala b/web/webkit/src/test/scala/net/liftweb/webapptest/MemoizeSpec.scala index f8d85b21b9..e4ab6b313e 100644 --- a/web/webkit/src/test/scala/net/liftweb/webapptest/MemoizeSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/webapptest/MemoizeSpec.scala @@ -71,7 +71,7 @@ object MemoizeSpec extends Specification { "Request memo should work in the same request" >> { S.initIfUninitted(session1) { requestMemo(3) must_== Empty - requestMemo(3, 44) must_== Full(44) + requestMemo(3, 44) must_== 44 requestMemo(3) must_== Full(44) } } From 7dfa0044d34118ebb43f7b4a6f128946b67d973b Mon Sep 17 00:00:00 2001 From: Naftoli Gugenheim Date: Sun, 25 Nov 2012 19:52:16 -0500 Subject: [PATCH 0284/1949] Test for Props.testMode detection Does not serve as a complete spec, only to assert that during the current run the mode has indeed been detected as "test." --- .../scala/net/liftweb/util/MailerSpec.scala | 2 -- .../scala/net/liftweb/util/PropsSpec.scala | 34 +++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 core/util/src/test/scala/net/liftweb/util/PropsSpec.scala diff --git a/core/util/src/test/scala/net/liftweb/util/MailerSpec.scala b/core/util/src/test/scala/net/liftweb/util/MailerSpec.scala index 85e20cea05..653e29fa34 100644 --- a/core/util/src/test/scala/net/liftweb/util/MailerSpec.scala +++ b/core/util/src/test/scala/net/liftweb/util/MailerSpec.scala @@ -37,8 +37,6 @@ object MailerSpec extends Specification { testModeSend.default.set((msg: MimeMessage) => { lastMessage = Full(msg) }) - - assert(Props.testMode) } import myMailer._ diff --git a/core/util/src/test/scala/net/liftweb/util/PropsSpec.scala b/core/util/src/test/scala/net/liftweb/util/PropsSpec.scala new file mode 100644 index 0000000000..b2ad585d65 --- /dev/null +++ b/core/util/src/test/scala/net/liftweb/util/PropsSpec.scala @@ -0,0 +1,34 @@ +/* + * Copyright 2006-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 util + +import org.specs2.mutable.Specification + + +/** + * Systems under specification for Lift Mailer. + */ +object PropsSpec extends Specification { + "Props Specification".title + + "Props" should { + "Detect test mode correctly" in { + Props.testMode must_== true + } + } +} From 11f607828e894b92c0436a723d7e177e225070be Mon Sep 17 00:00:00 2001 From: Naftoli Gugenheim Date: Sun, 6 Jan 2013 03:01:30 -0500 Subject: [PATCH 0285/1949] Props: Add another specs2 class to test for --- core/util/src/main/scala/net/liftweb/util/Props.scala | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/Props.scala b/core/util/src/main/scala/net/liftweb/util/Props.scala index b9799ca293..11bb6d097a 100644 --- a/core/util/src/main/scala/net/liftweb/util/Props.scala +++ b/core/util/src/main/scala/net/liftweb/util/Props.scala @@ -120,7 +120,9 @@ object Props extends Logger { val names = List( "org.apache.maven.surefire.booter.SurefireBooter", "sbt.TestRunner", - "org.specs2.runner.TestInterfaceRunner" // sometimes specs2 runs tests on another thread + "org.specs2.runner.TestInterfaceRunner", // sometimes specs2 runs tests on another thread + "org.specs2.runner.TestInterfaceConsoleReporter", + "org.specs2.specification.FragmentExecution" ) if(st.exists(e => names.exists(e.getClassName.startsWith))) Test @@ -208,7 +210,7 @@ object Props extends Logger { * before you call anything else in Props. */ @volatile var whereToLook: () => List[(String, () => Box[InputStream])] = () => Nil - + /** * The map of key/value pairs retrieved from the property file. @@ -226,7 +228,7 @@ object Props extends Logger { toTry.map{ f => { val name = f() + "props" - name -> {() => + name -> {() => val res = tryo{getClass.getResourceAsStream(name)}.filter(_ ne null) trace("Trying to open resource %s. Result=%s".format(name, res)) res From 12885f6a9366d267fd4aa05b18c080e1f279fa99 Mon Sep 17 00:00:00 2001 From: Naftoli Gugenheim Date: Sun, 6 Jan 2013 03:05:02 -0500 Subject: [PATCH 0286/1949] MailerSpec needs to touch Props.mode --- core/util/src/test/scala/net/liftweb/util/MailerSpec.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/util/src/test/scala/net/liftweb/util/MailerSpec.scala b/core/util/src/test/scala/net/liftweb/util/MailerSpec.scala index 653e29fa34..66bf60bc26 100644 --- a/core/util/src/test/scala/net/liftweb/util/MailerSpec.scala +++ b/core/util/src/test/scala/net/liftweb/util/MailerSpec.scala @@ -31,6 +31,8 @@ object MailerSpec extends Specification { "Mailer Specification".title sequential + Props.mode // touch the lazy val so it's detected correctly + val myMailer = new Mailer { @volatile var lastMessage: Box[MimeMessage] = Empty From a68b65d3920b998ff412ba90f1608d75f4328387 Mon Sep 17 00:00:00 2001 From: Naftoli Gugenheim Date: Sun, 6 Jan 2013 17:03:31 -0500 Subject: [PATCH 0287/1949] Joni's fix for JsonBoxSerializer --- .../net/liftweb/json/ext/JsonBoxSerializer.scala | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/core/json-ext/src/main/scala/net/liftweb/json/ext/JsonBoxSerializer.scala b/core/json-ext/src/main/scala/net/liftweb/json/ext/JsonBoxSerializer.scala index 75bff5592d..c3c0b8b217 100644 --- a/core/json-ext/src/main/scala/net/liftweb/json/ext/JsonBoxSerializer.scala +++ b/core/json-ext/src/main/scala/net/liftweb/json/ext/JsonBoxSerializer.scala @@ -40,12 +40,14 @@ class JsonBoxSerializer extends Serializer[Box[_]] { extract(chain, TypeInfo(BoxClass, Some(typeHoldingFailure))).asInstanceOf[Box[Failure]]) case JObject(JField("box_failure", JString("ParamFailure")) :: JField("msg", JString(msg)) :: - JField("exception", exception) :: + JField("exception", exn) :: JField("chain", chain) :: JField("paramType", JString(paramType)) :: JField("param", param) :: Nil) => val clazz = Thread.currentThread.getContextClassLoader.loadClass(paramType) - ParamFailure(msg, extract(param, TypeInfo(clazz, None))) + ParamFailure(msg, deserializeException(exn), + extract(chain, TypeInfo(BoxClass, Some(typeHoldingFailure))).asInstanceOf[Box[Failure]], + extract(param, TypeInfo(clazz, None))) case x => val t = ptype.getOrElse(throw new MappingException("parameterized type not known for Box")) Full(extract(x, TypeInfo(t.getActualTypeArguments()(0).asInstanceOf[Class[_]], None))) @@ -55,11 +57,6 @@ class JsonBoxSerializer extends Serializer[Box[_]] { def serialize(implicit format: Formats): PartialFunction[Any, JValue] = { case Full(x) => decompose(x) case Empty => JNull - case Failure(msg, exn, chain) => - JObject(JField("box_failure", JString("Failure")) :: - JField("msg", JString(msg)) :: - JField("exception", serializeException(exn)) :: - JField("chain", decompose(chain)) :: Nil) case ParamFailure(msg, exn, chain, param) => JObject(JField("box_failure", JString("ParamFailure")) :: JField("msg", JString(msg)) :: @@ -67,6 +64,11 @@ class JsonBoxSerializer extends Serializer[Box[_]] { JField("chain", decompose(chain)) :: JField("paramType", JString(param.asInstanceOf[AnyRef].getClass.getName)) :: JField("param", decompose(param)) :: Nil) + case Failure(msg, exn, chain) => + JObject(JField("box_failure", JString("Failure")) :: + JField("msg", JString(msg)) :: + JField("exception", serializeException(exn)) :: + JField("chain", decompose(chain)) :: Nil) } private val typeHoldingFailure = new ParameterizedType { From 9e2f87f4eb0897172cafbae5d190d15a73b94944 Mon Sep 17 00:00:00 2001 From: Naftoli Gugenheim Date: Mon, 7 Jan 2013 16:25:49 -0500 Subject: [PATCH 0288/1949] json-ext: JsonBoxSerializer: {exn->exception} --- .../liftweb/json/ext/JsonBoxSerializer.scala | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/core/json-ext/src/main/scala/net/liftweb/json/ext/JsonBoxSerializer.scala b/core/json-ext/src/main/scala/net/liftweb/json/ext/JsonBoxSerializer.scala index c3c0b8b217..486e9c30d8 100644 --- a/core/json-ext/src/main/scala/net/liftweb/json/ext/JsonBoxSerializer.scala +++ b/core/json-ext/src/main/scala/net/liftweb/json/ext/JsonBoxSerializer.scala @@ -34,18 +34,18 @@ class JsonBoxSerializer extends Serializer[Box[_]] { case JNull | JNothing => Empty case JObject(JField("box_failure", JString("Failure")) :: JField("msg", JString(msg)) :: - JField("exception", exn) :: + JField("exception", exception) :: JField("chain", chain) :: Nil) => - Failure(msg, deserializeException(exn), + Failure(msg, deserializeException(exception), extract(chain, TypeInfo(BoxClass, Some(typeHoldingFailure))).asInstanceOf[Box[Failure]]) case JObject(JField("box_failure", JString("ParamFailure")) :: JField("msg", JString(msg)) :: - JField("exception", exn) :: + JField("exception", exception) :: JField("chain", chain) :: JField("paramType", JString(paramType)) :: JField("param", param) :: Nil) => val clazz = Thread.currentThread.getContextClassLoader.loadClass(paramType) - ParamFailure(msg, deserializeException(exn), + ParamFailure(msg, deserializeException(exception), extract(chain, TypeInfo(BoxClass, Some(typeHoldingFailure))).asInstanceOf[Box[Failure]], extract(param, TypeInfo(clazz, None))) case x => @@ -57,17 +57,17 @@ class JsonBoxSerializer extends Serializer[Box[_]] { def serialize(implicit format: Formats): PartialFunction[Any, JValue] = { case Full(x) => decompose(x) case Empty => JNull - case ParamFailure(msg, exn, chain, param) => + case ParamFailure(msg, exception, chain, param) => JObject(JField("box_failure", JString("ParamFailure")) :: JField("msg", JString(msg)) :: - JField("exception", serializeException(exn)) :: + JField("exception", serializeException(exception)) :: JField("chain", decompose(chain)) :: JField("paramType", JString(param.asInstanceOf[AnyRef].getClass.getName)) :: JField("param", decompose(param)) :: Nil) - case Failure(msg, exn, chain) => + case Failure(msg, exception, chain) => JObject(JField("box_failure", JString("Failure")) :: JField("msg", JString(msg)) :: - JField("exception", serializeException(exn)) :: + JField("exception", serializeException(exception)) :: JField("chain", decompose(chain)) :: Nil) } @@ -77,7 +77,7 @@ class JsonBoxSerializer extends Serializer[Box[_]] { def getRawType = classOf[Box[Failure]] } - private def serializeException(exn: Box[Throwable]) = exn match { + private def serializeException(exception: Box[Throwable]) = exception match { case Full(x) => JString(javaSerialize(x)) case _ => JNull } From ca08df6259e5db21921216b08ebad436058e2c0c Mon Sep 17 00:00:00 2001 From: Naftoli Gugenheim Date: Sun, 6 Jan 2013 17:33:46 -0500 Subject: [PATCH 0289/1949] 2.10: Build configuration --- build.sbt | 4 ++-- project/Build.scala | 3 ++- project/Dependencies.scala | 12 +++++++----- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/build.sbt b/build.sbt index 4a86ac50e7..204a396651 100644 --- a/build.sbt +++ b/build.sbt @@ -12,9 +12,9 @@ startYear in ThisBuild := Some(2006) organizationName in ThisBuild := "WorldWide Conferencing, LLC" -crossScalaVersions in ThisBuild := Seq("2.9.2", "2.9.1-1", "2.9.1") +crossScalaVersions in ThisBuild := Seq("2.10.0", "2.9.2", "2.9.1-1", "2.9.1") -libraryDependencies in ThisBuild ++= Seq(specs2, scalacheck) +libraryDependencies in ThisBuild <++= scalaVersion {sv => Seq(specs2(sv), scalacheck) } // Settings for Sonatype compliance pomIncludeRepository in ThisBuild := { _ => false } diff --git a/project/Build.scala b/project/Build.scala index 01913a34cd..efcd53cc92 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -91,8 +91,9 @@ object BuildDef extends Build { .settings(description := "Webkit Library", parallelExecution in Test := false, libraryDependencies <++= scalaVersion { sv => - Seq(commons_fileupload, servlet_api, specs2.copy(configurations = Some("provided")), jetty6, jwebunit) + Seq(commons_fileupload, servlet_api, specs2(sv).copy(configurations = Some("provided")), jetty6, jwebunit) }, + libraryDependencies <++= scalaVersion { case "2.10.0" => scalaactors::Nil case _ => Nil }, initialize in Test <<= (sourceDirectory in Test) { src => System.setProperty("net.liftweb.webapptest.src.test.webapp", (src / "webapp").absString) }) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index fa0c1ce6b0..3535a99dee 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -24,8 +24,8 @@ object Dependencies { type ModuleMap = String => ModuleID lazy val CVMapping2911 = crossMapped("2.9.1-1" -> "2.9.1") - lazy val CVMapping29 = crossMapped("2.9.1-1" -> "2.9.2", "2.9.1" -> "2.9.2") - lazy val CVMappingAll = crossMapped("2.9.2" -> "2.9.1", "2.9.1-1" -> "2.9.1") + lazy val CVMapping29 = crossMapped("2.10.0" -> "2.10", "2.9.1-1" -> "2.9.2", "2.9.1" -> "2.9.2") + lazy val CVMappingAll = crossMapped("2.10.0" -> "2.10", "2.9.2" -> "2.9.1", "2.9.1-1" -> "2.9.1") lazy val slf4jVersion = "1.7.2" @@ -46,9 +46,10 @@ object Dependencies { lazy val scalajpa = "org.scala-libs" % "scalajpa" % "1.4" cross CVMapping29 lazy val scalap: ModuleMap = "org.scala-lang" % "scalap" % _ lazy val scala_compiler: ModuleMap = "org.scala-lang" % "scala-compiler" % _ - lazy val scalaz_core: ModuleMap = sv => scalazGroup(sv) % "scalaz-core" % scalazVersion(sv) cross CVMappingAll + lazy val scalaz_core: ModuleMap = sv => scalazGroup(sv) % "scalaz-core" % scalazVersion(sv) cross crossMapped("2.10.0" -> "2.10.0-RC5", "2.9.2" -> "2.9.1", "2.9.1-1" -> "2.9.1") lazy val slf4j_api = "org.slf4j" % "slf4j-api" % slf4jVersion lazy val squeryl = "org.squeryl" % "squeryl" % "0.9.5-6" cross CVMapping29 + @deprecated lazy val scalaactors= "org.scala-lang" % "scala-actors" % "2.10.0" // Aliases lazy val mongo_driver = mongo_java_driver @@ -80,7 +81,8 @@ object Dependencies { lazy val jetty6 = "org.mortbay.jetty" % "jetty" % "6.1.26" % "test" lazy val jwebunit = "net.sourceforge.jwebunit" % "jwebunit-htmlunit-plugin" % "2.5" % "test" lazy val mockito_all = "org.mockito" % "mockito-all" % "1.9.0" % "test" - lazy val scalacheck = "org.scalacheck" % "scalacheck" % "1.10.0" % "test" cross CVMappingAll - lazy val specs2 = "org.specs2" % "specs2" % "1.11" % "test" cross CVMappingAll + lazy val scalacheck = "org.scalacheck" %% "scalacheck" % "1.10.0" % "test" + lazy val specs2: ModuleMap = + "org.specs2" %% "specs2" % defaultOrMapped("1.12.3")(_) } From 311510115476c2ba840fbb1e3c091ddbfe8d1167 Mon Sep 17 00:00:00 2001 From: Naftoli Gugenheim Date: Sun, 6 Jan 2013 17:34:36 -0500 Subject: [PATCH 0290/1949] 2.10: lift-json (mainly Joni's work) --- .../scala/net/liftweb/json/Extraction.scala | 2 +- .../main/scala/net/liftweb/json/Formats.scala | 4 +- .../net/liftweb/json/JsonPrintingSpec.scala | 2 +- .../liftweb/json/SerializationExamples.scala | 83 ++++++++++--------- .../test/scala/net/liftweb/json/XmlBugs.scala | 8 +- 5 files changed, 51 insertions(+), 48 deletions(-) diff --git a/core/json/src/main/scala/net/liftweb/json/Extraction.scala b/core/json/src/main/scala/net/liftweb/json/Extraction.scala index 541bd47cab..bf02030903 100644 --- a/core/json/src/main/scala/net/liftweb/json/Extraction.scala +++ b/core/json/src/main/scala/net/liftweb/json/Extraction.scala @@ -80,7 +80,7 @@ object Extraction { case x: JValue => x case x if primitive_?(x.getClass) => primitive2jvalue(x)(formats) case x: Map[_, _] => JObject((x map { case (k: String, v) => JField(k, decompose(v)) }).toList) - case x: Collection[_] => JArray(x.toList map decompose) + case x: Iterable[_] => JArray(x.toList map decompose) case x if (x.getClass.isArray) => JArray(x.asInstanceOf[Array[_]].toList map decompose) case x: Option[_] => x.flatMap[JValue] { y => Some(decompose(y)) }.getOrElse(JNothing) case x => diff --git a/core/json/src/main/scala/net/liftweb/json/Formats.scala b/core/json/src/main/scala/net/liftweb/json/Formats.scala index f44ffc4ed9..3ab3cc101d 100644 --- a/core/json/src/main/scala/net/liftweb/json/Formats.scala +++ b/core/json/src/main/scala/net/liftweb/json/Formats.scala @@ -157,7 +157,7 @@ trait TypeHints { /** * Adds the specified type hints to this type hints. */ - def + (hints: TypeHints): TypeHints = CompositeTypeHints(hints.components ::: components) + def + (hints: TypeHints): TypeHints = CompositeTypeHints(components ::: hints.components) private[TypeHints] case class CompositeTypeHints(override val components: List[TypeHints]) extends TypeHints { val hints: List[Class[_]] = components.flatMap(_.hints) @@ -167,7 +167,7 @@ trait TypeHints { */ def hintFor(clazz: Class[_]): String = components.filter(_.containsHint_?(clazz)) .map(th => (th.hintFor(clazz), th.classFor(th.hintFor(clazz)).getOrElse(sys.error("hintFor/classFor not invertible for " + th)))) - .sort((x, y) => (delta(x._2, clazz) - delta(y._2, clazz)) < 0).head._1 + .sortWith((x, y) => (delta(x._2, clazz) - delta(y._2, clazz)) < 0).head._1 def classFor(hint: String): Option[Class[_]] = { def hasClass(h: TypeHints) = diff --git a/core/json/src/test/scala/net/liftweb/json/JsonPrintingSpec.scala b/core/json/src/test/scala/net/liftweb/json/JsonPrintingSpec.scala index 9c9321c975..b1c75a2616 100644 --- a/core/json/src/test/scala/net/liftweb/json/JsonPrintingSpec.scala +++ b/core/json/src/test/scala/net/liftweb/json/JsonPrintingSpec.scala @@ -36,7 +36,7 @@ object JsonPrintingSpec extends Specification with JValueGen with ScalaCheck { check(forAll(rendering)) } - private def parse(json: String) = scala.util.parsing.json.JSON.parse(json) + private def parse(json: String) = scala.util.parsing.json.JSON.parseRaw(json) implicit def arbDoc: Arbitrary[Document] = Arbitrary(genJValue.map(render(_))) } diff --git a/core/json/src/test/scala/net/liftweb/json/SerializationExamples.scala b/core/json/src/test/scala/net/liftweb/json/SerializationExamples.scala index c8bb951279..087e1ee455 100644 --- a/core/json/src/test/scala/net/liftweb/json/SerializationExamples.scala +++ b/core/json/src/test/scala/net/liftweb/json/SerializationExamples.scala @@ -45,7 +45,7 @@ object SerializationExamples extends Specification { } case class Nullable(name: String) - + "Lotto serialization example" in { import LottoExample.{Lotto, lotto} @@ -80,22 +80,22 @@ object SerializationExamples extends Specification { val ser = swrite(r3) read[Rec](ser) mustEqual r3 } - + "Set serialization example" in { - val s = SetContainer(Set("foo", "bar")) + val s = SetContainer(Set("foo", "bar")) val ser = swrite(s) read[SetContainer](ser) mustEqual s } - + "Array serialization example" in { - val s = ArrayContainer(Array("foo", "bar")) + val s = ArrayContainer(Array("foo", "bar")) val ser = swrite(s); - val unser = read[ArrayContainer](ser) + val unser = read[ArrayContainer](ser) s.array.toList mustEqual unser.array.toList } - + "Seq serialization example" in { - val s = SeqContainer(List("foo", "bar")) + val s = SeqContainer(List("foo", "bar")) val ser = swrite(s) read[SeqContainer](ser) mustEqual s } @@ -108,7 +108,7 @@ object SerializationExamples extends Specification { "None Option of tuple serialization example" in { // This is a regression test case, failed in lift json - val s = OptionOfTupleOfDouble(None) + val s = OptionOfTupleOfDouble(None) val ser = swrite(s) read[OptionOfTupleOfDouble](ser) mustEqual s } @@ -121,7 +121,7 @@ object SerializationExamples extends Specification { } "Case class from type constructors example" in { - val p = ProperType(TypeConstructor(Chicken(10)), (25, Player("joe"))) + val p = ProperType(TypeConstructor(Chicken(10)), Pair(25, Player("joe"))) val ser = swrite(p) read[ProperType](ser) mustEqual p } @@ -147,34 +147,34 @@ object ShortTypeHintExamples extends TypeHintExamples { object FullTypeHintExamples extends TypeHintExamples { import Serialization.{read, write => swrite} - + implicit val formats = Serialization.formats(FullTypeHints(List[Class[_]](classOf[Animal], classOf[True], classOf[False], classOf[Falcon], classOf[Chicken]))) - + "Ambiguous field decomposition example" in { val a = Ambiguous(False()) - - val ser = swrite(a) + + val ser = swrite(a) read[Ambiguous](ser) mustEqual a } - + "Ambiguous parameterized field decomposition example" in { val o = AmbiguousP(Chicken(23)) - - val ser = swrite(o) + + val ser = swrite(o) read[AmbiguousP](ser) mustEqual o } - + "Option of ambiguous field decomposition example" in { val o = OptionOfAmbiguous(Some(True())) - - val ser = swrite(o) + + val ser = swrite(o) read[OptionOfAmbiguous](ser) mustEqual o } - + "Option of ambiguous parameterized field decomposition example" in { val o = OptionOfAmbiguousP(Some(Falcon(200.0))) - - val ser = swrite(o) + + val ser = swrite(o) read[OptionOfAmbiguousP](ser) mustEqual o } } @@ -233,28 +233,28 @@ object CustomSerializerExamples extends Specification { import java.util.regex.Pattern class IntervalSerializer extends CustomSerializer[Interval](format => ( - { - case JObject(JField("start", JInt(s)) :: JField("end", JInt(e)) :: Nil) => - new Interval(s.longValue, e.longValue) + { + case JObject(JField("start", JInt(s)) :: JField("end", JInt(e)) :: Nil) => + new Interval(s.longValue, e.longValue) }, - { + { case x: Interval => - JObject(JField("start", JInt(BigInt(x.startTime))) :: - JField("end", JInt(BigInt(x.endTime))) :: Nil) + JObject(JField("start", JInt(BigInt(x.startTime))) :: + JField("end", JInt(BigInt(x.endTime))) :: Nil) } )) class PatternSerializer extends CustomSerializer[Pattern](format => ( - { - case JObject(JField("$pattern", JString(s)) :: Nil) => Pattern.compile(s) + { + case JObject(JField("$pattern", JString(s)) :: Nil) => Pattern.compile(s) }, - { - case x: Pattern => JObject(JField("$pattern", JString(x.pattern)) :: Nil) + { + case x: Pattern => JObject(JField("$pattern", JString(x.pattern)) :: Nil) } )) class DateSerializer extends CustomSerializer[Date](format => ( - { + { case JObject(List(JField("$dt", JString(s)))) => format.dateFormat.parse(s).getOrElse(throw new MappingException("Can't parse "+ s + " to Date")) }, @@ -264,27 +264,27 @@ object CustomSerializerExamples extends Specification { )) class IndexedSeqSerializer extends Serializer[IndexedSeq[_]] { - def deserialize(implicit format: Formats) = { + def deserialize(implicit formats: Formats) = { case (TypeInfo(clazz, ptype), json) if classOf[IndexedSeq[_]].isAssignableFrom(clazz) => json match { - case JArray(xs) => + case JArray(xs) => val t = ptype.getOrElse(throw new MappingException("parameterized type not known")) xs.map(x => Extraction.extract(x, TypeInfo(t.getActualTypeArguments()(0).asInstanceOf[Class[_]], None))).toIndexedSeq case x => throw new MappingException("Can't convert " + x + " to IndexedSeq") } } - def serialize(implicit format: Formats) = { + def serialize(implicit formats: Formats) = { case i: IndexedSeq[_] => JArray(i.map(Extraction.decompose).toList) } } - implicit val formats = Serialization.formats(NoTypeHints) + + implicit val formats = Serialization.formats(NoTypeHints) + new IntervalSerializer + new PatternSerializer + new DateSerializer + new IndexedSeqSerializer val i = new Interval(1, 4) val ser = swrite(i) ser mustEqual """{"start":1,"end":4}""" - val i2 = read[Interval](ser) + val i2 = read[Interval](ser) i2.startTime mustEqual i.startTime i2.endTime mustEqual i.endTime @@ -301,7 +301,7 @@ object CustomSerializerExamples extends Specification { val xs = Indexed(Vector("a", "b", "c")) val iser = swrite(xs) iser mustEqual """{"xs":["a","b","c"]}""" - read[Indexed](iser).xs.toList mustEqual List("a","b","c") + read[Indexed](iser).xs.toList mustEqual List("a","b","c") } case class Indexed(xs: IndexedSeq[String]) @@ -374,4 +374,5 @@ case class OptionOfTupleOfDouble(position: Option[Tuple2[Double, Double]]) case class Player(name: String) case class TypeConstructor[A](x: A) -case class ProperType(x: TypeConstructor[Chicken], t: (Int, Player)) +case class Pair[A, B](fst: A, snd: B) +case class ProperType(x: TypeConstructor[Chicken], t: Pair[Int, Player]) diff --git a/core/json/src/test/scala/net/liftweb/json/XmlBugs.scala b/core/json/src/test/scala/net/liftweb/json/XmlBugs.scala index 092ff1e78b..2cfbfe5cf5 100644 --- a/core/json/src/test/scala/net/liftweb/json/XmlBugs.scala +++ b/core/json/src/test/scala/net/liftweb/json/XmlBugs.scala @@ -52,13 +52,15 @@ object XmlBugs extends Specification { - val expected = """{"root":{"n":[{"x":"abc","id":"10"},{"x":"bcd","id":"11"}]}}""" - Printer.compact(render(toJson(xml))) mustEqual expected + val expected = """{"root":{"n":[{"x":"abc","id":"10"},{"x":"bcd","id":"11"}]}}""" + val expected210 = """{"root":{"n":[{"id":"10","x":"abc"},{"id":"11","x":"bcd"}]}}""" + val json = Printer.compact(render(toJson(xml))) + (json == expected || json == expected210) mustEqual true } "XML with empty node is converted correctly to JSON" in { val xml = - xxxyyy + xxxyyy val expected = """{"tips":{"group":[{"type":"Foo"},{"type":"Bar","tip":[{"text":"xxx"},{"text":"yyy"}]}]}}""" Printer.compact(render(toJson(xml))) mustEqual expected } From 5280bc81c09e74580fd28696f1a24cd9853b78e2 Mon Sep 17 00:00:00 2001 From: Naftoli Gugenheim Date: Sun, 6 Jan 2013 17:34:57 -0500 Subject: [PATCH 0291/1949] 2.10: lift-util --- core/util/src/main/scala/net/liftweb/util/CssSelector.scala | 3 ++- .../src/test/scala/net/liftweb/util/ClassHelpersSpec.scala | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/CssSelector.scala b/core/util/src/main/scala/net/liftweb/util/CssSelector.scala index 72e7f8eb40..e34f960a4b 100644 --- a/core/util/src/main/scala/net/liftweb/util/CssSelector.scala +++ b/core/util/src/main/scala/net/liftweb/util/CssSelector.scala @@ -177,9 +177,10 @@ object CssSelectorParser extends PackratParsers with ImplicitConversions { } } + private val atEnd = Parser { in => if(in.atEnd) Success(CharSequenceReader.EofCh, in) else Failure("", in)} private lazy val topParser: Parser[CssSelector] = phrase(rep1((_idMatch | _nameMatch | _classMatch | _attrMatch | _elemMatch | - _colonMatch | _starMatch) <~ (rep1(' ') | 26.toChar)) ~ opt(subNode)) ^^ { + _colonMatch | _starMatch) <~ (rep1(' ') | atEnd)) ~ opt(subNode)) ^^ { case (one :: Nil) ~ sn => fixAll(List(one), sn) case all ~ None if all.takeRight(1).head == StarSelector(Empty) => fixAll(all.dropRight(1), Some(KidsSubNode())) diff --git a/core/util/src/test/scala/net/liftweb/util/ClassHelpersSpec.scala b/core/util/src/test/scala/net/liftweb/util/ClassHelpersSpec.scala index 92a3c96efa..9dca96c2a8 100644 --- a/core/util/src/test/scala/net/liftweb/util/ClassHelpersSpec.scala +++ b/core/util/src/test/scala/net/liftweb/util/ClassHelpersSpec.scala @@ -173,11 +173,11 @@ object ClassHelpersSpec extends Specification { "return Empty if the instance is null" in { createInvoker("length", null) must_== Empty } - "return a Full can with the function from Unit to a can containing the result of the method to invoke" in { + "return a Full Box with the function from Unit to a Box containing the result of the method to invoke" in { createInvoker("length", "").openOrThrowException("Test").apply().get must_== 0 } "The invoker function will throw the cause exception if the method can't be called" in { - createInvoker("get", "").openOrThrowException("Test").apply must throwA[Exception] + (() => createInvoker("get", "").openOrThrowException("Test").apply)() must throwA[Exception] } } From 37c725685b6f0c7d965ff560935c5158bc57ff9e Mon Sep 17 00:00:00 2001 From: Naftoli Gugenheim Date: Sun, 6 Jan 2013 17:35:40 -0500 Subject: [PATCH 0292/1949] 2.10: lift-webkit --- .../scala/net/liftweb/builtin/snippet/Menu.scala | 2 +- .../main/scala/net/liftweb/http/LiftScreen.scala | 15 ++++++++------- .../src/main/scala/net/liftweb/http/S.scala | 8 ++++---- .../net/liftweb/http/rest/XMLApiHelper.scala | 2 +- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Menu.scala b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Menu.scala index 47a6895c2b..001bea2e6c 100644 --- a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Menu.scala +++ b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Menu.scala @@ -421,7 +421,7 @@ object Menu extends DispatchSnippet { } (S.request.flatMap(_.location), S.attr("param"), SiteMap.findAndTestLoc(name)) match { - case (_, Full(param), Full(loc: ConvertableLoc[T])) => { + case (_, Full(param), Full(loc: Loc[T] with ConvertableLoc[T])) => { (for { pv <- loc.convert(param) link <- loc.createLink(pv) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala index b6da30d469..1427243ff6 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala @@ -354,15 +354,16 @@ trait AbstractScreen extends Factory { * @param default - the default value of the field * @param stuff - any filter or validation functions */ - protected def builder[T](name: => String, default: => T, stuff: FilterOrValidate[T]*)(implicit man: Manifest[T]): FieldBuilder[T] = + protected def builder[T](name: => String, default: => T, stuff: FilterOrValidate[T]*)(implicit man: Manifest[T]): FieldBuilder[T] = { new FieldBuilder[T](name, default, man, Empty, stuff.toList.collect { - case AVal(v) => v + case AVal(v: (T => List[FieldError])) => v }, stuff.toList.collect { case AFilter(v) => v }, stuff) + } protected object FilterOrValidate { implicit def promoteFilter[T](f: T => T): FilterOrValidate[T] = AFilter(f) @@ -611,7 +612,7 @@ trait AbstractScreen extends Factory { */ protected def field[T](name: => String, default: => T, stuff: FilterOrValidate[T]*)(implicit man: Manifest[T]): Field {type ValueType = T} = new FieldBuilder[T](name, default, man, Empty, stuff.toList.flatMap { - case AVal(v) => List(v) + case AVal(v: (T => List[FieldError])) => List(v) case _ => Nil }, stuff.toList.flatMap { case AFilter(v) => List(v) @@ -756,7 +757,7 @@ trait AbstractScreen extends Factory { case _ => Nil }.toList override val validations = stuff.flatMap { - case AVal(v) => List(v) + case AVal(v: (T => List[FieldError])) => List(v) case _ => Nil }.toList @@ -793,7 +794,7 @@ trait AbstractScreen extends Factory { case _ => Nil }.toList override val validations = stuff.flatMap { - case AVal(v) => List(v) + case AVal(v: (T => List[FieldError])) => List(v) case _ => Nil }.toList @@ -943,7 +944,7 @@ trait AbstractScreen extends Factory { } /** - * Grabs the FormFieldId and FormParam parameters + * Grabs the FormFieldId and FormParam parameters */ protected def grabParams(in: Seq[FilterOrValidate[_]]): List[SHtml.ElemAttr] = { @@ -1482,7 +1483,7 @@ trait LiftScreen extends AbstractScreen with StatefulSnippet with ScreenWizardRe val localSnapshot = createSnapshot // val notices = S.getAllNotices - // if we're not Ajax, + // if we're not Ajax, if (!ajaxForms_?) { S.seeOther(S.uri, () => { // S.appendNotices(notices) diff --git a/web/webkit/src/main/scala/net/liftweb/http/S.scala b/web/webkit/src/main/scala/net/liftweb/http/S.scala index 983efb6809..d94220c50e 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/S.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/S.scala @@ -2028,13 +2028,13 @@ trait S extends HasParams with Loggable { * Returns the unprefixed attribute value as an Option[NodeSeq] * for easy addition to the attributes */ - def ~(key: String): Option[NodeSeq] = apply(key).toOption.map(Text) + def ~(key: String): Option[NodeSeq] = apply(key).toOption.map(Text(_)) /** * Returns the prefixed attribute value as an Option[NodeSeq] * for easy addition to the attributes */ - def ~(prefix: String, key: String): Option[NodeSeq] = apply(prefix, key).toOption.map(Text) + def ~(prefix: String, key: String): Option[NodeSeq] = apply(prefix, key).toOption.map(Text(_)) } /** @@ -2153,13 +2153,13 @@ trait S extends HasParams with Loggable { * Returns the unprefixed attribute value as an Option[NodeSeq] * for easy addition to the attributes */ - def ~(key: String): Option[NodeSeq] = apply(key).toOption.map(Text) + def ~(key: String): Option[NodeSeq] = apply(key).toOption.map(Text(_)) /** * Returns the prefixed attribute value as an Option[NodeSeq] * for easy addition to the attributes */ - def ~(prefix: String, key: String): Option[NodeSeq] = apply(prefix, key).toOption.map(Text) + def ~(prefix: String, key: String): Option[NodeSeq] = apply(prefix, key).toOption.map(Text(_)) } /** diff --git a/web/webkit/src/main/scala/net/liftweb/http/rest/XMLApiHelper.scala b/web/webkit/src/main/scala/net/liftweb/http/rest/XMLApiHelper.scala index a2fea36070..197ef65eb6 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/rest/XMLApiHelper.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/rest/XMLApiHelper.scala @@ -143,7 +143,7 @@ trait XMLApiHelper { (for (req <- S.request) yield req.path.partPath match { case _ :: name :: _ => name case _ => "" - }).map(Text) + }).map(Text(_)) /** * The method that wraps the outer-most tag around the body. The success, From d175b71de428efdc895b259261707819ba666f17 Mon Sep 17 00:00:00 2001 From: Naftoli Gugenheim Date: Wed, 19 Sep 2012 10:39:03 -0400 Subject: [PATCH 0293/1949] 2.10: lift-mapper --- .../scala/net/liftweb/mapper/ManyToMany.scala | 15 ++++----- .../net/liftweb/mapper/MappedField.scala | 4 +-- .../scala/net/liftweb/mapper/MetaMapper.scala | 30 +++++++++++------- .../scala/net/liftweb/mapper/OneToMany.scala | 31 ++++++++++++------- .../net/liftweb/mapper/view/ModelView.scala | 2 +- 5 files changed, 50 insertions(+), 32 deletions(-) diff --git a/persistence/mapper/src/main/scala/net/liftweb/mapper/ManyToMany.scala b/persistence/mapper/src/main/scala/net/liftweb/mapper/ManyToMany.scala index 2872cb2038..3ff96778a9 100644 --- a/persistence/mapper/src/main/scala/net/liftweb/mapper/ManyToMany.scala +++ b/persistence/mapper/src/main/scala/net/liftweb/mapper/ManyToMany.scala @@ -71,9 +71,6 @@ trait ManyToMany extends BaseKeyedMapper { val otherMeta: MetaMapper[T2], val qp: QueryParam[O]*) extends scala.collection.mutable.Buffer[T2] { - def thisFK[A](join: O)(f: MappedForeignKey[K,O,_>:T] => A): A = - thisField.actualField(join) match { case mfk: MappedForeignKey[K,O,T] => f(mfk) } - def otherFK[A](join: O)(f: MappedForeignKey[K2,O,T2] => A): A = otherField.actualField(join) match { case mfk: MappedForeignKey[K2,O,T2] => f(mfk) } @@ -106,7 +103,9 @@ trait ManyToMany extends BaseKeyedMapper { removedJoin // well, noLongerRemovedJoin... case None => val newJoin = joinMeta.create - thisFK(newJoin)(_.apply(ManyToMany.this)) + thisField.actualField(newJoin) match { + case mfk: MappedForeignKey[K,O,T] => mfk.set(primaryKeyField.is.asInstanceOf[K]) + } otherFK(newJoin)(_.apply(e)) newJoin } @@ -120,7 +119,7 @@ trait ManyToMany extends BaseKeyedMapper { removedJoins = join :: removedJoins val o = otherField.actualField(join) o.set(o.defaultValue) - thisFK(join)(f => f.set(f.defaultValue)) + thisField.actualField(join) match { case mfk => mfk set mfk.defaultValue } Some(join) case None => None @@ -190,7 +189,7 @@ trait ManyToMany extends BaseKeyedMapper { * Discard the cached state of this MappedManyToMany's children and reinitialize it from the database */ def refresh = { - val by = new Cmp[O, TheKeyType](thisField, OprEnum.Eql, Full(primaryKeyField.is), Empty, Empty) + val by = new Cmp[O, TheKeyType](thisField, OprEnum.Eql, Full(primaryKeyField.is.asInstanceOf[K]), Empty, Empty) _joins = joinMeta.findAll( (by :: qp.toList): _*) all @@ -210,7 +209,9 @@ trait ManyToMany extends BaseKeyedMapper { _joins = joins.filter { join => otherFK(join)(f => f.is != f.defaultValue) } - _joins foreach { thisFK(_)(_ set ManyToMany.this.primaryKeyField.is) } + _joins foreach { + thisField.actualField(_).asInstanceOf[MappedForeignKey[K,O,X] forSome {type X <: KeyedMapper[K,X]}] set ManyToMany.this.primaryKeyField.is.asInstanceOf[K] + } removedJoins.forall {_.delete_!} & ( // continue saving even if deleting fails children.forall(_.save) && diff --git a/persistence/mapper/src/main/scala/net/liftweb/mapper/MappedField.scala b/persistence/mapper/src/main/scala/net/liftweb/mapper/MappedField.scala index 0918cd35fe..d4533ea182 100644 --- a/persistence/mapper/src/main/scala/net/liftweb/mapper/MappedField.scala +++ b/persistence/mapper/src/main/scala/net/liftweb/mapper/MappedField.scala @@ -449,7 +449,7 @@ trait MappedField[FieldType <: Any,OwnerType <: Mapper[OwnerType]] extends Typed def toForm: Box[NodeSeq] = { - def mf(in: Node): NodeSeq = in match { + def mf(in: scala.xml.Node): NodeSeq = in match { case g: Group => g.nodes.flatMap(mf) case e: Elem => e % toFormAppendedAttributes case other => other @@ -690,7 +690,7 @@ trait MappedField[FieldType <: Any,OwnerType <: Mapper[OwnerType]] extends Typed case _ => false } - override def asHtml : Node = Text(toString) + override def asHtml: scala.xml.Node = Text(toString) } object MappedField { diff --git a/persistence/mapper/src/main/scala/net/liftweb/mapper/MetaMapper.scala b/persistence/mapper/src/main/scala/net/liftweb/mapper/MetaMapper.scala index 7ef0a1981d..405c2b2e25 100644 --- a/persistence/mapper/src/main/scala/net/liftweb/mapper/MetaMapper.scala +++ b/persistence/mapper/src/main/scala/net/liftweb/mapper/MetaMapper.scala @@ -1785,17 +1785,25 @@ trait KeyedMetaMapper[Type, A<:KeyedMapper[Type, A]] extends MetaMapper[A] with def asSafeJs(actual: A, f: KeyObfuscator): JsExp = { val pk = actual.primaryKeyField val first = (pk.name, JE.Str(f.obscure(self, pk.is))) - JE.JsObj(first :: ("$lift_class", JE.Str(dbTableName)) :: mappedFieldList. - map(f => this.??(f.method, actual)). - filter(f => !f.dbPrimaryKey_? && f.renderJs_?).flatMap{ - case fk: Q => - val key = f.obscure(fk.dbKeyToTable, fk.is) - List((fk.name, JE.Str(key)), - (fk.name+"_obj", - JE.AnonFunc("index", JE.JsRaw("return index["+key.encJs+"];").cmd))) - - case x => x.asJs}.toList ::: - actual.suplementalJs(Full(f)) :_*) + JE.JsObj( + first :: + ("$lift_class", JE.Str(dbTableName)) :: + mappedFieldList + .map(f => this.??(f.method, actual)) + .filter(f => !f.dbPrimaryKey_? && f.renderJs_?) + .flatMap{ + case fk0: MappedForeignKey[_, _, _] with MappedField[_, _] => + val fk = fk0.asInstanceOf[Q] + val key = f.obscure(fk.dbKeyToTable, fk.is) + List( + (fk.name, JE.Str(key)), + (fk.name+"_obj", JE.AnonFunc("index", JE.JsRaw("return index["+key.encJs+"];").cmd)) + ) + case x => x.asJs + } + .toList ::: + actual.suplementalJs(Full(f)) : _* + ) } private def convertToQPList(prod: Product): Array[QueryParam[A]] = { diff --git a/persistence/mapper/src/main/scala/net/liftweb/mapper/OneToMany.scala b/persistence/mapper/src/main/scala/net/liftweb/mapper/OneToMany.scala index e430df79e4..0387ec97d8 100644 --- a/persistence/mapper/src/main/scala/net/liftweb/mapper/OneToMany.scala +++ b/persistence/mapper/src/main/scala/net/liftweb/mapper/OneToMany.scala @@ -18,6 +18,12 @@ package net.liftweb package mapper +private[mapper] object RecursiveType { + val rec: { type R0 <: Mapper[R0] } = null + type Rec = rec.R0 +} +import RecursiveType._ + /** * Add this trait to a Mapper for managed one-to-many support * For example: class Contact extends LongKeyedMapper[Contact] with OneToMany[Long, Contact] { ... } @@ -26,13 +32,14 @@ package mapper * @author nafg */ trait OneToMany[K,T<:KeyedMapper[K, T]] extends KeyedMapper[K,T] { this: T => - private[mapper] lazy val oneToManyFields: List[MappedOneToManyBase[_]] = { - new FieldFinder[MappedOneToManyBase[_]]( + + private[mapper] lazy val oneToManyFields: List[MappedOneToManyBase[Rec]] = { + new FieldFinder[MappedOneToManyBase[Rec]]( getSingleton, net.liftweb.common.Logger(classOf[OneToMany[K,T]]) - ).accessorMethods map (_.invoke(this).asInstanceOf[MappedOneToManyBase[_]]) + ).accessorMethods map (_.invoke(this).asInstanceOf[MappedOneToManyBase[Rec]]) } - + /** * An override for save to propagate the save to all children * of this parent. @@ -52,9 +59,10 @@ trait OneToMany[K,T<:KeyedMapper[K, T]] extends KeyedMapper[K,T] { this: T => * If they are all successful returns true. */ override def delete_! = DB.use(connectionIdentifier){_ => - if(oneToManyFields.forall{ - case f: Cascade[_] => f.delete_! - case _ => true + if(oneToManyFields.forall{(_: MappedOneToManyBase[_ <: Mapper[_]]) match { + case f: Cascade[_] => f.delete_! + case _ => true + } }) super.delete_! else { @@ -97,7 +105,7 @@ trait OneToMany[K,T<:KeyedMapper[K, T]] extends KeyedMapper[K,T] { this: T => * @param foreign A function that gets the MappedForeignKey on the child that refers to this parent */ class MappedOneToManyBase[O <: Mapper[_]](val reloadFunc: ()=>Seq[O], - val foreign: O => MappedForeignKey[K,_,T]) extends scala.collection.mutable.Buffer[O] { + val foreign: O => MappedForeignKey[K,_,T] /*forSome { type X <: Mapper[X] }*/) extends scala.collection.mutable.Buffer[O] { private var inited = false private var _delegate: List[O] = _ /** @@ -117,10 +125,11 @@ trait OneToMany[K,T<:KeyedMapper[K, T]] extends KeyedMapper[K,T] { this: T => * Takes ownership of e. Sets e's foreign key to our primary key */ protected def own(e: O) = { - foreign(e) match { - case f: MappedLongForeignKey[O,T] with MappedForeignKey[_,_,T] => + val f0 = foreign(e).asInstanceOf[Any] + f0 match { + case f: MappedLongForeignKey[O,T] with MappedForeignKey[K,_,T] => f.apply(OneToMany.this) - case f => + case f: MappedForeignKey[K,_,T] => f.set(OneToMany.this.primaryKeyField.get) } if(!OneToMany.this.saved_?) diff --git a/persistence/mapper/src/main/scala/net/liftweb/mapper/view/ModelView.scala b/persistence/mapper/src/main/scala/net/liftweb/mapper/view/ModelView.scala index 8bee93dbba..14d67f4a7e 100644 --- a/persistence/mapper/src/main/scala/net/liftweb/mapper/view/ModelView.scala +++ b/persistence/mapper/src/main/scala/net/liftweb/mapper/view/ModelView.scala @@ -64,7 +64,7 @@ trait ModelSnippet[T <: Mapper[T]] extends StatefulSnippet { def load(entity: T) = view.entity = entity - def dispatch = { + def dispatch: DispatchIt = { case "list" => list _ case "edit" => edit _ case "newOrEdit" => view.newOrEdit _ From 985a97594db16e6e33f596538af02ab8a4c1331d Mon Sep 17 00:00:00 2001 From: Naftoli Gugenheim Date: Sun, 30 Sep 2012 16:37:24 -0400 Subject: [PATCH 0294/1949] 2.10: lift-record --- .../src/main/scala/net/liftweb/record/field/EnumField.scala | 2 +- .../src/main/scala/net/liftweb/record/field/EnumNameField.scala | 2 +- .../record/src/test/scala/net/liftweb/record/FieldSpec.scala | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/EnumField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/EnumField.scala index 02a009e924..74a5199f3e 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/EnumField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/EnumField.scala @@ -70,7 +70,7 @@ trait EnumTypedField[EnumType <: Enumeration] extends TypedField[EnumType#Value] * is the value of the field and the second string is the Text name of the Value. */ def buildDisplayList: List[(Box[EnumType#Value], String)] = { - val options = enum.values.map(a => (Full(a), a.toString)).toList + val options = enum.values.toList.map(a => (Full(a), a.toString)) if (optional_?) (Empty, emptyOptionLabel)::options else options } diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/EnumNameField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/EnumNameField.scala index 4b6b4f9a90..fa78bf5bb9 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/EnumNameField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/EnumNameField.scala @@ -52,7 +52,7 @@ trait EnumNameTypedField[EnumType <: Enumeration] extends TypedField[EnumType#Va * is the value of the field and the second string is the Text name of the Value. */ def buildDisplayList: List[(Box[EnumType#Value], String)] = { - val options = enum.values.map(a => (Full(a), a.toString)).toList + val options = enum.values.toList.map(a => (Full(a), a.toString)) if (optional_?) (Empty, emptyOptionLabel)::options else options } diff --git a/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala b/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala index e356508d5d..3869fc30bb 100644 --- a/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala +++ b/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala @@ -245,6 +245,7 @@ object FieldSpec extends Specification { optional.valueBox must_== Empty } } + success } def passConversionTests[A](example: A, mandatory: MandatoryTypedField[A], jsexp: JsExp, jvalue: JValue, formPattern: Box[NodeSeq]): Fragment = { From a137d7a4675e3b488f6b32087fc73c75d8dd2590 Mon Sep 17 00:00:00 2001 From: Naftoli Gugenheim Date: Fri, 23 Nov 2012 04:40:13 -0500 Subject: [PATCH 0295/1949] 2.10: lift-squeryl-record --- .../squerylrecord/RecordMetaDataFactory.scala | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/persistence/squeryl-record/src/main/scala/net/liftweb/squerylrecord/RecordMetaDataFactory.scala b/persistence/squeryl-record/src/main/scala/net/liftweb/squerylrecord/RecordMetaDataFactory.scala index b5e87a34c3..f8fb73650b 100644 --- a/persistence/squeryl-record/src/main/scala/net/liftweb/squerylrecord/RecordMetaDataFactory.scala +++ b/persistence/squeryl-record/src/main/scala/net/liftweb/squerylrecord/RecordMetaDataFactory.scala @@ -29,13 +29,17 @@ import net.liftweb.record.OptionalTypedField /** FieldMetaDataFactory that allows Squeryl to use Records as model objects. */ class RecordMetaDataFactory extends FieldMetaDataFactory { + private val rec: { type R0 <: Record[R0] } = null + private type Rec = rec.R0 + /** Cache MetaRecords by the model object class (Record class) */ - private var metaRecordsByClass: Map[Class[_], MetaRecord[_]] = Map.empty + private var metaRecordsByClass: Map[Class[Rec], MetaRecord[Rec]] = Map.empty + /** Given a model object class (Record class) and field name, return the BaseField from the meta record */ - private def findMetaField(clasz: Class[_], name: String): BaseField = { - def fieldFrom(mr: MetaRecord[_]): BaseField = - mr.asInstanceOf[Record[_]].fieldByName(name) match { + private def findMetaField(clasz: Class[Rec], name: String): BaseField = { + def fieldFrom(mr: MetaRecord[Rec]): BaseField = + mr.asInstanceOf[Record[Rec]].fieldByName(name) match { case Full(f: BaseField) => f case Full(_) => org.squeryl.internals.Utils.throwError("field " + name + " in Record metadata for " + clasz + " is not a TypedField") case _ => org.squeryl.internals.Utils.throwError("failed to find field " + name + " in Record metadata for " + clasz) @@ -45,12 +49,12 @@ class RecordMetaDataFactory extends FieldMetaDataFactory { case Some(mr) => fieldFrom(mr) case None => try { - val rec = clasz.newInstance.asInstanceOf[Record[_]] + val rec = clasz.newInstance.asInstanceOf[Record[Rec]] val mr = rec.meta metaRecordsByClass = metaRecordsByClass updated (clasz, mr) fieldFrom(mr) } catch { - case ex => org.squeryl.internals.Utils.throwError("failed to find MetaRecord for " + clasz + " due to exception " + ex.toString) + case ex: Exception => org.squeryl.internals.Utils.throwError("failed to find MetaRecord for " + clasz + " due to exception " + ex.toString) } } } @@ -60,14 +64,14 @@ class RecordMetaDataFactory extends FieldMetaDataFactory { property: (Option[Field], Option[Method], Option[Method], Set[Annotation]), sampleInstance4OptionTypeDeduction: AnyRef, isOptimisticCounter: Boolean): FieldMetaData = { if (!isRecord(parentMetaData.clasz) || isOptimisticCounter) { - // Either this is not a Record class, in which case we'll + // Either this is not a Record class, in which case we'll //treat it as a normal class in primitive type mode, or the field //was mixed in by the Optimisitic trait and is not a Record field. return SquerylRecord.posoMetaDataFactory.build(parentMetaData, name, property, sampleInstance4OptionTypeDeduction, isOptimisticCounter) } - val metaField = findMetaField(parentMetaData.clasz, name) + val metaField = findMetaField(parentMetaData.clasz.asInstanceOf[Class[Rec]], name) val (field, getter, setter, annotations) = property From 61f296172cb50bf00807d3caeea843427afb605f Mon Sep 17 00:00:00 2001 From: Dave Briccetti Date: Wed, 9 Jan 2013 17:28:45 -0800 Subject: [PATCH 0296/1949] Make some minor comment improvements to Menu.scala. --- web/webkit/src/main/scala/net/liftweb/sitemap/Menu.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/sitemap/Menu.scala b/web/webkit/src/main/scala/net/liftweb/sitemap/Menu.scala index 83f9493627..3e813687db 100644 --- a/web/webkit/src/main/scala/net/liftweb/sitemap/Menu.scala +++ b/web/webkit/src/main/scala/net/liftweb/sitemap/Menu.scala @@ -217,7 +217,7 @@ object Menu extends MenuSingleton { /** * An intermediate class that holds the basic stuff that's needed to make a Menu item for SiteMap. - * You must include at least one URI path element by calling the / method + * You must include at least one URI path element by calling the / method. */ class PreParamsMenu[T<:AnyRef](name: String, linkText: Loc.LinkText[T], parser: List[String] => Box[T], @@ -335,7 +335,7 @@ object Menu extends MenuSingleton { /** * An intermediate class that holds the basic stuff that's needed to make a Menu item for SiteMap. - * You must include at least one URI path element by calling the / method + * You must include at least one URI path element by calling the / method. */ class PreMenu(name: String, linkText: Loc.LinkText[Unit]) { /** @@ -554,7 +554,7 @@ sealed trait MenuSingleton { def apply(name: String,linkText: Loc.LinkText[Unit]): PreMenu = new PreMenu(name, linkText) /** - * A convenient way to define a Menu items that's got the same name as it does it's localized LinkText. + * A convenient way to define a Menu item that has the same name as its localized LinkText. *
    Menu.i("Home") / "index"
    is short-hand for
    Menu("Home", S.loc("Home", Text("Home")) / "index"
    */ def i(nameAndLink: String): PreMenu = Menu.apply(nameAndLink, S.loc(nameAndLink, scala.xml.Text(nameAndLink))) From cbd65ce85539f7376266bdaae74da7a3f44c3eda Mon Sep 17 00:00:00 2001 From: Jakub Czuchnowski Date: Sun, 6 Jan 2013 23:19:37 +0100 Subject: [PATCH 0297/1949] Updated contributors file --- contributors.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/contributors.md b/contributors.md index d5940ca270..317f6e00a5 100644 --- a/contributors.md +++ b/contributors.md @@ -76,4 +76,10 @@ gregmflanagan at gmail dot com Ali Salim Rashid ### Email: ### -a.rashid at zantekk dot com \ No newline at end of file +a.rashid at zantekk dot com + +### Name: ### +Jakub Czuchnowski + +### Email: ### +jakub.czuchnowski at gmail dot com \ No newline at end of file From cbada718b9d402f56de56bbd7a526e4cf741ce28 Mon Sep 17 00:00:00 2001 From: Jakub Czuchnowski Date: Sun, 6 Jan 2013 23:21:29 +0100 Subject: [PATCH 0298/1949] Fixed Issue #1384 - provide a way to override input field's type attribute --- .../src/main/scala/net/liftweb/record/Field.scala | 5 +++++ .../net/liftweb/record/field/DateTimeField.scala | 2 +- .../net/liftweb/record/field/NumericField.scala | 2 +- .../net/liftweb/record/field/PasswordField.scala | 4 +++- .../scala/net/liftweb/record/field/StringField.scala | 2 +- .../test/scala/net/liftweb/record/FieldSpec.scala | 12 ++++++++++++ .../src/test/scala/net/liftweb/record/Fixtures.scala | 12 ++++++++++++ 7 files changed, 35 insertions(+), 4 deletions(-) diff --git a/persistence/record/src/main/scala/net/liftweb/record/Field.scala b/persistence/record/src/main/scala/net/liftweb/record/Field.scala index 0d01a29b6e..f27d64f73f 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/Field.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/Field.scala @@ -121,6 +121,11 @@ trait BaseField extends FieldIdentifier with util.BaseField { */ def notOptionalErrorMessage : String = "Value required" + /** + * Form field's type. + * Defaults to 'text', but you may want to change it to other HTML5 values. + */ + def formInputType = "text" def tabIndex: Int = 1 diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/DateTimeField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/DateTimeField.scala index 3e4ca30672..233cf3ae41 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/DateTimeField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/DateTimeField.scala @@ -50,7 +50,7 @@ trait DateTimeTypedField extends TypedField[Calendar] { private def elem = S.fmapFunc(SFuncHolder(this.setFromAny(_))){funcName => - toInternetDate(s.getTime)) openOr ""} tabindex={tabIndex toString}/> diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/NumericField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/NumericField.scala index 5c6868932c..1b6319a95e 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/NumericField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/NumericField.scala @@ -41,7 +41,7 @@ trait NumericTypedField[MyType] extends TypedField[MyType] { } private def elem = S.fmapFunc((s: List[String]) => setFromAny(s)) { - funcName => + funcName => } /** diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/PasswordField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/PasswordField.scala index cd74b9e658..34e87cf790 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/PasswordField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/PasswordField.scala @@ -89,8 +89,10 @@ trait PasswordTypedField extends TypedField[String] { override def notOptionalErrorMessage = S.?("password.must.be.set") + override def formInputType = "password" + private def elem = S.fmapFunc(SFuncHolder(this.setFromAny(_))){ - funcName => } diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/StringField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/StringField.scala index 324eef2a0f..fcc58162eb 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/StringField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/StringField.scala @@ -48,7 +48,7 @@ trait StringTypedField extends TypedField[String] with StringValidators { private def elem = S.fmapFunc(SFuncHolder(this.setFromAny(_))) { funcName => - diff --git a/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala b/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala index 3869fc30bb..1c54adb8c8 100644 --- a/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala +++ b/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala @@ -469,6 +469,18 @@ object FieldSpec extends Specification { Full() ) } + + "IntField with custom HTML5 type" should { + val rec = CustomTypeIntFieldRecord.createRecord + val num = 123 + passConversionTests( + num, + rec.customIntField, + JsRaw(num.toString), + JInt(num), + Full() + ) + } "LocaleField" should { val rec = FieldTypeTestRecord.createRecord diff --git a/persistence/record/src/test/scala/net/liftweb/record/Fixtures.scala b/persistence/record/src/test/scala/net/liftweb/record/Fixtures.scala index 8ad5d9d670..8de42ac825 100644 --- a/persistence/record/src/test/scala/net/liftweb/record/Fixtures.scala +++ b/persistence/record/src/test/scala/net/liftweb/record/Fixtures.scala @@ -260,3 +260,15 @@ class CustomFormatDateTimeRecord private () extends Record[CustomFormatDateTimeR } object CustomFormatDateTimeRecord extends CustomFormatDateTimeRecord with MetaRecord[CustomFormatDateTimeRecord] + +class CustomTypeIntFieldRecord private () extends Record[CustomTypeIntFieldRecord] { + + def meta = CustomTypeIntFieldRecord + + object customIntField extends IntField(this) { + override def formInputType = "number" + } + +} + +object CustomTypeIntFieldRecord extends CustomTypeIntFieldRecord with MetaRecord[CustomTypeIntFieldRecord] From bc6c466dd92316723a9338da1fa86c695e7e5a99 Mon Sep 17 00:00:00 2001 From: Russel Date: Sat, 5 Jan 2013 13:09:05 +0100 Subject: [PATCH 0299/1949] MongoListField handles list of Array[Byte] --- .../net/liftweb/mongodb/record/MongoRecordExamplesSpec.scala | 4 ++++ .../mongodb/src/main/scala/net/liftweb/mongodb/Meta.scala | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordExamplesSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordExamplesSpec.scala index b37590664e..db6c253c56 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordExamplesSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordExamplesSpec.scala @@ -156,6 +156,7 @@ package mongotestrecords { object objidlist extends MongoListField[ListDoc, ObjectId](this) object dtlist extends MongoListField[ListDoc, Date](this) object patternlist extends MongoListField[ListDoc, Pattern](this) + object binarylist extends MongoListField[ListDoc, Array[Byte]](this) // specialized list types object jsonobjlist extends MongoJsonObjectListField(this, JsonDoc) @@ -489,6 +490,7 @@ class MongoRecordExamplesSpec extends Specification with MongoTestKit { ld1.jsonobjlist.set(List(jd1, JsonDoc("2", "jsondoc2"), jd1)) ld1.patternlist.set(List(Pattern.compile("^Mongo"), Pattern.compile("^Mongo2"))) ld1.maplist.set(List(Map("name" -> "map1", "type" -> "map"), Map("name" -> "map2", "type" -> "map"))) + ld1.binarylist.set(List[Array[Byte]]("foo".getBytes(), "bar".getBytes())) ld1.save must_== ld1 @@ -513,6 +515,8 @@ class MongoRecordExamplesSpec extends Specification with MongoTestKit { l.jsonobjlist.value(i).id must_== ld1.jsonobjlist.value(i).id l.jsonobjlist.value(i).name must_== ld1.jsonobjlist.value(i).name } + //using view allows to transform to Seq to make a deep equals + l.binarylist.value.view must_== ld1.binarylist.value.view } if (!debug) { diff --git a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/Meta.scala b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/Meta.scala index d0c46326ba..0cc67df840 100644 --- a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/Meta.scala +++ b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/Meta.scala @@ -41,7 +41,7 @@ private[mongodb] object Meta { classOf[Short], classOf[java.lang.Integer], classOf[java.lang.Long], classOf[java.lang.Double], classOf[java.lang.Float], classOf[java.lang.Byte], classOf[java.lang.Boolean], - classOf[java.lang.Short]) + classOf[java.lang.Short], classOf[scala.Array[Byte]]) def primitive_?(clazz: Class[_]) = primitives contains clazz From a5ccea17f26a85cdfea2bbbc9620688a7b66bafe Mon Sep 17 00:00:00 2001 From: Russel Date: Sat, 5 Jan 2013 13:12:48 +0100 Subject: [PATCH 0300/1949] Follow contribution rules --- contributors.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/contributors.md b/contributors.md index 317f6e00a5..c1a60873b0 100644 --- a/contributors.md +++ b/contributors.md @@ -82,4 +82,10 @@ a.rashid at zantekk dot com Jakub Czuchnowski ### Email: ### -jakub.czuchnowski at gmail dot com \ No newline at end of file +jakub.czuchnowski at gmail dot com + +## Name: ### +Alexandre Russel + +### Email: ### +alexandre at russel dot fr From df40d43dfab11b2e66d40a4a96a38f3d0f4b52a9 Mon Sep 17 00:00:00 2001 From: Ali Salim Rashid Date: Fri, 14 Dec 2012 15:42:30 +0300 Subject: [PATCH 0301/1949] Remove lift-couchdb from framework. It is now a lift module. --- contributors.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/contributors.md b/contributors.md index c1a60873b0..574acbd670 100644 --- a/contributors.md +++ b/contributors.md @@ -89,3 +89,9 @@ Alexandre Russel ### Email: ### alexandre at russel dot fr + +### Name: ### +Ali Salim Rashid + +### Email: ### +a.rashid at zantekk dot com From 9f3e18769d358b74d5586b05e215afe866263770 Mon Sep 17 00:00:00 2001 From: Kristof Jozsa Date: Sat, 5 Jan 2013 10:50:22 +0100 Subject: [PATCH 0302/1949] Fix issue #1388 - Allow Paginator to send correct page links on ajax --- contributors.md | 6 ++++++ web/webkit/src/main/scala/net/liftweb/http/Paginator.scala | 6 +++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/contributors.md b/contributors.md index 574acbd670..10942a1c5f 100644 --- a/contributors.md +++ b/contributors.md @@ -95,3 +95,9 @@ Ali Salim Rashid ### Email: ### a.rashid at zantekk dot com + +### Name: ### +Kristof Jozsa + +### Email: ### +kristof.jozsa at gmail dot com diff --git a/web/webkit/src/main/scala/net/liftweb/http/Paginator.scala b/web/webkit/src/main/scala/net/liftweb/http/Paginator.scala index a3ed43bad7..c284bd0ef7 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Paginator.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Paginator.scala @@ -187,10 +187,14 @@ trait PaginatorSnippet[T] extends Paginator[T] { * Sets the default starting record of the page (URL query parameters take precedence over this) */ def first_=(f: Long) = _first = f max 0 min (count-1) + /** + * Set base URI for pagination when snippet loads + */ + val paginationBaseUri = S.uri /** * Returns a URL used to link to a page starting at the given record offset. */ - def pageUrl(offset: Long): String = appendParams(S.uri, List(offsetParam -> offset.toString)) + def pageUrl(offset: Long): String = appendParams(paginationBaseUri, List(offsetParam -> offset.toString)) /** * Returns XML that links to a page starting at the given record offset, if the offset is valid and not the current one. * @param ns The link text, if the offset is valid and not the current offset; or, if that is not the case, the static unlinked text to display From 903c684251932ef867136c084f6c92d904b9b7d4 Mon Sep 17 00:00:00 2001 From: Kristof Jozsa Date: Tue, 8 Jan 2013 10:04:49 +0100 Subject: [PATCH 0303/1949] Revert "Fix issue #1388 - Allow Paginator to send correct page links on ajax" This reverts commit 8c269034b59ba54ca5fd480a6bdac630a6920b4d. --- contributors.md | 8 +------- .../src/main/scala/net/liftweb/http/Paginator.scala | 6 +----- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/contributors.md b/contributors.md index 10942a1c5f..e96eb19f20 100644 --- a/contributors.md +++ b/contributors.md @@ -94,10 +94,4 @@ alexandre at russel dot fr Ali Salim Rashid ### Email: ### -a.rashid at zantekk dot com - -### Name: ### -Kristof Jozsa - -### Email: ### -kristof.jozsa at gmail dot com +a.rashid at zantekk dot com \ No newline at end of file diff --git a/web/webkit/src/main/scala/net/liftweb/http/Paginator.scala b/web/webkit/src/main/scala/net/liftweb/http/Paginator.scala index c284bd0ef7..a3ed43bad7 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Paginator.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Paginator.scala @@ -187,14 +187,10 @@ trait PaginatorSnippet[T] extends Paginator[T] { * Sets the default starting record of the page (URL query parameters take precedence over this) */ def first_=(f: Long) = _first = f max 0 min (count-1) - /** - * Set base URI for pagination when snippet loads - */ - val paginationBaseUri = S.uri /** * Returns a URL used to link to a page starting at the given record offset. */ - def pageUrl(offset: Long): String = appendParams(paginationBaseUri, List(offsetParam -> offset.toString)) + def pageUrl(offset: Long): String = appendParams(S.uri, List(offsetParam -> offset.toString)) /** * Returns XML that links to a page starting at the given record offset, if the offset is valid and not the current one. * @param ns The link text, if the offset is valid and not the current offset; or, if that is not the case, the static unlinked text to display From c7e320947680c9cc8ecb1e98e450c390d0e788d0 Mon Sep 17 00:00:00 2001 From: Kristof Jozsa Date: Wed, 9 Jan 2013 19:56:42 +0100 Subject: [PATCH 0304/1949] Fix #1388 - Allow Paginator to send correct page links on ajax --- contributors.md | 8 +++++++- .../src/main/scala/net/liftweb/http/Paginator.scala | 5 ++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/contributors.md b/contributors.md index e96eb19f20..a4f62bdcea 100644 --- a/contributors.md +++ b/contributors.md @@ -94,4 +94,10 @@ alexandre at russel dot fr Ali Salim Rashid ### Email: ### -a.rashid at zantekk dot com \ No newline at end of file +a.rashid at zantekk dot com + +### Name: ### +Kristof Jozsa + +### Email: ### +kristof.jozsa at gmail dot com \ No newline at end of file diff --git a/web/webkit/src/main/scala/net/liftweb/http/Paginator.scala b/web/webkit/src/main/scala/net/liftweb/http/Paginator.scala index a3ed43bad7..09bdfb3f64 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Paginator.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Paginator.scala @@ -190,7 +190,10 @@ trait PaginatorSnippet[T] extends Paginator[T] { /** * Returns a URL used to link to a page starting at the given record offset. */ - def pageUrl(offset: Long): String = appendParams(S.uri, List(offsetParam -> offset.toString)) + def pageUrl(offset: Long): String = { + def originalUri = S.originalRequest.map(_.uri).openOr(sys.error("No request")) + appendParams(originalUri, List(offsetParam -> offset.toString)) + } /** * Returns XML that links to a page starting at the given record offset, if the offset is valid and not the current one. * @param ns The link text, if the offset is valid and not the current offset; or, if that is not the case, the static unlinked text to display From 4f038e951c31d7e7ea24a8944c5a12cf67dc7923 Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Wed, 9 Jan 2013 20:37:42 -0500 Subject: [PATCH 0305/1949] Fixed failing test in MongoRecordExamplesSpec --- .../liftweb/mongodb/record/MongoRecordExamplesSpec.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordExamplesSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordExamplesSpec.scala index db6c253c56..341858bfca 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordExamplesSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordExamplesSpec.scala @@ -515,8 +515,10 @@ class MongoRecordExamplesSpec extends Specification with MongoTestKit { l.jsonobjlist.value(i).id must_== ld1.jsonobjlist.value(i).id l.jsonobjlist.value(i).name must_== ld1.jsonobjlist.value(i).name } - //using view allows to transform to Seq to make a deep equals - l.binarylist.value.view must_== ld1.binarylist.value.view + for { + orig <- ld1.binarylist.value.headOption + queried <- l.binarylist.value.headOption + } new String(orig) must_== new String(queried) } if (!debug) { From 52274c0f2a36cf6b43f77eb851b66dc1c34b7538 Mon Sep 17 00:00:00 2001 From: Naftoli Gugenheim Date: Wed, 9 Jan 2013 02:17:21 -0500 Subject: [PATCH 0306/1949] @{o->p}aram --- web/webkit/src/main/scala/net/liftweb/http/SHtml.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala b/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala index a7d629a08f..bbea56e096 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala @@ -1651,14 +1651,14 @@ trait SHtml { * the request will be processed by the jsonHandler * * @param jsonHandler - the handler that process this request - * @oaram formId - the id of the form + * @param formId - the id of the form */ def submitJsonForm(jsonHandler: JsonHandler, formId: String):JsCmd = jsonHandler.call("processForm", FormToJSON(formId)) /** * Having a regular form, this method can be used to send the serialized content of the form. * - * @oaram formId - the id of the form + * @param formId - the id of the form */ def submitAjaxForm(formId: String):JsCmd = SHtml.makeAjaxCall(LiftRules.jsArtifacts.serialize(formId)) @@ -1710,7 +1710,7 @@ trait SHtml { /** * Having a regular form, this method can be used to send the serialized content of the form. * - * @oaram formId - the id of the form + * @param formId - the id of the form * @param postSubmit - the function that needs to be called after a successfull request */ def submitAjaxForm(formId: String, postSubmit: Call):JsCmd = From c27ac10842dabeeb04ef4e1d21596da3de3c1f85 Mon Sep 17 00:00:00 2001 From: Naftoli Gugenheim Date: Fri, 11 Jan 2013 03:59:53 -0500 Subject: [PATCH 0307/1949] Fix more scaladoc attribute typos --- web/webkit/src/main/scala/net/liftweb/http/SHtml.scala | 4 ++-- .../src/main/scala/net/liftweb/http/rest/RestHelper.scala | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala b/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala index bbea56e096..5165903f0a 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala @@ -1894,7 +1894,7 @@ trait SHtml { * For use with DHTML form updating. * * @param opts -- the options. A list of value and text pairs - * @poram jsFunc -- user provided function + * @param jsFunc -- user provided function * @param deflt -- the default value (or Empty if no default value) * @param func -- the function to execute on form submission * @param attrs -- select box attributes @@ -1913,7 +1913,7 @@ trait SHtml { * * @param opts -- the options. A list of value and text pairs * @param deflt -- the default value (or Empty if no default value) - * @poram jsFunc -- user provided function + * @param jsFunc -- user provided function * @param func -- the function to execute on form submission * @param attrs -- select box attributes */ diff --git a/web/webkit/src/main/scala/net/liftweb/http/rest/RestHelper.scala b/web/webkit/src/main/scala/net/liftweb/http/rest/RestHelper.scala index 874832e2e3..ff7c638c7a 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/rest/RestHelper.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/rest/RestHelper.scala @@ -319,7 +319,7 @@ trait RestHelper extends LiftRules.DispatchPF { * type (e.g., JSON or XML).

    * @param selection -- a function that determines the response type * based on the Req. - * @parama pf -- a PartialFunction that converts the request to a + * @param pf -- a PartialFunction that converts the request to a * response type (e.g., a case class that contains the response). * @param cvt -- a function that converts from the response type * to a the appropriate LiftResponse based on the selected response @@ -352,7 +352,7 @@ trait RestHelper extends LiftRules.DispatchPF { /** * Serve a request returning either JSON or XML. * - * @parama pf -- a Partial Function that converts the request into + * @param pf -- a Partial Function that converts the request into * an intermediate response. * @param cvt -- convert the intermediate response to a LiftResponse * based on the request being for XML or JSON. If T is JsonXmlAble, From ccd3fbc46a8966811657b34ac4d4b33001d5ba6a Mon Sep 17 00:00:00 2001 From: Tim Nelson Date: Thu, 10 Jan 2013 12:58:20 -0600 Subject: [PATCH 0308/1949] Remove custom equals methods in mongo-record test fixtures --- .../liftweb/mongodb/record/BsonRecord.scala | 19 +++- .../mongodb/record/field/MongoListField.scala | 15 +-- .../mongodb/record/field/MongoMapField.scala | 5 +- .../mongodb/record/BsonRecordSpec.scala | 11 +- .../net/liftweb/mongodb/record/Fixtures.scala | 62 +--------- .../mongodb/record/MongoFieldSpec.scala | 103 +++++++++-------- .../mongodb/record/MongoRecordSpec.scala | 106 +++++++----------- .../scala/net/liftweb/record/Record.scala | 4 +- 8 files changed, 142 insertions(+), 183 deletions(-) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/BsonRecord.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/BsonRecord.scala index 2cf2045eaa..e9e73ed4f6 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/BsonRecord.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/BsonRecord.scala @@ -1,5 +1,5 @@ /* - * Copyright 2011-2012 WorldWide Conferencing, LLC + * Copyright 2011-2013 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. @@ -20,6 +20,7 @@ package record import common._ +import java.util.regex.Pattern import scala.collection.JavaConversions._ import net.liftweb.record.{Field, MetaRecord, Record} @@ -49,6 +50,22 @@ trait BsonRecord[MyType <: BsonRecord[MyType]] extends Record[MyType] { * Save the instance and return the instance */ override def saveTheRecord(): Box[MyType] = throw new BackingStoreException("BSON Records don't save themselves") + + /** + * Pattern.equals doesn't work properly so it needs a special check. If you use PatternField, be sure to override equals with this. + */ + protected def equalsWithPatternCheck(other: Any): Boolean = { + other match { + case that: BsonRecord[MyType] => + that.fields.corresponds(this.fields) { (a,b) => + (a.name == b.name) && ((a.valueBox, b.valueBox) match { + case (Full(ap: Pattern), Full(bp: Pattern)) => ap.pattern == bp.pattern && ap.flags == bp.flags + case _ => a.valueBox == b.valueBox + }) + } + case _ => false + } + } } /** Specialized MetaRecord that deals with BsonRecords */ diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoListField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoListField.scala index dbeabd5665..92c37199c7 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoListField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoListField.scala @@ -1,5 +1,5 @@ /* - * Copyright 2010-2012 WorldWide Conferencing, LLC + * Copyright 2010-2013 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. @@ -36,9 +36,11 @@ import com.mongodb._ import org.bson.types.ObjectId /** -* List field. Compatible with most object types, -* including Pattern, ObjectId, Date, and UUID. -*/ + * List field. Compatible with most object types, + * including Pattern, ObjectId, Date, and UUID. + * + * Note: setting optional_? = false will result in incorrect equals behavior when using setFromJValue + */ class MongoListField[OwnerType <: BsonRecord[OwnerType], ListType: Manifest](rec: OwnerType) extends Field[List[ListType], OwnerType] with MandatoryTypedField[List[ListType]] @@ -82,11 +84,6 @@ class MongoListField[OwnerType <: BsonRecord[OwnerType], ListType: Manifest](rec case other => setBox(Failure("Error parsing String into a JValue: "+in)) } - /* - * MongoListField is built on MandatoryField, so optional_? is always false. It would be nice to use optional to differentiate - * between a list that requires at least one item and a list that can be empty. - */ - /** Options for select list **/ def options: List[(ListType, String)] = Nil diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoMapField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoMapField.scala index 9f567dbee6..edd6362c53 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoMapField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoMapField.scala @@ -1,5 +1,5 @@ /* - * Copyright 2010-2011 WorldWide Conferencing, LLC + * Copyright 2010-2013 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. @@ -30,6 +30,9 @@ import net.liftweb.util.Helpers.tryo import com.mongodb._ +/** + * Note: setting optional_? = false will result in incorrect equals behavior when using setFromJValue + */ class MongoMapField[OwnerType <: BsonRecord[OwnerType], MapValueType](rec: OwnerType) extends Field[Map[String, MapValueType], OwnerType] with MandatoryTypedField[Map[String, MapValueType]] with MongoFieldFlavor[Map[String, MapValueType]] { diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/BsonRecordSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/BsonRecordSpec.scala index 88e67f8ecc..e181e6d6b6 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/BsonRecordSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/BsonRecordSpec.scala @@ -1,5 +1,5 @@ /* - * Copyright 2010-2012 WorldWide Conferencing, LLC + * Copyright 2010-2013 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. @@ -30,7 +30,7 @@ class BsonRecordSpec extends Specification with MongoTestKit { } "BsonRecord" should { - "compare properly" in { + "compare properly with set values" in { val subRec = SubSubRecord.createRecord.name("subrecord") val subRec2 = SubSubRecord.createRecord.name("subrecord") @@ -42,5 +42,12 @@ class BsonRecordSpec extends Specification with MongoTestKit { (subRec == subRec2) must_== false } + + "compare properly with default values" in { + val subRec = SubSubRecord.createRecord + val subRec2 = SubSubRecord.createRecord + + (subRec == subRec2) must_== true + } } } diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala index e7450b1a35..eda4e1df10 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala @@ -1,5 +1,5 @@ /* - * Copyright 2010-2012 WorldWide Conferencing, LLC + * Copyright 2010-2013 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. @@ -21,7 +21,7 @@ package fixtures import field._ -import common.{Box, Empty, Failure, Full} +import common._ import json._ import json.ext.{EnumSerializer, JsonBoxSerializer} import http.SHtml @@ -197,7 +197,7 @@ case class TypeTestJsonObject( } object TypeTestJsonObject extends JsonObjectMeta[TypeTestJsonObject] -class DBRefTestRecord private () extends MongoRecord[DBRefTestRecord] with MongoId[DBRefTestRecord] { +class DBRefTestRecord private () extends MongoRecord[DBRefTestRecord] with ObjectIdPk[DBRefTestRecord] { def meta = DBRefTestRecord } object DBRefTestRecord extends DBRefTestRecord with MongoMetaRecord[DBRefTestRecord] @@ -241,22 +241,7 @@ class PatternFieldTestRecord private () extends MongoRecord[PatternFieldTestReco object mandatoryPatternField extends PatternField(this) object legacyOptionalPatternField extends PatternField(this) { override def optional_? = true } - /** - * Pattern.equals doesn't work properly, so a custom equals is required with PatternField - */ - override def equals(other: Any): Boolean = { - other match { - case that: PatternFieldTestRecord => - that.fields.corresponds(this.fields) { (a,b) => - (a.name == b.name) && ((a.valueBox, b.valueBox) match { - case (Full(ap: Pattern), Full(bp: Pattern)) => - ap.pattern == bp.pattern && ap.flags == bp.flags - case _ => a.valueBox == b.valueBox - }) - } - case _ => false - } - } + override def equals(other: Any): Boolean = equalsWithPatternCheck(other) def dirtyFields = this.allFields.filter(_.dirty_?) } @@ -278,30 +263,14 @@ class ListTestRecord private () extends MongoRecord[ListTestRecord] with UUIDPk[ def meta = ListTestRecord object mandatoryStringListField extends MongoListField[ListTestRecord, String](this) - object legacyOptionalStringListField extends MongoListField[ListTestRecord, String](this) { override def optional_? = true } - object mandatoryIntListField extends MongoListField[ListTestRecord, Int](this) - object legacyOptionalIntListField extends MongoListField[ListTestRecord, Int](this) { override def optional_? = true } - object mandatoryMongoJsonObjectListField extends MongoJsonObjectListField(this, TypeTestJsonObject) - object legacyOptionalMongoJsonObjectListField extends MongoJsonObjectListField(this, TypeTestJsonObject) { override def optional_? = true } - object mongoCaseClassListField extends MongoCaseClassListField[ListTestRecord, MongoCaseClassTestObject](this) { override def formats = owner.meta.formats } // TODO: More List types - override def equals(other: Any): Boolean = other match { - case that: ListTestRecord => - this.id.value == that.id.value && - this.mandatoryStringListField.value == that.mandatoryStringListField.value && - this.mandatoryIntListField.value == that.mandatoryIntListField.value && - this.mandatoryMongoJsonObjectListField.value == that.mandatoryMongoJsonObjectListField.value && - this.mongoCaseClassListField.value == that.mongoCaseClassListField.value - case _ => false - } - def dirtyFields = this.allFields.filter(_.dirty_?) } object ListTestRecord extends ListTestRecord with MongoMetaRecord[ListTestRecord] { @@ -312,21 +281,10 @@ class MapTestRecord private () extends MongoRecord[MapTestRecord] with StringPk[ def meta = MapTestRecord object mandatoryStringMapField extends MongoMapField[MapTestRecord, String](this) - object legacyOptionalStringMapField extends MongoMapField[MapTestRecord, String](this) { override def optional_? = true } - object mandatoryIntMapField extends MongoMapField[MapTestRecord, Int](this) - object legacyOptionalIntMapField extends MongoMapField[MapTestRecord, Int](this) { override def optional_? = true } // TODO: More Map types, including JsonObject (will require a new Field type) - override def equals(other: Any): Boolean = other match { - case that: MapTestRecord => - this.id.value == that.id.value && - this.mandatoryStringMapField.value == that.mandatoryStringMapField.value && - this.mandatoryIntMapField.value == that.mandatoryIntMapField.value - case _ => false - } - def dirtyFields = this.allFields.filter(_.dirty_?) } object MapTestRecord extends MapTestRecord with MongoMetaRecord[MapTestRecord] { @@ -335,7 +293,7 @@ object MapTestRecord extends MapTestRecord with MongoMetaRecord[MapTestRecord] { class LifecycleTestRecord private () extends MongoRecord[LifecycleTestRecord] - with MongoId[LifecycleTestRecord] + with ObjectIdPk[LifecycleTestRecord] { def meta = LifecycleTestRecord @@ -363,10 +321,7 @@ class SubRecord private () extends BsonRecord[SubRecord] { object pattern extends PatternField(this) object uuid extends UUIDField(this) - override def equals(other: Any): Boolean = other match { - case that: SubRecord => this.toString == that.toString - case _ => false - } + override def equals(other: Any): Boolean = equalsWithPatternCheck(other) } object SubRecord extends SubRecord with BsonMetaRecord[SubRecord] { override def formats = allFormats @@ -394,11 +349,6 @@ class SubRecordTestRecord private () extends MongoRecord[SubRecordTestRecord] wi override def optional_? = true } - override def equals(other: Any): Boolean = other match { - case that: SubRecordTestRecord => this.toString == that.toString - case _ => false - } - def dirtyFields = this.allFields.filter(_.dirty_?) } object SubRecordTestRecord extends SubRecordTestRecord with MongoMetaRecord[SubRecordTestRecord] { diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala index fbec632c01..89b6fbaf1f 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala @@ -1,5 +1,5 @@ /* - * Copyright 2006-2012 WorldWide Conferencing, LLC + * Copyright 2006-2013 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. @@ -58,7 +58,7 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundExample example: A, example2: A, mandatory: MandatoryTypedField[A], - legacyOptional: MandatoryTypedField[A], + legacyOptionalBox: Box[MandatoryTypedField[A]], canCheckDefaultValues: Boolean = true )(implicit m: scala.reflect.Manifest[A]): Unit = { @@ -137,42 +137,44 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundExample } } - "support 'legacy' optional fields (override optional_?)" in { - "which are configured correctly" in { - legacyOptional.optional_? must_== true - } - - "which initialize to Empty" in { - legacyOptional.valueBox must_== Empty - } + legacyOptionalBox map { legacyOptional => + "support 'legacy' optional fields (override optional_?)" in { + "which are configured correctly" in { + legacyOptional.optional_? must_== true + } - "common behaviors for all flavors" in { - commonBehaviorsForAllFlavors(legacyOptional) - } + "which initialize to Empty" in { + legacyOptional.valueBox must_== Empty + } - "which do not fail when set to Empty" in { - legacyOptional.set(example) - legacyOptional.value must_== example - legacyOptional.valueBox must_== Full(example) - legacyOptional.clear - if (canCheckDefaultValues) { - legacyOptional.value must_== legacyOptional.defaultValue - legacyOptional.valueBox must_== legacyOptional.defaultValueBox + "common behaviors for all flavors" in { + commonBehaviorsForAllFlavors(legacyOptional) } - legacyOptional.set(example) - legacyOptional.value must_== example - legacyOptional.valueBox must_== Full(example) - legacyOptional.setBox(Empty) - if (canCheckDefaultValues) { - legacyOptional.value must_== legacyOptional.defaultValue - legacyOptional.valueBox must_== legacyOptional.defaultValueBox + + "which do not fail when set to Empty" in { + legacyOptional.set(example) + legacyOptional.value must_== example + legacyOptional.valueBox must_== Full(example) + legacyOptional.clear + if (canCheckDefaultValues) { + legacyOptional.value must_== legacyOptional.defaultValue + legacyOptional.valueBox must_== legacyOptional.defaultValueBox + } + legacyOptional.set(example) + legacyOptional.value must_== example + legacyOptional.valueBox must_== Full(example) + legacyOptional.setBox(Empty) + if (canCheckDefaultValues) { + legacyOptional.value must_== legacyOptional.defaultValue + legacyOptional.valueBox must_== legacyOptional.defaultValueBox + } + success } - success } } } - def passConversionTests[A](example: A, mandatory: MandatoryTypedField[A], jsexp: JsExp, jvalue: JValue, formPattern: Box[NodeSeq]): Fragment = { + def passConversionTests[A](example: A, mandatory: MandatoryTypedField[A], jsexp: JsExp, jvalue: JValue, formPattern: Box[NodeSeq], canCheckSetFromJValue: Boolean = true): Fragment = { /* "convert to JsExp" in { @@ -185,7 +187,7 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundExample mandatory.asJValue mustEqual jvalue } - if (!example.isInstanceOf[Pattern]) { // Patterns don't compare well + if (canCheckSetFromJValue) { "get set from JValue" in { mandatory.setFromJValue(jvalue) mustEqual Full(example) mandatory.value mustEqual example @@ -220,7 +222,7 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundExample val nowStr = rec.meta.formats.dateFormat.format(now) val now2 = Calendar.getInstance() now2.add(Calendar.DATE, 1) - passBasicTests(now, now2.getTime, rec.mandatoryDateField, rec.legacyOptionalDateField, false) + passBasicTests(now, now2.getTime, rec.mandatoryDateField, Full(rec.legacyOptionalDateField), false) passConversionTests( now, rec.mandatoryDateField, @@ -235,7 +237,7 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundExample val ttjo = TypeTestJsonObject(1, "jsonobj1", Map("x" -> "a")) val ttjo2 = TypeTestJsonObject(2, "jsonobj2", Map("x" -> "b")) val json = ("intField" -> 1) ~ ("stringField" -> "jsonobj1") ~ ("mapField" -> (("x" -> "a"))) - passBasicTests(ttjo, ttjo2, rec.mandatoryJsonObjectField, rec.legacyOptionalJsonObjectField) + passBasicTests(ttjo, ttjo2, rec.mandatoryJsonObjectField, Full(rec.legacyOptionalJsonObjectField)) passConversionTests( ttjo, rec.mandatoryJsonObjectField, @@ -251,7 +253,7 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundExample val rec = MongoFieldTypeTestRecord.createRecord val oid = ObjectId.get val oid2 = ObjectId.get - passBasicTests(oid, oid2, rec.mandatoryObjectIdField, rec.legacyOptionalObjectIdField, false) + passBasicTests(oid, oid2, rec.mandatoryObjectIdField, Full(rec.legacyOptionalObjectIdField), false) passConversionTests( oid, rec.mandatoryObjectIdField, @@ -265,13 +267,14 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundExample val rec = PatternFieldTestRecord.createRecord val ptrn = Pattern.compile("^Mo", Pattern.CASE_INSENSITIVE) val ptrn2 = Pattern.compile("^MON", Pattern.CASE_INSENSITIVE) - passBasicTests(ptrn, ptrn2, rec.mandatoryPatternField, rec.legacyOptionalPatternField, false) + passBasicTests(ptrn, ptrn2, rec.mandatoryPatternField, Full(rec.legacyOptionalPatternField), false) passConversionTests( ptrn, rec.mandatoryPatternField, JsObj(("$regex", Str(ptrn.toString)), ("$flags", Num(2))), JObject(List(JField("$regex", JString(ptrn.toString)), JField("$flags", JInt(2)))), - Empty + Empty, + false ) } @@ -279,7 +282,7 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundExample val rec = MongoFieldTypeTestRecord.createRecord val uuid = UUID.randomUUID val uuid2 = UUID.randomUUID - passBasicTests(uuid, uuid2, rec.mandatoryUUIDField, rec.legacyOptionalUUIDField, false) + passBasicTests(uuid, uuid2, rec.mandatoryUUIDField, Full(rec.legacyOptionalUUIDField), false) passConversionTests( uuid, rec.mandatoryUUIDField, @@ -314,7 +317,7 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundExample val rec = ListTestRecord.createRecord val lst = List("abc", "def", "ghi") val lst2 = List("ab", "de", "gh") - passBasicTests(lst, lst2, rec.mandatoryStringListField, rec.legacyOptionalStringListField) + passBasicTests(lst, lst2, rec.mandatoryStringListField, Empty) passConversionTests( lst, rec.mandatoryStringListField, @@ -330,7 +333,7 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundExample val rec = ListTestRecord.createRecord val lst = List(4, 5, 6) val lst2 = List(1, 2, 3) - passBasicTests(lst, lst2, rec.mandatoryIntListField, rec.legacyOptionalIntListField) + passBasicTests(lst, lst2, rec.mandatoryIntListField, Empty) passConversionTests( lst, rec.mandatoryIntListField, @@ -350,7 +353,7 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundExample ("intField" -> 1) ~ ("stringField" -> "jsonobj1") ~ ("mapField" -> (("x" -> "1"))), ("intField" -> 2) ~ ("stringField" -> "jsonobj2") ~ ("mapField" -> (("x" -> "2"))) ) - passBasicTests(lst, lst2, rec.mandatoryMongoJsonObjectListField, rec.legacyOptionalMongoJsonObjectListField) + passBasicTests(lst, lst2, rec.mandatoryMongoJsonObjectListField, Empty) passConversionTests( lst, rec.mandatoryMongoJsonObjectListField, @@ -377,7 +380,7 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundExample val rec = MapTestRecord.createRecord val map = Map("a" -> "abc", "b" -> "def", "c" -> "ghi") val map2 = Map("a" -> "ab", "b" -> "de", "c" -> "gh") - passBasicTests(map, map2, rec.mandatoryStringMapField, rec.legacyOptionalStringMapField) + passBasicTests(map, map2, rec.mandatoryStringMapField, Empty) passConversionTests( map, rec.mandatoryStringMapField, @@ -397,7 +400,7 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundExample val rec = MapTestRecord.createRecord val map = Map("a" -> 4, "b" -> 5, "c" -> 6) val map2 = Map("a" -> 1, "b" -> 2, "c" -> 3) - passBasicTests(map, map2, rec.mandatoryIntMapField, rec.legacyOptionalIntMapField) + passBasicTests(map, map2, rec.mandatoryIntMapField, Empty) passConversionTests( map, rec.mandatoryIntMapField, @@ -415,12 +418,13 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundExample "BsonRecordField" should { "function correctly" in { val rec = SubRecordTestRecord.createRecord - val subRec = SubRecord.createRecord.name("subrecord") + val subSubRec = SubSubRecord.createRecord.name("subsub") + val subRec = SubRecord.createRecord.name("subrecord").subsub(subSubRec) val subRec2 = SubRecord.createRecord.name("subrecord2") val srJson = ("name" -> "subrecord") ~ - ("subsub" -> ("name" -> "")) ~ + ("subsub" -> ("name" -> "subsub")) ~ ("subsublist" -> JArray(Nil)) ~ ("when" -> ("$dt" -> rec.meta.formats.dateFormat.format(subRec.when.value))) ~ ("slist" -> JArray(Nil)) ~ @@ -436,7 +440,7 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundExample def toJsCmd = Printer.compact(render(srJson)) } - passBasicTests(subRec, subRec2, rec.mandatoryBsonRecordField, rec.legacyOptionalBsonRecordField, false) + passBasicTests(subRec, subRec2, rec.mandatoryBsonRecordField, Full(rec.legacyOptionalBsonRecordField), false) passConversionTests( subRec, rec.mandatoryBsonRecordField, @@ -450,11 +454,12 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundExample "BsonRecordListField" should { "function correctly" in { val rec = SubRecordTestRecord.createRecord - val lst = List(SubRecord.createRecord.name("subrec1"), SubRecord.createRecord.name("subrec2")) + val subSubRec = SubSubRecord.createRecord.name("subsub") + val lst = List(SubRecord.createRecord.name("subrec1").subsub(subSubRec), SubRecord.createRecord.name("subrec2").subsub(subSubRec)) val lst2 = List(SubRecord.createRecord.name("subrec3"), SubRecord.createRecord.name("subrec4")) val sr1Json = ("name" -> "subrec1") ~ - ("subsub" -> ("name" -> "")) ~ + ("subsub" -> ("name" -> "subsub")) ~ ("subsublist" -> JArray(Nil)) ~ ("when" -> ("$dt" -> rec.meta.formats.dateFormat.format(lst(0).when.value))) ~ ("slist" -> JArray(Nil)) ~ @@ -468,7 +473,7 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundExample val sr2Json = ("name" -> "subrec2") ~ - ("subsub" -> ("name" -> "")) ~ + ("subsub" -> ("name" -> "subsub")) ~ ("subsublist" -> JArray(Nil)) ~ ("when" -> ("$dt" -> rec.meta.formats.dateFormat.format(lst(1).when.value))) ~ ("slist" -> JArray(Nil)) ~ @@ -487,7 +492,7 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundExample def toJsCmd = compact(render(sr2Json)) } - passBasicTests(lst, lst2, rec.mandatoryBsonRecordListField, rec.legacyOptionalBsonRecordListField) + passBasicTests(lst, lst2, rec.mandatoryBsonRecordListField, Full(rec.legacyOptionalBsonRecordListField)) passConversionTests( lst, rec.mandatoryBsonRecordListField, diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala index 415fee7460..e26417a9b6 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala @@ -1,5 +1,5 @@ /* - * Copyright 2010-2012 WorldWide Conferencing, LLC + * Copyright 2010-2013 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. @@ -228,14 +228,11 @@ class MongoRecordSpec extends Specification with MongoTestKit { val ltrJson = ("_id" -> ("$uuid" -> ltr.id.toString)) ~ ("mandatoryStringListField" -> List("abc", "def", "ghi")) ~ - ("legacyOptionalStringListField" -> List[String]()) ~ ("mandatoryIntListField" -> List(4, 5, 6)) ~ - ("legacyOptionalIntListField" -> List[Int]()) ~ ("mandatoryMongoJsonObjectListField" -> List( (("intField" -> 1) ~ ("stringField" -> "jsonobj1") ~ ("mapField" -> ("x" -> "1"))), (("intField" -> 2) ~ ("stringField" -> "jsonobj2") ~ ("mapField" -> ("x" -> "2"))) )) ~ - ("legacyOptionalMongoJsonObjectListField" -> List[JObject]()) ~ ("mongoCaseClassListField" -> List( ("intField" -> 1) ~ ("stringField" -> "str") ~ ("enum" -> 1) )) @@ -251,13 +248,11 @@ class MongoRecordSpec extends Specification with MongoTestKit { ("b" -> "def") ~ ("c" -> "ghi") )) ~ - ("legacyOptionalStringMapField" -> JObject(Nil)) ~ ("mandatoryIntMapField" -> ( ("a" -> 4) ~ ("b" -> 5) ~ ("c" -> 6) - )) ~ - ("legacyOptionalIntMapField" -> JObject(Nil)) + )) // SubRecord val ssr1 = SubSubRecord.createRecord.name("SubSubRecord1") @@ -278,61 +273,36 @@ class MongoRecordSpec extends Specification with MongoTestKit { .mandatoryBsonRecordListField(List(sr1,sr2)) val sr1Json = - JObject(List( - JField("name", JString("SubRecord1")), - JField("subsub", JObject(List( - JField("name", JString("SubSubRecord1")) - ))), - JField("subsublist", JArray(List( - JObject(List(JField("name", JString("SubSubRecord1")))), - JObject(List(JField("name", JString("SubSubRecord2")))) - ))), - JField("when", JObject(List( - JField("$dt", JString(srtr.meta.formats.dateFormat.format(sr1.when.value))) - ))), - JField("slist", JArray(List(JString("s1"), JString("s2")))), - JField("smap", JObject(List( - JField("a", JString("s1")), - JField("b", JString("s2")) - ))), - JField("oid", JObject(List(JField("$oid", JString(sr1.oid.value.toString))))), - JField("pattern", JObject(List( - JField("$regex", JString(sr1.pattern.value.pattern)), - JField("$flags", JInt(sr1.pattern.value.flags)) - ))), - JField("uuid", JObject(List(JField("$uuid", JString(sr1.uuid.value.toString))))) - )) + ("name" -> "SubRecord1") ~ + ("subsub" -> ("name" -> "SubSubRecord1")) ~ + ("subsublist" -> List( + ("name" -> "SubSubRecord1"), + ("name" -> "SubSubRecord2") + )) ~ + ("when" -> ("$dt" -> srtr.meta.formats.dateFormat.format(sr1.when.value))) ~ + ("slist" -> List("s1", "s2")) ~ + ("smap" -> (("a" -> "s1") ~ ("b" -> "s2"))) ~ + ("oid" -> ("$oid" -> sr1.oid.value.toString)) ~ + ("pattern" -> (("$regex" -> sr1.pattern.value.pattern) ~ ("$flags" -> sr1.pattern.value.flags))) ~ + ("uuid" -> ("$uuid" -> sr1.uuid.value.toString)) val sr2Json = - JObject(List( - JField("name", JString("SubRecord2")), - JField("subsub", JObject(List( - JField("name", JString("")) - ))), - JField("subsublist", JArray(List())), - JField("when", JObject(List( - JField("$dt", JString(srtr.meta.formats.dateFormat.format(sr2.when.value))) - ))), - JField("slist", JArray(List())), - JField("smap", JObject(List())), - JField("oid", JObject(List(JField("$oid", JString(sr2.oid.value.toString))))), - JField("pattern", JObject(List( - JField("$regex", JString(sr2.pattern.value.pattern)), - JField("$flags", JInt(sr2.pattern.value.flags)) - ))), - JField("uuid", JObject(List(JField("$uuid", JString(sr2.uuid.value.toString))))) - )) - - val srtrJson = JObject(List( - JField("_id", JObject(List(JField("$oid", JString(srtr.id.toString))))), - JField("mandatoryBsonRecordField", sr1Json), - JField("legacyOptionalBsonRecordField", JNothing), - JField("mandatoryBsonRecordListField", JArray(List( - sr1Json, - sr2Json - ))), - JField("legacyOptionalBsonRecordListField", JArray(List())) - )) + ("name" -> "SubRecord2") ~ + ("subsub" -> ("name" -> "")) ~ + ("subsublist" -> JArray(Nil)) ~ + ("when" -> ("$dt" -> srtr.meta.formats.dateFormat.format(sr2.when.value))) ~ + ("slist" -> JArray(Nil)) ~ + ("smap" -> JObject(Nil)) ~ + ("oid" -> ("$oid" -> sr2.oid.value.toString)) ~ + ("pattern" -> (("$regex" -> sr2.pattern.value.pattern) ~ ("$flags" -> sr2.pattern.value.flags))) ~ + ("uuid" -> ("$uuid" -> sr2.uuid.value.toString)) + + val srtrJson = + ("_id" -> ("$oid" -> srtr.id.toString)) ~ + ("mandatoryBsonRecordField" -> sr1Json) ~ + ("legacyOptionalBsonRecordField" -> JNothing) ~ + ("mandatoryBsonRecordListField" -> List(sr1Json, sr2Json)) ~ + ("legacyOptionalBsonRecordListField", JArray(Nil)) // JObjectField val joftrFieldJObject: JObject = ("minutes" -> 59) @@ -409,7 +379,7 @@ class MongoRecordSpec extends Specification with MongoTestKit { val srtrFromDb = SubRecordTestRecord.find(srtr.id.value) srtrFromDb.isDefined must_== true - srtrFromDb.toList map { tr => + srtrFromDb foreach { tr => tr mustEqual srtr } @@ -482,21 +452,31 @@ class MongoRecordSpec extends Specification with MongoTestKit { "convert Mongo type fields to JValue" in { mfttr.asJValue mustEqual mfttrJson + } + "convert pattern field to JValue" in { pftr.asJValue mustEqual pftrJson + } + "convert list fields to JValue" in { ltr.asJValue mustEqual ltrJson + } + "convert map fields to JValue" in { mtr.asJValue mustEqual mtrJson + } + + "convert JObject fields to JValue" in { + joftr.asJValue mustEqual joftrJson + } + "convert BsonRecord fields to JValue" in { val srtrAsJValue = srtr.asJValue srtrAsJValue \\ "_id" mustEqual srtrJson \\ "_id" srtrAsJValue \\ "mandatoryBsonRecordField" mustEqual srtrJson \\ "mandatoryBsonRecordField" srtrAsJValue \\ "legacyOptionalBsonRecordField" mustEqual srtrJson \\ "legacyOptionalBsonRecordField" srtrAsJValue \\ "mandatoryBsonRecordListField" mustEqual srtrJson \\ "mandatoryBsonRecordListField" srtrAsJValue \\ "legacyOptionalBsonRecordListField" mustEqual srtrJson \\ "legacyOptionalBsonRecordListField" - - joftr.asJValue mustEqual joftrJson } "get set from json string using lift-json parser" in { diff --git a/persistence/record/src/main/scala/net/liftweb/record/Record.scala b/persistence/record/src/main/scala/net/liftweb/record/Record.scala index 376deef58a..50531decad 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/Record.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/Record.scala @@ -1,5 +1,5 @@ /* - * Copyright 2007-2012 WorldWide Conferencing, LLC + * Copyright 2007-2013 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. @@ -158,7 +158,7 @@ trait Record[MyType <: Record[MyType]] extends FieldContainer { case Full(c: java.util.Calendar) => c.getTime().toString() case Full(null) => "null" case Full(v) => v.toString - case _ => "" + case x => x.toString })) "%s={%s}" format (this.getClass.toString, fieldList.mkString(", ")) From b5c49a609ea434a3e6bacd74c4f2f63068b03094 Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Sat, 12 Jan 2013 08:36:24 -0500 Subject: [PATCH 0309/1949] fixed S.respondAsync --- web/webkit/src/main/scala/net/liftweb/http/S.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/S.scala b/web/webkit/src/main/scala/net/liftweb/http/S.scala index d94220c50e..e25a32373b 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/S.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/S.scala @@ -3016,7 +3016,7 @@ trait S extends HasParams with Loggable { * */ def respondAsync(f: => Box[LiftResponse]): () => Box[LiftResponse] = { - RestContinuation.async {reply => f} + RestContinuation.async {reply => reply(f.openOr(EmptyResponse))} } From dc6042e2c503f0272ddc3fe9911fc3981f872959 Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Mon, 14 Jan 2013 22:54:44 -0500 Subject: [PATCH 0310/1949] Fixed #1397 - specs2 was not in test dependency, it was included in compile --- project/Dependencies.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 3535a99dee..caf678f420 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -83,6 +83,6 @@ object Dependencies { lazy val mockito_all = "org.mockito" % "mockito-all" % "1.9.0" % "test" lazy val scalacheck = "org.scalacheck" %% "scalacheck" % "1.10.0" % "test" lazy val specs2: ModuleMap = - "org.specs2" %% "specs2" % defaultOrMapped("1.12.3")(_) + "org.specs2" %% "specs2" % defaultOrMapped("1.12.3")(_) % "test" } From fdaf5357a3e7b3c7738aae8ef431faefdd7365f3 Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Tue, 15 Jan 2013 00:16:37 -0500 Subject: [PATCH 0311/1949] Fixed #1387 - solve net.liftweb.util.ComputeTransformRules[Double] --- core/util/src/main/scala/net/liftweb/util/CssSel.scala | 10 ++++++++++ .../test/scala/net/liftweb/util/CssSelectorSpec.scala | 8 ++++++++ 2 files changed, 18 insertions(+) diff --git a/core/util/src/main/scala/net/liftweb/util/CssSel.scala b/core/util/src/main/scala/net/liftweb/util/CssSel.scala index 1fc7f76cf1..2f03275a8d 100644 --- a/core/util/src/main/scala/net/liftweb/util/CssSel.scala +++ b/core/util/src/main/scala/net/liftweb/util/CssSel.scala @@ -749,6 +749,9 @@ object ComputeTransformRules { } } + implicit def doubleTRansform: ComputeTransformRules[Double] = new ComputeTransformRules[Double] { + def computeTransform(str: => Double, ns: NodeSeq): Seq[NodeSeq] = List(Text(str.toString)) + } implicit def jsCmdTransform: ComputeTransformRules[ToJsCmd] = new ComputeTransformRules[ToJsCmd] { def computeTransform(str: => ToJsCmd, ns: NodeSeq): Seq[NodeSeq] = List(Text(str.toJsCmd)) @@ -820,6 +823,7 @@ object ComputeTransformRules { def computeTransform(info: => Option[NST], ns: NodeSeq): Seq[NodeSeq] = info.toList.map(f2) } + implicit def iterableStringTransform[T[_]](implicit f: T[String] => Iterable[String]): ComputeTransformRules[T[String]] = new ComputeTransformRules[T[String]] { def computeTransform(info: => T[String], ns: NodeSeq): Seq[NodeSeq] = f(info).toSeq.map(a => Text(a)) @@ -831,6 +835,12 @@ object ComputeTransformRules { if (a eq null) Nil else List(Text(a.toString))) } + implicit def iterableDouble[T[Double]](implicit f: T[Double] => Iterable[Double]): ComputeTransformRules[T[Double]] = + new ComputeTransformRules[T[Double]] { + def computeTransform(info: => T[Double], ns: NodeSeq): Seq[NodeSeq] = f(info).toSeq.flatMap(a => + if (a equals null) Nil else List(Text(a.toString))) + } + implicit def iterableBindableTransform[T[_]](implicit f: T[Bindable] => Iterable[Bindable]): ComputeTransformRules[T[Bindable]] = new ComputeTransformRules[T[Bindable]] { def computeTransform(info: => T[Bindable], ns: NodeSeq): Seq[NodeSeq] = Helpers.ensureUniqueId(f(info).toSeq.map(_.asHtml)) diff --git a/core/util/src/test/scala/net/liftweb/util/CssSelectorSpec.scala b/core/util/src/test/scala/net/liftweb/util/CssSelectorSpec.scala index 2dec8bcbd0..0e0a96647e 100644 --- a/core/util/src/test/scala/net/liftweb/util/CssSelectorSpec.scala +++ b/core/util/src/test/scala/net/liftweb/util/CssSelectorSpec.scala @@ -897,14 +897,22 @@ object CheckTheImplicitConversionsForToCssBindPromoter { bog #> 1 bog #> 'foo bog #> 44L + bog #> 1.22 bog #> false bog #> List() bog #> Full() + val e: Box[String] = Empty + bog #> e bog #> Some() + val n: Option[String] = None + bog #> n bog #> List("Hello") + bog #> List(1.22) + bog #> List(44L) + bog #> List(1) bog #> Full("Dog") bog #> Some("Moo") From 8cf68eb758995eb0a750ad9fd71b7331d4029eae Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Fri, 18 Jan 2013 22:00:59 -0500 Subject: [PATCH 0312/1949] De-duplicate code that base64 encodes strings in SecurityHelpers. --- .../src/main/scala/net/liftweb/util/SecurityHelpers.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/SecurityHelpers.scala b/core/util/src/main/scala/net/liftweb/util/SecurityHelpers.scala index adb1ede659..020045d97e 100644 --- a/core/util/src/main/scala/net/liftweb/util/SecurityHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/SecurityHelpers.scala @@ -72,7 +72,7 @@ trait SecurityHelpers { def md5(in: Array[Byte]): Array[Byte] = (MessageDigest.getInstance("MD5")).digest(in) /** create a MD5 digest from a String */ - def md5(in: String): String = new String(cleanArray((new Base64) encode md5(in.getBytes("UTF-8")))) + def md5(in: String): String = base64Encode(md5(in.getBytes("UTF-8"))) /** create a SHA hash from a Byte array */ def hash(in : Array[Byte]) : Array[Byte] = { @@ -81,7 +81,7 @@ trait SecurityHelpers { /** create a SHA hash from a String */ def hash(in: String) : String = { - new String(cleanArray((new Base64) encode (MessageDigest.getInstance("SHA")).digest(in.getBytes("UTF-8")))) + base64Encode(MessageDigest.getInstance("SHA").digest(in.getBytes("UTF-8"))) } /** create a SHA hash from a String */ @@ -127,7 +127,7 @@ trait SecurityHelpers { /** create a SHA-256 hash from a String */ def hash256(in : String): String = { - new String(cleanArray((new Base64) encode (MessageDigest.getInstance("SHA-256")).digest(in.getBytes("UTF-8")))) + base64Encode(MessageDigest.getInstance("SHA-256").digest(in.getBytes("UTF-8"))) } /** create an hex encoded SHA hash from a Byte array */ From 0183b938769e193e1fe59e3e469853193097e60b Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Fri, 18 Jan 2013 22:01:46 -0500 Subject: [PATCH 0313/1949] Remove various unneeded parens in SecurityHelpers. --- .../main/scala/net/liftweb/util/SecurityHelpers.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/SecurityHelpers.scala b/core/util/src/main/scala/net/liftweb/util/SecurityHelpers.scala index 020045d97e..488883bab8 100644 --- a/core/util/src/main/scala/net/liftweb/util/SecurityHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/SecurityHelpers.scala @@ -69,14 +69,14 @@ trait SecurityHelpers { def base64Decode(in: String): Array[Byte] = (new Base64).decode(in.getBytes("UTF-8")) /** create a MD5 digest from a Byte array */ - def md5(in: Array[Byte]): Array[Byte] = (MessageDigest.getInstance("MD5")).digest(in) + def md5(in: Array[Byte]): Array[Byte] = MessageDigest.getInstance("MD5").digest(in) /** create a MD5 digest from a String */ def md5(in: String): String = base64Encode(md5(in.getBytes("UTF-8"))) /** create a SHA hash from a Byte array */ def hash(in : Array[Byte]) : Array[Byte] = { - (MessageDigest.getInstance("SHA")).digest(in) + MessageDigest.getInstance("SHA").digest(in) } /** create a SHA hash from a String */ @@ -122,7 +122,7 @@ trait SecurityHelpers { /** create a SHA-256 hash from a Byte array */ def hash256(in : Array[Byte]) : Array[Byte] = { - (MessageDigest.getInstance("SHA-256")).digest(in) + MessageDigest.getInstance("SHA-256").digest(in) } /** create a SHA-256 hash from a String */ @@ -132,13 +132,13 @@ trait SecurityHelpers { /** create an hex encoded SHA hash from a Byte array */ def hexDigest(in: Array[Byte]): String = { - val binHash = (MessageDigest.getInstance("SHA")).digest(in) + val binHash = MessageDigest.getInstance("SHA").digest(in) hexEncode(binHash) } /** create an hex encoded SHA-256 hash from a Byte array */ def hexDigest256(in: Array[Byte]): String = { - val binHash = (MessageDigest.getInstance("SHA-256")).digest(in) + val binHash = MessageDigest.getInstance("SHA-256").digest(in) hexEncode(binHash) } From 893da6166d07aa47665c197dc8cc9ba33da36b82 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Fri, 18 Jan 2013 22:17:36 -0500 Subject: [PATCH 0314/1949] Add myself to the contributors file. --- contributors.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/contributors.md b/contributors.md index a4f62bdcea..b7644a1275 100644 --- a/contributors.md +++ b/contributors.md @@ -100,4 +100,10 @@ a.rashid at zantekk dot com Kristof Jozsa ### Email: ### -kristof.jozsa at gmail dot com \ No newline at end of file +kristof.jozsa at gmail dot com + +### Name: ### +Matt Farmer + +### Email: ### +matt at frmr dot me From bf99e3392d993af1b7a890942a4da4aacc5cbe09 Mon Sep 17 00:00:00 2001 From: Andrew Goodnough Date: Wed, 16 Jan 2013 13:47:39 -0600 Subject: [PATCH 0315/1949] Fixed comment for devMode * Standardized productionMode comment with others --- core/util/src/main/scala/net/liftweb/util/Props.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/Props.scala b/core/util/src/main/scala/net/liftweb/util/Props.scala index 11bb6d097a..ca7abc5dfc 100644 --- a/core/util/src/main/scala/net/liftweb/util/Props.scala +++ b/core/util/src/main/scala/net/liftweb/util/Props.scala @@ -133,13 +133,13 @@ object Props extends Logger { } /** - * Is the system in production mode (apply full optimizations) + * Is the system running in production mode (apply full optimizations) */ lazy val productionMode: Boolean = mode == RunModes.Production || mode == RunModes.Pilot || mode == RunModes.Staging /** - * Is the system in production mode (apply full optimizations) + * Is the system running in development mode */ lazy val devMode: Boolean = mode == RunModes.Development From a88da1745c09bf202585fc769fc73024b5c89eba Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Wed, 16 Jan 2013 14:16:38 -0500 Subject: [PATCH 0316/1949] DoubleField can be set from JInt --- .../main/scala/net/liftweb/record/field/DoubleField.scala | 5 ++++- .../src/test/scala/net/liftweb/record/FieldSpec.scala | 7 ++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/DoubleField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/DoubleField.scala index 745347141b..3791e9f3a5 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/DoubleField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/DoubleField.scala @@ -21,10 +21,12 @@ package field import scala.xml._ import net.liftweb.common._ import net.liftweb.http.{S} -import net.liftweb.json.JsonAST.{JDouble, JNothing, JNull, JValue} +import json.JsonAST._ import net.liftweb.util._ import Helpers._ import S._ +import json.JsonAST.JDouble +import common.Full trait DoubleTypedField extends NumericTypedField[Double] { @@ -47,6 +49,7 @@ trait DoubleTypedField extends NumericTypedField[Double] { def setFromJValue(jvalue: JValue) = jvalue match { case JNothing|JNull if optional_? => setBox(Empty) case JDouble(d) => setBox(Full(d)) + case JInt(i) => setBox(Full(i.toDouble)) case other => setBox(FieldHelpers.expectedA("JDouble", other)) } } diff --git a/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala b/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala index 1c54adb8c8..c04e62adc5 100644 --- a/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala +++ b/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala @@ -392,7 +392,7 @@ object FieldSpec extends Specification { "DoubleField" should { val rec = FieldTypeTestRecord.createRecord val d = 12.34 - val d2 = 1.22 + val d2 = 1.00 passBasicTests(d, d2, rec.mandatoryDoubleField, rec.legacyOptionalDoubleField, rec.optionalDoubleField) passConversionTests( d, @@ -401,6 +401,11 @@ object FieldSpec extends Specification { JDouble(d), Full() ) + + "get set from JInt" in { + rec.mandatoryDoubleField.setFromJValue( JInt(1) ) mustEqual Full( d2 ) + rec.mandatoryDoubleField.value mustEqual d2 + } } "EmailField" should { From 5d2d62bded5870911f10977153e2a94bb6f6b614 Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Wed, 16 Jan 2013 14:26:53 -0500 Subject: [PATCH 0317/1949] IntField/LongField can be set from JDouble --- .../main/scala/net/liftweb/record/field/IntField.scala | 5 ++++- .../scala/net/liftweb/record/field/LongField.scala | 5 ++++- .../src/test/scala/net/liftweb/record/FieldSpec.scala | 10 ++++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/IntField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/IntField.scala index 3cd6b16351..db07d55528 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/IntField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/IntField.scala @@ -21,10 +21,12 @@ package field import scala.xml._ import net.liftweb.common._ import net.liftweb.http.S -import net.liftweb.json.JsonAST.{JInt, JNothing, JNull, JValue} +import json.JsonAST._ import net.liftweb.util._ import Helpers._ import S._ +import common.Full +import json.JsonAST.JInt trait IntTypedField extends NumericTypedField[Int] { @@ -43,6 +45,7 @@ trait IntTypedField extends NumericTypedField[Int] { def setFromJValue(jvalue: JValue): Box[Int] = jvalue match { case JNothing|JNull if optional_? => setBox(Empty) case JInt(i) => setBox(Full(i.intValue)) + case JDouble(d) => setBox(Full(d.toInt)) case other => setBox(FieldHelpers.expectedA("JInt", other)) } } diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/LongField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/LongField.scala index 7bdbb1b696..6799b0c2b8 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/LongField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/LongField.scala @@ -21,10 +21,12 @@ package field import scala.xml._ import net.liftweb.common._ import net.liftweb.http.{S} -import net.liftweb.json.JsonAST.{JInt, JNothing, JNull, JValue} +import json.JsonAST._ import net.liftweb.util._ import Helpers._ import S._ +import common.Full +import json.JsonAST.JInt trait LongTypedField extends NumericTypedField[Long] { @@ -46,6 +48,7 @@ trait LongTypedField extends NumericTypedField[Long] { def setFromJValue(jvalue: JValue): Box[Long] = jvalue match { case JNothing|JNull if optional_? => setBox(Empty) case JInt(i) => setBox(Full(i.longValue)) + case JDouble(d) => setBox(Full(d.toLong)) case other => setBox(FieldHelpers.expectedA("JLong", other)) } } diff --git a/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala b/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala index c04e62adc5..1c15a9e237 100644 --- a/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala +++ b/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala @@ -473,6 +473,11 @@ object FieldSpec extends Specification { JInt(num), Full() ) + + "get set from JDouble" in { + rec.mandatoryIntField.setFromJValue( JDouble(num) ) mustEqual Full( num ) + rec.mandatoryIntField.value mustEqual num + } } "IntField with custom HTML5 type" should { @@ -512,6 +517,11 @@ object FieldSpec extends Specification { JInt(lng), Full() ) + + "get set from JDouble" in { + rec.mandatoryLongField.setFromJValue( JDouble(lng) ) mustEqual Full( lng ) + rec.mandatoryLongField.value mustEqual lng + } } "PasswordField" should { From 655c9359a148c4b6266edb432c7e051c178ac495 Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Wed, 16 Jan 2013 14:29:52 -0500 Subject: [PATCH 0318/1949] Updated contributors.md --- contributors.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/contributors.md b/contributors.md index b7644a1275..64584b05c5 100644 --- a/contributors.md +++ b/contributors.md @@ -67,6 +67,7 @@ Francis Rhys-Jones francis.rhys-jones at guardian dot co dot uk ### Name: ### +<<<<<<< HEAD Gregory Flanagan ### Email: ### @@ -107,3 +108,9 @@ Matt Farmer ### Email: ### matt at frmr dot me + +### Name: ### +Chris Williams + +### Email: ### +chris dot williams @ joulebug dot com From 186836ec20cbc0d1685ced578c78dabde99029e1 Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Sat, 19 Jan 2013 15:51:53 -0500 Subject: [PATCH 0319/1949] Cleaned up Double/Int/LongField imports --- .../src/main/scala/net/liftweb/record/field/DoubleField.scala | 4 +--- .../src/main/scala/net/liftweb/record/field/IntField.scala | 4 +--- .../src/main/scala/net/liftweb/record/field/LongField.scala | 4 +--- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/DoubleField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/DoubleField.scala index 3791e9f3a5..e1cad37d8d 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/DoubleField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/DoubleField.scala @@ -21,12 +21,10 @@ package field import scala.xml._ import net.liftweb.common._ import net.liftweb.http.{S} -import json.JsonAST._ +import json._ import net.liftweb.util._ import Helpers._ import S._ -import json.JsonAST.JDouble -import common.Full trait DoubleTypedField extends NumericTypedField[Double] { diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/IntField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/IntField.scala index db07d55528..df4f5c65fd 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/IntField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/IntField.scala @@ -21,12 +21,10 @@ package field import scala.xml._ import net.liftweb.common._ import net.liftweb.http.S -import json.JsonAST._ +import json._ import net.liftweb.util._ import Helpers._ import S._ -import common.Full -import json.JsonAST.JInt trait IntTypedField extends NumericTypedField[Int] { diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/LongField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/LongField.scala index 6799b0c2b8..b81409b6b6 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/LongField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/LongField.scala @@ -21,12 +21,10 @@ package field import scala.xml._ import net.liftweb.common._ import net.liftweb.http.{S} -import json.JsonAST._ +import json._ import net.liftweb.util._ import Helpers._ import S._ -import common.Full -import json.JsonAST.JInt trait LongTypedField extends NumericTypedField[Long] { From 40253c8072d8834c9591e7a033774ee6277476d8 Mon Sep 17 00:00:00 2001 From: Greg Flanagan Date: Mon, 3 Dec 2012 20:50:09 -0800 Subject: [PATCH 0320/1949] Adding ajaxEditableSelect to SHtml. Allows users to dynamically add new selections into an ajaxSelect. --- contributors.md | 6 ++++++ .../main/scala/net/liftweb/http/SHtml.scala | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/contributors.md b/contributors.md index 64584b05c5..012ed3e785 100644 --- a/contributors.md +++ b/contributors.md @@ -114,3 +114,9 @@ Chris Williams ### Email: ### chris dot williams @ joulebug dot com + +### Name: ### +Gregory Flanagan + +### Email: ### +gregmflanagan at gmail dot com diff --git a/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala b/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala index 5165903f0a..27bc060d13 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala @@ -962,6 +962,25 @@ trait SHtml { } ajaxSelect(options, deflt, func, attributes: _*) +======= + val id = nextFuncName + val textOpt = nextFuncName + val idAttr = Seq(ElemAttr.pairToBasic("id", id)) + val options = opts :+ (textOpt , "New Element") + var _options = options + + def addId(elem: Elem) = (idAttr.foldLeft(elem)(_ % _)) + + lazy val func: (String) => JsCmd = (select: String) => { + def text(in: String): JsCmd = { + _options = (in, in) +: _options + Replace(id, addId({ajaxSelect(_options, Some(in), func, attrs: _*)})) + } + if (select == textOpt) Replace(id, addId({ajaxText("", text(_), attrs: _*)})) & Focus(id) else f(select) + } + + addId({ajaxSelect(options, deflt, func, attrs: _*)}) +>>>>>>> Adding ajaxEditableSelect to SHtml. Allows users to dynamically add new selections into an ajaxSelect. } def ajaxSelect(opts: Seq[(String, String)], deflt: Box[String], From 841006c1570630c7d78b682cd43000625ae2f1ac Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Wed, 23 Jan 2013 23:47:45 -0500 Subject: [PATCH 0321/1949] * Updated sbt to 0.12.1 --- liftsh | 2 +- liftsh.cmd | 2 +- project/build.properties | 2 +- ...h-0.12.0-RC2.jar => sbt-launch-0.12.1.jar} | Bin 1103469 -> 1103618 bytes 4 files changed, 3 insertions(+), 3 deletions(-) rename project/{sbt-launch-0.12.0-RC2.jar => sbt-launch-0.12.1.jar} (83%) diff --git a/liftsh b/liftsh index 8017a1f68a..0db10227b4 100755 --- a/liftsh +++ b/liftsh @@ -17,4 +17,4 @@ DEFAULT_OPTS="" cd `dirname $0` # Call with INTERNAL_OPTS followed by LIFTSH_OPTS (or DEFAULT_OPTS). java always takes the last option when duplicate. -exec java ${INTERNAL_OPTS} ${LIFTSH_OPTS:-${DEFAULT_OPTS}} -jar project/sbt-launch-0.12.0-RC2.jar "$@" +exec java ${INTERNAL_OPTS} ${LIFTSH_OPTS:-${DEFAULT_OPTS}} -jar project/sbt-launch-0.12.1.jar "$@" diff --git a/liftsh.cmd b/liftsh.cmd index 851f300f8e..e314d6fb7e 100644 --- a/liftsh.cmd +++ b/liftsh.cmd @@ -17,4 +17,4 @@ if "%LIFTSH_OPTS%"=="" ( ) @REM Call with INTERNAL_OPTS followed by LIFTSH_OPTS (or DEFAULT_OPTS). java always takes the last option when duplicate. -java %INTERNAL_OPTS% %LIFTSH_OPTS% -jar "%~dp0\project\sbt-launch-0.12.0-RC2.jar" %* +java %INTERNAL_OPTS% %LIFTSH_OPTS% -jar "%~dp0\project\sbt-launch-0.12.1.jar" %* diff --git a/project/build.properties b/project/build.properties index a1353b2d0a..bb1fd56022 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1,2 +1,2 @@ # Deprecate using build.properties, use -Dsbt.version=... in launcher arg instead -sbt.version=0.12.0-RC2 +sbt.version=0.12.1 diff --git a/project/sbt-launch-0.12.0-RC2.jar b/project/sbt-launch-0.12.1.jar similarity index 83% rename from project/sbt-launch-0.12.0-RC2.jar rename to project/sbt-launch-0.12.1.jar index 5d420809604e6af01660aa6f32cd70eb4169d102..06ad8d880592ad776fdfc38c573359261314e6c3 100644 GIT binary patch delta 49777 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 delta 50007 zcmZrY30zIv*XnlXv3Z`TG!KdtO&SQLK@p-TMWvF=Ttkx5B-$hi5v54jNkSzwC}Tt_ zW9FigA-;Xj+1K;l|9kKEd+(gJ)?RDvHSN9EKDRBU&L2)X^Sq`}q~u00(7&{4cN{Z$ zp;Ds%m#z9PNj#LtQ*_wm(*d3hop{PBuF%PfQ5BT)6l|#^Zn|m*k9;yQJRm_lf#3w1 zW|p}PwfwWu91ObpP(r7Cg~<#2>ePZ>H<|5|A_tUZNa|#5GNXas&3u*eV5=-l_BWhb zK6>0MteUh<4M|bnW?MladGtn6L_+nmdNth%8+dHvtCj~v%aMYBbQ8gpfNPi6#x`Mvo!UwI@=Gh7NYbp$yL7GIVJ@0J5;|Ea+j`0QWPU=D z^LMd9R1#mN{9#+nDTCY~0%h#{jP*XPiZohqh?aFavzg#mzIm4i zo@?wHiY72m=8q{KwPzLPnl4GOkOSq(e8uvfy?Zfh!r1w{?G{!t}yyEK{Uk zb{b!COuff2#L=?F~l zf=GZlHWD(N9uR~{P~k>jiIte8$a2}2CFleKwB#c7oC73|aQ{&c^1>t*W7`wzrXAp` zLbya?7q+RijSfIGbhtWF;v$xXG$mz$1$ju*_+=7lA}#Sp3~)S*Vz23@ zM8-eyNGc%0&_MBFJ`WZ)No12~h%B+5lnv2xwTE2%0AWa^AOkEo!lwg`0yNRA7ZS;O zq}PzN60w2sR7grloi0mQlDye|ShDCUoZN4H6?!nrPH!2}-3lBEQp zs2qqGFu|3bk}}XfMbaDVfa(Z|fe7<~oHs-@p<9KNAX-Gpk(^RwvD?LxZrC`Yl7h77 z!6~OC7h>&+Oh7Pjxs)#td&?x3V+0XcgaVqU_%iV5S;@&_ZBdLMHZ+jYCMgA#FGwE2 z2+{gNAWom=Q$gn`z7dqKmOMl@T7?I8pO=dBMp{Z+v)8(kJ8$6O6Ce?>X+H-^cF zs`z@~z%&X2O1e{=Ni;fPXo6NJiZqD4gen-qVgx{y!5lojgzRSFN%@Rz(7OULbFAiT zfJiUOFH&>lcml?RdEOK=oW4jEV=`JG+IW4WNQ=%oBmVkl4(Risi~>8ap!H4p3SSon zhEnjZR&)v=If~G#uyipcAKMsJAx+Rb6RA6J1?AYjNQy718AR$X!8876xYjZX5AKhl zv}24&RhS<~*^A$aK+af0;e(|sDUXQhK(wpXBhe7;fWyg0v^Hx3L}&>0)>AfOW7sw$ zHBZ+eW6Y@It3l>Q$_LCBd6mzULO4QNdU@nT_>3b;h&}$c^31bMmqxq9;AwvJg9zm6!PzMWKx1aLu zuOcBD0+A-mvB?P62yUWC3(or!p+t4cFQf<{$fs<_ln&pJaS4AT?#64T29{uv4lo;vYI@E;lAcq1x;Y7RMuYrDJ`04OqLh)k z6O$5Y1Qs-*efQkclsGIVShUy^n2Swl7a)IzQcDgYP7wq`t(mV6Ez2p-@sO79NC2T3 z!pM=DEjmY8fzgC!S8kzIUjQjfNFdx$K}p62Mu9yV^fse~&#{^^>F=;XKo(d~hYHfw ztAB!@2q+N-BFt*+J|fP|n<%PK{yIek1Bnt#0HbbF@<|{>4${RspzvOa_dZ1E27RE= zLGcrc0*HQyV(wQvWgVUZiW^giFeh-NBOOe^+B+z0bGj+Dm>nvQKDZtd(Il{xk_Xf8 zqJ!$MHxzO&feulcAm~2d1WtKJ`9O~Q2e$14<=1~txy1%RvV6m8ITpRWODe5Jg{#3%~SW6ON|LD_~ahw8#;fXtx-n}1P?mB;`f z$}$3~!z+qXYO(|nB1^2qgV6bI0x*4q(*KN!6kanRvfO*jAK{&`{RsXUkI+(|+K!gR zHMUX_VkV+B8Cgyhj08>+>DsVuH$C|)`Oet$kQj{o!L<}{cQJK^iO!-HC*2Fa& z7zd>~@S$9^QxeUX50JJ}prn&ehpD%usJLq;s*pkLR2I0>#iwR>p=JEZeJKxoz92IA z4;IetmXgEK^7k-xc;T5;EU5=H?UCA#ZH-n_Bart@iUWdB%z}s>DOspKD8;}A{}*!} zL^RC*A5-v4>VXV7b)o=yeV3xclhV{WJoP_7fPidRqDZ|+?tB1|i}?-+n5Rb-pH+$A z69K_gV=8%|0YoJrV5?uClDFA}Iu%DXn#8rg1B$SQnI%;k-m##H&%}S^faxz$6?U|w z;=+zX#{eK|CXBYCZotz+<(C8k1Z02>uh3#P-iF#hPLU}4t*=no_qL_#;3Mf7%!Mgl_ABF#i5-l4W_?AyPgtHDhnTGkv`@k zpfvnBi|T@vL{)V(Xe~i}5XJ-^nn%?nBcE7Pktl+X07?Z>WwCS6WbI+Va%vXNQuOZq zPYT(C-)~XqC9kHk{sO`W>!_8asR1$m&IT%OKmuaVkntW>`=pXn7u(+7|{qG@!#t1D7-f#4I0jgz(1iP#Hbs}mSSXjxDF`Oj3$MdW1f6~8 z+~)P3s)Kd?7g`n6e#G$xQT9mv#R6XGr8Z*+W`3dii*xJ?)eN+L;VVPSZ&ciAg(wQ) zUqNyD3snFP^rO9I>o2McmP`1Hz*I}p=+IMwW`}2mk|r4oLVLh(SO+B23?NcjIFC;2 z!6lSf_ctZbs4$X8QzIi3q89QV>>@M=RO1jHm}}BlAmA(70R*eiS};#yNf)EJ;QUwQ zmlbNXWf)EDVE#cXYS3_1_`?<=wi|KO{?SDwO9r$I@tLr1By9qzDl|8sQ86VE9)I}4 zrN%TvJRfut`bS?5yfKC*6p-2fM;93FOmo1D$TN{>dgmMjwj>Z%d(*1;B_Y3mMpf_}LAk1i!qPChjL`=VBm0KpOZsj0%!pIPIAfIaiSma6uGpA?AT} z)`4$gXjgD&s$>mKAD1W;D}=!Wkg-+F9-`1P#+qbqqXl8ENE6(;6^Y*yDd582$u#jv zja&^yrc{#<$U|W&4R0ewjUY|nm`h`VRT2UvXq-;-#oj`E^kMNH+H&k-^v)9(5Ydia zGziWuq)o^3Aaa=C^r@UC4PO+|#5-cN^k5kx47hX}{paXO+AGo?XkGs=7CtJW6=IkD z8x7H5yiEhdCBi=f)6USwVrLS;O6Z3K`rz?gsRMhq0v(7@h1v~Xj~{6kSpQL9X=lg)ee{*4g@I_O z!X8=rLjtkq6TO}^vsUSIlGEG26WJrP%8#6uu9vWR|%^FJGRZ6Fax zSA&mZ=<;|X{mbbVe`yaLR?@|d&m*ho4w&lI8hRLRPkWkE*aJn=ThXs$27k zUWEs2`Ak>D{y#84*T9dLhv;XqRaOnt%Q4hcf-woJzJkIa`=21lo1p+EN-%>|vz7zgmAS|73%z|a^5d8Z5CTQbC#Qs_|? zZX1t$L=Gq3ee4+hf4_%LV$8tPiRCl3;iJh67M=s)u^>+d4|u5ym_W{*QBE>4XEU|4 zH3Td;=+59_HBbcTfKToWI<)j)$o_`|zD+F`1_F@p_$M7JBINd-T z5JDaP^kZ1#T}ahN637FK1I6li%W^P>sF4CDOrOtKNjeuCk|n8GK!gDZLj%XP1v1bt zl!4Fg|1XWqNJMiaj4?n?2Iekd9Kw$%toPw5qWJqr&KpIZc!RwuoTUv(Ik zo-y;UcYyE$7`~qI4SP{UBm(l`o-K?d9GTiX7_Aupv>Dk5;dxLeol%9wBTe)mE0e(> zlLjDl^x(uSMm5Hx@!G&kPoMzD?qLLDAYrSRVuk=*E?}_16+HnBKFMa3lE+DyR=~J~ z#m^{a)Zy}>c#3fWhfL8LWKlGJBvdSClwhWp-y=x~r~)bH7`Q_TBz7S~WI)DEJQAs^ z4WjE9OtAeT<1}Hn?B8hG1gPBr8GKb0qY_&wXg)^)~MRA$|3o@dJ|+k$}impw=)$9c%0Ko6&>uF>%O}2(Jv-mT2U131$O{fsPdBJuCyc zi;y7&Zuu~1(38fTia8LC89Y&E{viMY`k#gqmrBbnX*z#xLH!2-Xb7asn5jsL$QC%?LJW;0}2e7btH#>i1!$Z z?LgEog28?xCVAtSeE?~dVSx;P*_c_0eTc>y0j;S_Iy_~@{8Q(CqZCJ!W_UhCwL$MP zz|kI24v%5J!+HLGJX0Kf=y4V_o4{Te3k*25;!e zkVNn5;FdGeSg@FR6i-^DvB<_!uxBY#g;bkJ1vFC@h;tE0!$~WdSFzS;u3#7mVH&lH>cQU8r ziZ3!8?ApaF|ChN$JPGn%H-Rkd*ey035j|pNJgA?|9F6%9)AQQNM6Q~QCZ3YZ#9uK2 zu>vyiRUuRFFR=iTOF@UyKfyz~stg6o{L@TzQXwcTW8THOc`rsL6b%BUA|g=Ed_uZG ztd)*^A*TJzqu^& z!cLYGUx5MK7|6PUO(qH(LhOcMmKBDfP;rCaBYBq1FO?m>0(18Y6>}FlT zIVxIekX$aDyPt&*<3LoGL@=;mF5=sn%eqTu&YxukoSlodlwS&26LFt{VBH#sYC_0q z)*Q_GAK;hsEK6(#B4I_8pk*Ek#)TEE4;VwN!$h4V7$sD)%*cy1APRjzj9D9q3_sA! zs>CaVScDw>e3xY}z77zR!_W@aJsl!ZB|-DDd8F9|jsj_+mFkry@Rf3rs6xF)w|Hf%14QDdWvSqZlIrVnL~ z0niE-D8n8qdm>&j2;e3Wa4C!JKms9J9CumszDOw|3iegv~$25e=_3|Ye*nEz%`p_eIJoKIqX zWZ_A3_Hk?ti*al#xYLq-2KO)A%r%Ek2qMdR(5$F2jt7V2cSy=eJ@H_BOn3AR+@8 zIGZj0(3Mzvky;FZfOKHK1jTplT(%kx1Qg1I*60Ro3Ccv(K=umKGst}gAY&;?w1z{Tg4_1Xr+E{2jBWBGL9kwvvrMGXe;201%L94v;+<=vBp5`k(1th(f!Q zf=ld$|8O$|f@P?U>T;Pa{*F@Yeg@R6VSghh4N24H=0WaCOUr&f%x*UnG zf5o;S#iNDB03a9_TJ*3T$httlG~>|r>hvHx2`>@i3V}XljYn~l6o-av{?1;5Val(rTYLcN&ycI|MTL_OS91VDE z948c4JG3D(ffFZiWU#6TZwfcrbHuF!;{C28#{(Nh^bP{oqtz|InKSus;As)?kt-*L z1VTjVhw+4vyHhzzSP1d1=K1gUP;ZX%-}qBAIr4wKg9958|DUrsa(@FqiGWk)ih&Su zp7s|WJ_+WC3ts*Pjv|=g%n^W2A*yvYp&W5j6)nLEus58OLD~c+L~-ynsHioj03R>o z%)wb06^G0d7Q^A=v^apQoe|4y4(j=ad5$fmsWGe zV^XB422@{|e$Z~acl z!A%r^OoZ-$H*>_7gG7YrZsTO&?G6z^EvXy>tPNTT6hLhTM-J+xbHtlsLVE=K>ma89 zxO+Itkdnm_*LhT)6d(dJaYU#9&IA0|zmJoNBaqPVU>*lwRzuAqjv_Ya(_@@-Sc@eU zoFOvU^`Y21@W-h?TK3K7$^phn&TTS5)|YT@kWU>aIs3_h;5Y|2{NbF#oK0A~Z87IO ziGOyEGYLy`FUNzx@-yP>L&_+C`K25{QtOv-#9PX5r5y4LQdo0V>`%~B&cP?1&IG-h4E+y#Gd=h8NG5f71Saja^;6SHn?X~U65Q82Jn&R=^Up20_vK}O zOz}V8p0s#2++^gFIIre&fsLNgqRJ&J1&)2YiyvHjy~$sr+v#K4lI@@Ul{!|oANTDX zHa`(u8uFTZU_sT7i#_Etx!)JRf3JKs>n+86uF*H+>2}xl4jfXuo%D+8HI2@=aH3T< z4SmLHCNW~fzkkpNVm;AUzv(NuL)cmDAl_KKHg`-xA6h!P@SfrKL-iazn8e|V*Xpte zt_r*x&7)%vb|v$~Hh*!GCy(nv*GG;D_;r&f3!|xgK&~dhX_(^(w-j=5+XH%abH8GD z#H`_t27%{Ms!x#RrC_b>PH+!lyyq>FpE_LT#m~h+6MdeeF!{Kn_X-0gxvllvqbA(b zeH{4Yqs)%Wbzz#Tv%`1f3?1L$(6GAj!`6GW4G-hOZauY$`*`5V!OPX_Ddwry5A6L= z7nSCkrgmNCx^voOH6yh(X?|(v7?Z}EX)ABua+OgT!RWd>MIp z{pdAdX8@Qt|`Kc z&s*jW9Wz)HIPw0y`JX>jex7+i!&5!N|5!qp$K(E2N?X{T?&b*!u2;=`n=X_-AEUEk ztIh(G@q^g`gR0j@zguO|-*e35^TwlEPez~Nd~Q1(X4|f^=F5#k=7uv{x*T#I{Jt*i zFnG6c)@=7@-x}UN^R)vZUJukeO@zt1zVmD6n@i2-WvyEjHu<6MnQ82Z^o1va9gOGy zNc21UT43rYZEjF_D%Cpi=>s>-Ll3?d<>jBu&^5`-eeNsSnaRrbFbQ8Wp| z*H%h~m!noG(bC?i+`oG)jB|TbX-~ZJ}`S z`w<;ua~n>rcbUN*J-TOI`cIbwc6PE)eo71kj2u$$_>rWyWo+G3?d2olHM=RfL#*mG zN!3cl+Mc7ImyNhO6sDsT?(Hn(UebI!u2eSBKvJ*U>R67O+l|?q4)0R_k&xGR%@DN$OXp2$Bxlm*kba6MtdZ{9~7p(xK(K=peL2c%y@ zdzjP9c?o#!YEIxVf;7dKpdzU0lI!MfRO zrWxeDQCYE5ecG(Gqd9KV2Be%%G;e_GI(g13ozI0IS*DuH6IaZwd{(1rGigCw z*pwA-KMh@I?Uq%4`L2|qG-=WT!RyLT=?U*Vly>nBCRv_vZS(z}`JlgXfh{HZ&ZO%p z!jGJnU2c!P8`#SNRdNpvS>E#5(x04ReAeIgk)y&Lb5(uIuR6RnTdj<=J!WSADvHxc z8aF>^;%&NfUSy<3Khva^<#Z%CoO)AWHK-XX6{ZlLldYt@UEN~y1QCJ!!78aqGO ze*4U28R=SIi4yu$p))@UmCr-S-~aWssAR#X!F+Mw=h7lnI5%i* zn?suG-1RFu`VQag47u?@RXuT_^2bxv)g?EcJ4Kl7aMpA_oa#PyA`Zi|?t6s5}c7J^u#oj2O zy$Ps&72A?BG112Q#jlk9)9NwvV^nQoc z$8Rl@9+`d`_;Gvmv*Ao@;R}ukT+NQ-6U!ziG+-9i~b}iWQ6WXY|&4egE{M zH9y=c+>s}5V=ou5yhyIiEz-y|KS_S()tD^*(1qW^3tJN_RvcE)HN0dmV=O(*UHJ4{ z^0P2j_~Mz0`(EgjcO}eJ zkrN}T1a@QBZd%>>AUWloslnWumOFLnbz2>-XolvxdUgeeg*~lWW4Wf&;oyQ1p0`)t zUU7SQ*{sB^%c-1Ft}fqiwZ_WR-NB07#ABV@>+9<&H}C6L?5KB-yyf4$*?pUe^!uTX zxry1A?HBevyWmwb&-}%Y@20`w+b8<=37r=dud`_IS(e~G`tIIox4hWfrk0i$>bq~B zxT|j2&sCE{^UJL~OA|ux&0=y6Y?~*DFgT$%9JKdyz%I{{#rhgcibDE3hdJAn!M?z; z2i^3N&W^kKa{WkGW6r);8BQFVwcK}H<>i5sj6dp_PR@FDXLTR{p z-%a;ic|o@EfciDRLG5dX^jM;8H1`0Zyi93M$*CztKTr_%yorVRv$! zFea}OWnt)Dfg0p5=Fh}Tg%7zdYVubVWOlht*V5Tyb~jXi>*J2aE2mxQ>`KoZ?IHs@ zA9B@%H~RK2N_;MV-S~P$nzb4=ZRGVyGEo(~T_)6LO$oAME^9KK=Xprh+P9}(o-&I& z&(eHxSLJ3^+OmKf3mY8kubkahSh1*EdD-06X-u{mrz#;zJ8b2eC*|hb-PvFE1}UAf zJgQ!1q&8znK1}yc!>JcadND(9azwf&E(_jc68IXx^=xahOpZI1hf-M{_r zS~acBT0LrX1Q@JYUXc53PP1>RdHl3%^YjL7Uuq@YnjBuLyza=gAjNeDSLU_Nd2iM> z$?!wN#k|jcR*k}p8wVq8SyzvRD4Fc2{IpC_QaH`;t?jbxR2${*QyW0^yZdME6({b} zz94h&oKdrY^G0Ev3MI?P$nzS3Yuo^@Kux=EU%+&9e4~ z&vE(W9^-Di>y9|I=Is98l7aTy#5y1ce(ghtTu*5(zzu7(0I9;u?>XWNceGQ-=Q7cL zUj^)};Yov2jcAbCC*CmLhoF6i3RqvtL! z!nA2xO_gy|v8|8(n<-?m6Fs@c6g^D3LRY~j?sODLA7IZe+h zH{0CfG(8Wbz`C|ALzP=san^5ArKyaJd{(qMelP3Jo)d{%*n3Xgr@xmjpZDs+Y}Med z)f38DZp@F5nnL}{Rc)&$w+>l^{3vaVl9-#CK{xK-`l#Y*Ako2J$cY1s~Zvzhp2TI6Z1t1aihpPVvN-(M!ut{09fQ-5P_F`^;l1oupyuKUVc zeYw5aTe`0;13r}#8JGR@O}{;!=d@&xYu=@cFV;ncjXtc_P^|!b&Ck4Dz24;Q+100S z&M_@ZIkS=VdV4|ikay6R(8&dyj!t3drH)^VoxeI?K6p$k%<|MP^M$7I;q=^XXY*c4 z_ISrdT|c_*iTm5SH?hmUTv{wQ?lBy`@%ZEYuaO$jB|(uvJ^e(_2Iq2jfp$eei#+qo0Xq;_Mn5M=B^UW=-w;A z!qP7u3mV(6wAy;>XP*oO+l*Fxp55>MIMl8v;N#t`H`oVwaGc8uv(MuU_Uate*wq^PNB@d?w;6!pK=y%+5slKN9|@xZ$EJ{^@mM)z+xdGFUI&s`_?-L>4Od~UbF z3AyGF_$#q5z~F}>i>_o4dN99&?_swo=jrwEJ?eZ^z-5Q zOv?V)Q9`5Zb|5S7uB+>V*x}jRjhGfk8(e0)jqMIBZ1#B}y4E+%`*dnb)Sh(n?@}#x z*KT~;_5Q-5z=VX66MS`@Xx2VGnlI-}y5wHBGrZ}f#>nUgJa6+tzC~pAJ!hp?zB`BY zZwhCiKZr3*CypwYS3X=y88L!M|I-1WYwB;~GjN0@-k`f$;E4s?J8ag||NL zDj1kl_QJ;Avh!AxwPoJ$ms1CYRP)sROYX_d^X@&rWtE(jys=S5X;@6#4hV=zX zv!rX6$j4Tdj@<0)Q#JBhgyEgley%jLX@|lxU7TvExqBA+IID+p!c^FsNxA9I~fHmSGq-a5&@nlRzoK4puxxjplYA6#3pDtkc=e;{ss zrM-h?o6)VoxAX43R#dtZxNNM$K!tlkf^g!Fn%ONY{X^}&)6#Z&KTX?}Gv3}zbz$_i zxLIr4B3s&ftiM(+oV+VUNqw(jV&?0~+npXWbJpFrc+#SjSQ~Ly?s3h?b*3|a-VIMY zNjOLXmwUd!z@qf71GxvdNzc6w@%A! zjg;#*sglmTGVSZg{Ig{v^G6@9x>L$N)OmMM?eG`0JinZ~J}}_&H`RL^)_ZSY#SbeS z?VK{*dvR((UU)%&h_k^vgTN)nQ>TwA3F|-W64Lg2(_m)auzD?h@11hK#hLW-O2d*z zi3+pFcIMn2+gYT;%SigPs9;iTgF|-+hd$*xC3H-z*~Yr)y)wJqg-q^(Z#OTmyHPIq zyz_N{Tx0Qe;W<_I=CY8P-KE{@lOnuN?_E!quipAI*R416MqGc%&n*wvc3UKBF6@_1 z%p2nVf)7m86aogFD?R*=S8i1{Uu)p8t@Vr4?u59P4V3VgZz&;-=@~yPo*fEsXWSJM zjRQCKD)Stajn@BUBTh!qZ_Ppfc)kL;>HN`*Zf2&)@o)aK9UTu9VtL}P@rdTyizbe? zus`C*l8BNmQ_6z)54J|MC^)gGhh`Q$8`iDQIkVsR@yX+t8}g+1zeWgm?~O`P2 zv(I`>Q`^_;qc_*v#R-4y1_%kNg2bNy0ZZr%<4l$M_n_Mj%uG9-Q7 zD$cddjNSf&TMn5&R9o&GXQn)OeRp`@c&qPD&B9%KPmcM%LR()e@%!v0Htv&5pU-`^ z*%#``HKb{kq<)Bblo=edK&inut8P--%%YpVa@4fQ(s@5m?3AazjgN|{(0lGOSAOd6 zU1pKfYto)@-n4|3epqw0q$RcNm!A@Ks(xeZuW2hU7Id`k_&6A7wMLQZTz_r1D>D=- z7k%`1=1qIEuu)Fc`k`Cwmla)x(^PZboA?Da1_!&U+noHl_N;Zy&zloijlu%%EX(7= z=m1Y=#H$Z>TcWeV@&7$9p!2*6xMat-5zeX)JOK@-Y%yKCNyE@~k<;!m=C7L^I&I=! z{T^DsjvkX}r#fNW(s6I8vu9h*4OZuz-#Su_?iV@X;kHJvC04`I2V-aSh4;kEnjGo2 zao+c0$qo6hwplLq((e98)-=XEoL&EWS7O708&mWE=h4b1ik$d^h2E>%(vECaJC>u` zyF%!F&FgchPU+oPtrh9?Y{2Ru({60b_s@CX?`a%!dPa@SU2ScvF+-+(^rX@0Aw{mVF(Kg> z>L-k8AM56G_OxrVMTJY@FqQsdj@b=6@0uQ!8)kJ9_F%xCZ=mvNyxSOeWG5cc{P_Fa z3ZE5m7m#~CxRb?uhdUbR97G(OXkcm`=Q!D%|8tbezKTv#g*$opb0~P{DM$RrnNVL? z{8Od?-y2A-$TJ?hrST=As}(HDO; zz?+Ug(?Cac6)5$SV}l2^zeOAA$Sl;p`F0bvW)9us{l=I-G69k)4QF29rIXr0)Y&VM z6q*WlC$3?9x7@9x-@ID#>~5(Id_`GC?uCOR0;g-mB=r>(_4n^Mww zzqda-nDMnY{yG0f#5DT&h>*p6^Wc#CJ2QRD6AMn*_>BoU67=H!mK?>TXYHL{_Onui z!6BOEOZ!GFT33DZ558edd;59os1JIJf(-ZT%#%%3YCBx(^X!Ayu=F#_%{`+s z6yMAhZn-ySK=6_>Q)AJKRjJ>Fj*CaW8H$)ZGUbOLv^?Ht)n17s)(INUX3}0(^B%lS z&ks+XtiQxC-Qv4>aF(`8&HmrSnSPeL;*#yCPTr>d*|mr_EuI@uzr;+De@UCXqCv-b z@eSmk;DeCHyNwSdA}4`c4>=5|W5Nx_F>z`)Vq#Lkjly0C3*$2}J-TsFf!nt6@UN!; z5srQGJp5xRKy=yq8#v;x+R!`E*kglJ_2_#3k_49lTQBqSuq1+ARzLRvd6xoFjBs(F zdfeblf#{TkKR3JNw;0o6fbil?Hz481sNHq zx2)1VK6_V2{RA0*o70KbI^pBZg4I)G##NYK&=+*h^%sMj@prL#U+Rnl-& z2UEf$^W&TNP~RP)avR2u3)`1dZL61`b?@8mUET`~MrC>XW-sn3OE{X79J)7Qdq?jY z-$yf>t9CUVzuWGyCp>VOmC1gS*|~E2AAD9QQ`vED&x|=`1;Mk+`sSHh*erewgQlGs zD|j=1dDA`N&WElhwQb9G=D z@o9s}ha;BUKY!B3r&<7CqbmlhWhEaE{}$#PU4B1xm!Ow>+5Tx_%k=xPuXev^Zq5xV z82_PY$w|4@r)8=bQ}-;BZ<~8-?Hq~vQ7tPQ&ap}_OgHX(FFfJeT^T;@MSbuvwf4sG zWKYL|0FSnlaguM3pR}AawfhINce;{M(TH&+a{c$(6Al%gyT4`0*1`|X)S<$$fd>pi zvWje4JdSi#Zu{^ew!OE`BBymim|WBD244fme3h>te%_I3-mK$+^L;-@q&c5!%!fGppW>3wjXM{*@5OQJ(2%&q(zpJ3M~-E-m}PtmDX5$z zG4q>Dg4L-a|J7Rd_Z(WSb6H>0>-KE!4z@qg?^-<~_P!OX|9gH1ewsBjLN*I`$1Pp^YJs@rT9H5(BQyeuk5xQv73N#`|&HhBN&S-HFM2J=52BOuI?p{I!j{ zs=?U{@Xuz5h^6Sn1#T`pssq+hgiIe6?Rs%nrW( zJ6}-eq3i1vc6a8d{ho(wD3qnbiFP>?Wi@<$_-t+Hy`gPf+Rshp)ZOjA+MjWlm%7tu z{ZvnSQR1kat@WSPdkj{D%&y(MaPxlYW6=^02b$%NZ&Mt(Q8OH)++u2B`29#I{eYM{9E?gquTEYnJ_t;>p+{y4u<{zUwf zlEA8dsfBM6oN{`l^^#>xz2{sQH>gzYsu8SF+2fpWq5MfoenpDn%D!i^Gh?P*V5YbF zRfgv(T-p(5`pB;P)nj$g_3>H3{^ts9IrOf)Z>36B%%?8Qe z&o}|5>pU|*ESJAI?m|vTit(<<-4Rp9U5jw1g_?c25r1-O_Uv=>T3jAoUmD`@rGe}1 ze_hAFWqnhN$_xJsuFJCbzi_kBr_auy4tD>XG4_mhMJsEZO_cFM2{v_(&VS=ZvF5dtvQ%+bugL zFwiCG^U*-QQ|DNjWZQ4zB8qL8b>Sy<#E4nk|E`G;?XbkVCv+jM3R)g=Xz;NOZ!-Q= zNwhG+(C6Gacu^wOu=S-}Py9|a?W!N}*ub%lrz+H*cgfrE@Rm7qth(eS%#vJ-rl>oq z_g{5??8}gqnJ!D~2VECyQ~0(=HrYOZ=^FOaJ8fOgl+BCRKg#M@fARI>=&+Vg4hJtw zD%H(YQ`Fr#6_xMGgjgoj4bh!GrD9ZfX~H^gM(lJqhWePA@!F%djH*$Se^s$_d}XMW zmiF|*>B6p3H^6|9-&w-ap zykp;wDV&q{7;6uIq%RrWHgWl4<2KXJ%)l$Ed1m2zsGklezD|2NwpL)abn)P} zV&%H+Ddm&ORD@OqzK8NU;`ICOSG{}D2#wo}{mvA-RvRacIeXtDAz3Z4P+d$mv+9 zw)E;5wE^t~=M8OR7rg7ROTRTtDeexxa4j)WdTWB4m+Zoh@He4nZ{Hg53E5O-YO8$U zcK^8c&fE0bHD}6}#_Y;Jdpy3C@3kercD%CJz?H`%^gpZ{(I4dHx7KuhkI~v~I<^Oo zT1NNkTdw*|e-S@@P`F)KwZr-DoATP*-BU*9BxP8u?f*f2={|B$wplgYy>8s?N}j=) z%~MvZmnb*53v0Rs;{^P5SHoXKRn(u`S0F3(*~@&c?(&H3ad%#;Eemtn8U4Pe^$o>2 zQ%ic;$d}J)J!b?5)Ni&W74G$D!#i=mUXRH?H_^+K4;ms_+AaTSOAU##MiAqR`O@T@Zfbi0`imey%(&{^NiU zwc_zFrvdSqfWtGC{Igf`qA{!M$>MD#kcp1->h)#DXmn@qysngCA>3xKJR`<;gwzU`9|L30-?B=1WYk{u zI%lvUX_NWYYsTe!$Ia@UeqHalefwp7EoD2mgN-MZ1EOE>$G)4b@Y1F2bM^Hx+1}D> zUp$P$Ds=7y6tC~o9P*K$AA7y;smhRhZRq`@tnqIi>n_@=TV=4M{g}03e(;l<%952U z+`be)PrD?HEFCg^-n7~0&?f(mv#9|N2Q8jZt92zBmK2Y^dc<_xEdPV?kXT;~5ENbm7X|ph6%g)b?F;BlY8TxDv(0Meoy6e)B z=S3@B=9UNK6}SM_bl}$gx!tmaG0fJGrv!Onj}$XN{g$ zs)zfBJ5R6~t38zu1x9%lk4sjb(TKhj@_5b8-Z%E`f&H1|O*M*4V z{pW|?X+_<0>IVCt?yvbYaagt0ZHi_y&qv{qLtwmB{Yt@8+U0(0{c(G)XdKpyEuoz| zrx5>k%79&o&kUjPx!`KQy=}#ihV7c*IlpPKvP(nVPK=uPOa2*ePI$pR--osPOkC^_ z+t_FQ+T7Y$x5_H|kX;!)RQAls-3E`hhflx$_4co$4arlzYX_3|1dpjHaaq_Gn6PL^ z;Mw}Y+X@a>E;uLLsCh(Lv0?nKjN^;eTbuOgzR*a0XWp+WYSL_14%p~>5_KKU{Ll3l zynUb3iO+2)m+s=U65V1!uY)}C5$d1sZrC@_cQ>`ydH7O{ya_{}Vz2?{Jo==!S(-Ny z^B~T8XIr_d_=G0PCy4T?0yi8sIB^^ClKF={xRCC6P|!!0G+y zleU*S+^I4aPDI_tRj?>7Q;9#nxdY`JVKU33V9)rnjxeixm5!PJ`q z^vAqfcyhv#Q#Z5xPn>_cOV4G0_}KoaO{3D(7x$Pu|CGO+f9kuWhx^(!OYfvFt>jON z$~<-;VZ&mx!TAv%;$E%)-Pb3(Noi%r-uUNbsxzLH#wyFRX1s(d~Eu5y3CVa_ueaJPBno{%HB+^MN{i#gmM~NCMJX$KhNB$^D4AJ zkWrRQRaV`b)u+a%G9)AxUG;i3y`;!HaBTAJ;U~(Ck0TEBZjM^wmf7_*sAr0!?dprV zyHC9E3rv0G5m-3SGBx~W+5OyGb2|2?9aG%vs`I>~y_1n76+P|z5|itv&&i~G+&5^@ zJ$1Cl#oQpZPwOmWTRhVW=*o4xOj_0!G1TB`mu zT30;Pam-NkrP9|gJYUB*x4w^;7=H60C8E*TTtarj>rlgmRN<9{OSJ`kiXM+=O#aw- z{KbnEjaHs#4=*|~FY1JWS&;q_<;WI;mwV%H?4eGfA6xYDQE7cvSzDs;n&Gkey8g@3 zY-bJYlw>zH>9?}iy*|Fy)lswD`0in6!!dK+b-?o-vkgzx&Dyne)yv?+FdfPq+ z)5?kzFDnP+gr6%PvfN49m9vm5;f+G>W+M6G*MA$_K@TZ?jpdgDCZ_U zvQ4~nY3$B~H?y|FwA(E+&9pqrF1u^$)LTt5sb7ThH!|T;so$(Y)w<_jd%w*a>%*BJ zR3Kq+Q4nx4_;^}h<2d1rluo00DeJGe6i(S2X*!$}k?5Z`xb0m<+e>(K9-ik8Rt>VSt%=){h$b)yOT&usgvX?h; z(a*tL`5rjsh&*1V!n=rn1WB|A)s1-R7*Dk4(05yMFy;WSMx4wW(M}rOrBgv(1G+HJ zc*t9U|Lh8pi0HE|^p{M(h`&??L^Dvfl_P-_5%rSc{4kyjekbnIQ6EYkK6c}a|GEUx z%;KNp=8-ZX>UF-w^l0v*LEaJcJBHurFDH^fcuJ2e{#hLQ&Ts>U+PZVaf7FENU=ure za|Tyg*bsWH$Sd%SW8}MO+zn;dwwZQFN}AlUy~~`YaEHlUEKB=2`sax4%7v&|5E>4?PIokGnrL-+B+!eoSF8Nrj2|e z69#C@Zu$;YSLYxlW8{jL3X2GzCaAL*x?gd9Dw(R?% zL2Pe_+F4FkI2Q?|&1mgq6a#=9l+rI)zd0NJU4$0<{attjUhROZnLZoJ|mR^0*NsrYW;y&op*gdk0iHnq1YE{B_j8-7VDSScb1D@AfA?{i@ge8?W{2 z$n|i?xx7C!?`2OptP?o+*=IKUbw^Os%R}$)8zTSAmxpQGM^{n97p_BX1@;7tq zMg`4O@LU)z`Z~fb>V8pf^vv#9?}(`jndPBS3$w?|`-P10Si>YDmbSFeRtw;o`t zuiF;o$T7WdBxg3^^6QFIKbvhWw*E0a*=un?sKtWkvO>O756hYG5RJ@mi#+467GX1d z=d~oeSFoq6t^cwib%v55{^v}Hr?mBf5asYwi>(_B9{N}ldt|#(EMvMB(xe$rv7cGl z>~cC+>qf`Cz?;{$Qw|E{`putiQ5h?ts(VrDt*9KmU5aC(iO1`O&=>17gP3sUBOWML$ydwlL<3RXE2RNJt3b=(ym+4Dl2l<*93ZVjw< zkjKd#K>#Z$_BH>_xs@OV5%Hu-OOsnoNH%y1qGl(Gg_GmyBgYkwxH@`yMenz97ck^n zYOb7ja+S&BsgO=exaZ@V-~Lb8x{GR?UW_-dE4kFLtNe`goaOY$?a^Y!*U>z_uQ%&5 z80uePh&;p)+0JJ4Tej)yh+9wdt~TQ>6Pw=+tG){u`|)Fza(-#;!>Fp{$J4Be>wIk< zX|Y;z)#i^=E(KQvFRU$+doO3zeqrLEOXr*Tpwt~4&)&s1JnwmG^wn1QU|Od{xJA!C zHlqnX)1N1M)HaVcF^0#m2}T|FuuVF~QhK_UEqa3Ksi*g6{+9huRV7b;bqhOE{NlkSwz8h4knN{ia)XRe-@;mukU(#dZ~TU$#!QcJ`$s& zpDz8Pvuf5Qux0LE@~7CNjKQ5FO7iyds|;T{Y%)KoYi&6Ey(u}aMUbN1^=a%zHshb1 zvr7{OO9gy>TXVVh*Gy^()UT{`B8R7YnyAT`?_j^XPpfP~CPDgEhpn31^O~kO>oe*r zLxz+C&MDcR`AFIA)lU9uw}wMGNh?pm@*i{is(vT#Fx5W>DOY7$6Q)=ig$R6KbXv}? zKKwZ8s+UH1voCL7Qq9z6u^oiL3yzgr=7>>;xK0Zt&N68JDku{;oIibTq@z8?-TktS z7SkPtJ;ze6cV#$!xZO&aa%&XWd%j+%?arIj(R@Pg@!_&2Bg#PN`W-RhJxW{>&r;W` zcL=p1Jufb?DsIVBvAgOq##!QGHR&u?*}-x`grnv#-{{&Cn-RxT4tz)24iaCLxSyLe zI<9m7S65}}^J6ixnhr`+ZWd?WR^&XW*}1UEQrOrx)OB0Gp4yApJjVR{@mp814qo~_ z|9op~}|G>=v zo${?$rd&4WI={GA@}l>0;d?)weCOV@)GHAUi}vFpH!td%4>bl`SA|pB6gvz3_!K^9 zU;ZL$ZRv;X-+RtT>a22arq>7XRsrL=R%rugO&=M*F8fHBhb)0FYQWw}7_}<{FBjZ?N36nslj^%m>ipl60!&do zFS0V~a2tGJdZ-i}si)ja#6LDiX|D`|&6Z$!45=3I zep02q*M6xNyi9n(iwsv#tfJjw9;B5G{{y_f(tM(p>{;sm0{K3i^`t}>(Xa`}W! ziH58#e5NY3*9Ip+dgGvAPs%~5^I&9_|Gy=TF0 z))j3`B)U-U3bs@1^*`v}J(zJNF3q#wgyrxdcdh2h%hN?R0&b@bNPAYf$93ttZ=Tez zxpTr_V}Ad&f!r$O?yghAswVZtDEZ6Ph0Qa`alIeCzfZs@|>qC z=J#9Ox>>Tta(S#z7<9rCHK(B8(!?g4jsKf_7+ErZGI-unZSCtYKj9T-(q%+~DE&N{x7?90u3 zdVRunNf&G8gjF`8E-&KcGmS!~wI$_yTrTHqQ^}n<{B50^Bqd;53Mt7s!uam(s6~mz ziGpMDPF6EM%%%iPNh{YJIYKB=~&Ia$rXH8}_nN2y1hYj((oA;{aN$fwJc0gm& zx@HsayDR4t^$WePemEcZhNXNl-8-i`;1l_cuyKgR1BuGo+aZnCr~Oy&DCc?=nk+5Y zf3!iuI_z`T)S(7tio*H?YttMx!<_OhVkiCLyB7^D*B6M(aCOUkxna+Bk7?+(dJgdf zll9K^ml9;l{=~n1kol5Zf+GHhcPt`a=(2WG|KXtG9UIxL4IRou*A!Lr9-R(V=eOR$ zG`(M$-^#fOv1utkeOzI6pNl!EsO7PeO{})QUjceHyx!1UaD0R^Akq4y#YkHChFR{f zTe`2T$ifndYp&dUlh_nh`)(@A;7L-OSe49( zMe!|}mGf~g+Q&k4_lIVv6>Ht&KHkxzxo1~)`L zWg~B+d6+>B?@_-^ilc8G-i>gmafinj?!D%{L%uuX#(H+23qy+Atvr<=DHxA_{b}Z=4nHSy< zVM#UEZ>Ln2?@?0!`?dPu{z-H;}eY$?xVb2KiYSPT}OD|-d4_i z`!TKBHQwrK^Oc5A^D|bxzoOBMej*QxvJdIg>exGe<>-TcqVS3#bhMwSK;cgFPn$W= zmz6kenY2MCw{!79En|jl{WyD#NNwlvt^tNdiCU3&OSjsLPN^*l@2*+Asj+$J`@W2{ zjMkFhp&DiduV?m^(d=%1o+t%@EAdKBn{LHZ*L&-Q(8rTNHq{H%85MZ6*aAP>Z@ipqka*Q=c2wuxmY$KNpTDLxg2elt`zAG?=8t{udt>}&ururR z^;Y|D=Zzb#8sY8S`q*uS1mkw*m+VS;`j6r+5l-E_C&=M(;9*r5p9yPKX;zRQQF?Nv z{BbL;_8UP`loe7YE>@4Y8hz^>0t*gE+~r>xfB(rus=!b@&u81m-;Y!_DSZ~n{y3E7 zW%8;sAgjUbsm<&si{Wo>=Z_|5cTMcbJ;P#O-eu*}^4;w@OTd`V%HeM($~TqWsy}Ic zNJp;xqoWSR?dJELF5tVmiE}TbEcvPraLAOi*Lv|KMQ7hP+%tV1%b1F?dbR4g5_KKCMD+WuO28!>et$^P~0KtH_YWd{lex% z_%9JYm6~lMU-a4cw(ALM&LqoJPnGR4T?s1Htim2cM$CH)!X(plU1jIDzjM;~<~sK4 zMT&E6$EGAL)}s@YeV^_*uC|G3QS$E?Ak=jjsD88iN%D?)yf)MN8!77R?z6@``8Gqj zO^VVLqQ&|PPW*{EZO;ocihPsU3f{ffO^n(ie?(mDsi@StLAIl$%kEwExo@ zBK0wO_?)EN>qFxF66eM%0|8VbnciWQ; zz6mhY7`3lh&zqH;_c{5Dh*>RDvo1AecQIZlI05Qn-~OlK{a4H~h@t0A!KEBt4TQUR z?2-18bMfPrs4k4|P5~zyF-;L(;m-{M$Xtx{->A}RQOIM0B&G3|nI005bwe>?1-H7Q zt=`MHl;aWn%94utP7lS(4UA7Vl_sp+&Y}A-R%KMYJ3{&C>zC(_bq00>{_zcD@(8sf zUgQ75U$ODyaoP6{T|S|AawIr>%_S3C#n*0?j#S9uYsftv^{VQ{`?=Mp23}6_?|yd0 zw9@*NHp%3f(v>flPg1%MFiovHYgK#N(8fDQVrYL@2xTH?ZdaA%;yD4Xn0|R1%^oes zh~8tSIVD5EVq@d=M@D+DJdd{GM|cWhh3_UqaR_=WmWb%+b3>6zpX9uD}3yhQCr8Xs`_z*8HMZS$KM6_($*-1 zh;55vmAJ4rC*p0y+tyV(u8FE_4^We0adq&MmfgEmbf+m&UtBAy9QNHLZ7E4fJy1*L z>G^zllU*yi+j#GEr+vHZk?y+Y7Old`7px8ri)3Kf{6B9^{%uJ`@So((;7%OcS7zXa zUm`kl10>+zZp06Go}&IDfP!BH6e~-?t#PgW!uod`(^n zK7hl2cBr&fB=O^UocfMH<)2m}JTr{;3nX|OZf)gFB@%p1l2*UWM2Vt!%@mvuAcoG) z5mr&KT0pv`i^Z@dmrjE^tG*+PSg3m3Jx_r<{KiHDsmW5OT%PW&;Ce58Eu1}G>Og|s z`{bFEsi!hD|KWVXW72LSlkqECv#wvA>5x_4^=?1^1>upiGl2_!qq={V<~4#2{g!S_ zijSMSb9v9vm*`yiW%r{2>nF7Mz-Roj6EYM&-S!`b&84gX?~hbH{;bj{+bG0*K9p-^ zSG&^9OpXxNqqT*ZtOl%HwGWk^Dm8o-Z`?)TB$O&`HUB2H)thuKiZNb{C@$uAOzFM zuSzJF)Xt5yBwSK3xjAs`M^L_qNYa+01w5CADtP#JOYfG*Q@s+f zo4H;&*zZ+t!khb6<5x}GpGp1{Ky9O$OCp3~-*L>jxa~S<8*f%xUOZW*^3iNS>ENk4 z%j1q`oa2il%mr`k(AxLN)8g#NtA98)9ZugBch__(`Ho`Q)L6H1)Y2dMqJ>9OH`1&8 z@_*(XmpNcb+11`(xVFM{r%W#o=X;6W?WJo?4-Q`O50Cv2Y_zU?pR=u1gYw}l&k_Dz ze3RA#cQ_lvZ33mMckW|d2tRYFE$LIWgl1LIN0ko0!=CpQ9X}0N%6av*9aD6)){@uD z?{T|4+*i%MXS3g+q1}0Hn_znz#|unDCtUWow+=mXec|a%DHP2ZRFzPEew#3x9NHHD zJBsr15GO~{ZO39C=MbZUXx`)$Q>hyb2giBeT(7h+HMza_G%ENZOeNv{o~?VNrP8F* zd_LxQXd6^#(#x6^Z&PnON@92u^vd%IcEEOIb1 z$Bm`kYjAp?q-GoClYvXrMwZjIzIV4xYP6V2fg=al8ik^(E3ST#Jyib6l$R8FPl#i$ zb514OiQU$ucRMb>e)FC0CzB!D)%(KPt{l~R>;Ww4rx$GegZr}&4UMgOtr;Ae^Q2@_ zU16GaZ|>>m2X_=j`lQ-#o=P#Q@-`{m8`@W-WuNL`pHX#Z3-2ci%lD|!0iS;+8Qj90 zo#!?3V<**;b$sqkImM)hCLbA2IWzrmPh>)K9#f-jiBj!ZMZXfES1cd6ADbL}f22ZJ=)Jd{N8U%$hd^{!3JqZC(GmZmcyqH4cnD19w+#G9Dg~x##{Tu zm|mlz=6A#@S+cCp;w4*7f2GOwuUz-mg;a-R^1Pp9{$|m&;)NrEYAJ`FN9-UhADQ^|`C_g;dhjYVCtj7ACsi97O~pgxcv|ikczFg#CEBW0 z`EIyKS+m7R>{2AxVPD;gn?_&XXnuT?t0%=o?~%FU=+15d_k6L55jBoYc@8A!svn_I zd)C@MHe~Ks${yG3TzC9+ZLm{7*7&Mqu9t6~YyIBYBE{?`FZ=%Ux(S5y0GGFN?!yK( zp1U04y%S$KzVuG8Eqq(;-*{WVE9^<0RMJ_Kyi;y!l+=r;mdq0G|F zSJNn2z^3Wu=+#`&7fp=SH}6VZlH)kd7WSCCZie40d46MJ*IyEu|D1jpeJ}i^FCwZ$~0(g?VPZ#7?UvIaI-fVGp1Lv*tpJFR^ z*X_A=d9_E&UEAz-{@q(@W)8NiZ8m=U{@2^Q)3t^9zqe}WRNPa{lH4TbJ%8-IwV)oy zx@zXA->0+L?zg&cw7=_Nie4?9iupPp-8DAnbD>_-)S$sBRY*)BKBl)qnX<&GWAr2K z=u3@J>GvvI_=A^%i`MwBPGv}?$OWl5zDSTQjErUODqAzuoWx*_=((@)>WDo3X-$?P z`!_|!&@RKmo;6c9d0LBJY(DaAZgS0`!=V-p#9W;)l|6~2&zN)@r&p`i@4Dmu%0RD! z6xH*d)pwwoot^ycF7MXd=ik@@_#(bp6)%PCD=4-7$$53hUhgkbZVvHZ`fq@1Ax(+e zlw|8Awn3R6hZO#>&a(?!9)5X^LfE75IX}UY#bd2VWuaxjONH{*Iwv=^&oPUmY#$0{ zb+VJIDl?;*N0rC4vSNOY&KW11^NKYu$oXM0r~KwU=QfGh0=0W%LteGY!7@hM7>~#= zs(rciPW~Sku`fAlT(Q_h?bbVH4Y$B#N zZ{$ahr{!mLTV*N#VeGy#=hk(`{#;nM?r(!}5&l1MbJD}xFHQ#(cx7>4pPn?0{qdDO*{8jTD5hCqdzM7+*Qk zDz$y`Bv`4XcCC0wFvK6z3IyQSXHJUE21z|$o!${uH~-W}x2MnVlstwAJv(Z@a)Ztq zecxL%>yqx3jMT5ueZ1lGYbR}_Ch~I`hpusej&$p`{a*roOgn!n&VN~4Sn-fidiUj2 zURC8&hi@fqT72iwE`-`SgWSP2@fz27s; zUtaqa+qr(gt5Ru0ylRP0^-;$kddg!T&9YVA#+GC!sj6#ug$HEsuGaUAD+^oFpzb7t z26>re{J;i8<9{|Fm7q#ricfR26+ZY7YQ?`l{|TV11>hU4!Z>0bZhSB|CgMk7g5MJ0 zD^y^k$&Z2^gB13azmf;h8Q+qCSD-9#dek2T(D4`4EzK!%1nwfB@vqNiD#7P6@cb|E z76U&z-wI9`aZRMj7ou~cRG~82uE-w98=@EJ}>JHo3&rE5oodGkjaG7=heq=ob=(Ai@u9{ z*p9TRui-N*AO3d1^v9*|s!{}-H#NkR_}Ru^xiim?D@EPl5w6V=s?Yvf7N&c`(shaU zUE}vA=^cE)=+WShzUGHg_xqLL-4i$5s32_NZ!a__kWhuYOO(1zM1MX8PS9UY!ms_U z1aUox9~=mrKmd(+21*Q}dSnZn9&m>i3`D_4OvaUDJK`w*e{8R5PHezmMF&4X@mGRl zC_+|{Rxolg{B>}a(|NKM_J(ZEdGa<4`goqKk3lLFvMC0IQpl$;=sShH4}(mD;M2?? zvK`j8;sW^q2H9R9TVYVu1@a*b;tz&UKrq<_Ynuut2Vsy~2-z8f29_a%P_i}FmK93& zz@W7k$xaw_{v!D>2EDmRcEBK`FtQ^C6^D`SF$f8VPra9+fp9W*ffKqhf^3Q5#YR9m zK1Y!EVr|Gv9RyiSZI%_Sn?4J&nym(QM(Mu#*^K#r`O`4)>bBv z{jjzR31m+U`kny$9!`W%XCmZJH;H^2!z)iBpTHoAE3obA6{zv0D^Le6$*`?68IG%g zLhYuYP|DvZl=4^#*&n0)CIx!V)~gUN=PI<((p9Jfhg4|6x>V?sYp=n+p4XuDUtEKD z%4v|Rtq{@Q_~Ex1qS72kZm!DITMa?A`|*< zYbMmGS{8&dvmli}S+K{^8xVSO1CA?|4I?Tj8}c`j4e|7I;24=X&<|GU!l-b|g?K%= z(2LjSK`1m2axjqxEwe2jwq@r-daMOdwi5;L>5~G;mE=vxLGUs(z6@M3d z1!X&O3-Z@`3)(}u2tJK2g4Ulag1+Hi46WZ?4CRn3fxZ!10y%wC0(GTV3bm163S)u) zHuR0a+mK4%Z78qK9cbyoJJ7x>%An*9Wzg5E%3v%=--V-v+=V@c?vlMQk1)Rnbx^tt z5zArT@F^#|Vt8-Mp&YsuP|BPpKU70{4mD&StjAytq`adR>aV&MW+cTrn1?d!pnciup>|KzLn-^} zVc(q(VD@Q#Kn}&mFntKEU-=Mvgy17cFZ>aF`uP#0XVm~@t7w23M6?lR(bz_4!QYMK zbJ!T?nxH+tG(jrXkD*ShA44w@ZH8VKx(vNvhD=(ZwQ5@+r(&(pV*^_uSHrE4=WS1* zZ)80oQ!rj6+n`Pp+Mr&T+aU)h+hLE^cIXk}PvIEnpTbxjdkTGHUkCKvnhw~Px08Gp z8!fRDYI3O)%6sS;gzBF`o&~$0*PZWz+`sFB`a9MQwbt1Ub+DxeQn}s({fhND)QkIb zIDd>UL*_4FTf;J>+6&uqddX)no`w6!AsCd~2el^KPY%G^3i_d?c?O^+FAhLn-VQ)7 zwi$$+whzKKy&>55_7IeuGz=lvVHgW7!_bT6Uc$baFJWKu2-Hi=2sseriafg9HVVgG z8ihUF#$aY>8iO7tI}U9bIS$)Cjg!MMdcLnVsBHr3 zWz!p|my|b<9^oyN_rzP+qvtJ@cjF}V#A}n#4_3Z|9(HmW>Ujq(t??dOu;4xHOPYfI z7df>o+Y}tb>;r`EE<@bYu+487dN~bsYV#3FUi%TQ7`J|cZCRh-xT|L%sf|0 zX5kpAvp8PzXUN{s&oFg1euha!VGagQ${bY2iZ2j4@dc{BX}L||D`X&X8T$DZs?XsY z9IfdaWL|0>8XxHo@>}mLax($h)vM z>wn1R7*zTX3^LXwNX20Zj&XO1yc@$?`3LI7^$*ll#~(bkU(rM`a#vyzY|C-w1A?!{ z^j0AFt|4Ou{t#VUhW0Ze_|D`GBZ9Bw5GDj)*PdrW@cDRV89KBQ!S^0LD-nFxuz?v; ziDHJMO)(?*u0eMdY)fB-7-Qm0uR^wB(1z6rzDG$~4e8CTM(};r&NT?WqQAWc*@5-= zwFbd=A^TV$UJ(oI%fgBnVR-vlVUJ8!L>FtDSZ-5fLkzGsA2!Ir5F3K;roaLI_-^kC z0ph(RAoyNToCqnq5fL4XN(m9c_l)0(u!jx_!Pgq`B&glbB*YNwv6+l)!JuR*WGB{xl^t@hpB=$>U3Kh`@+uAl-^m&-L!lf{_dOiQc8rQ3Cu}>x z336ycY6ewidP}E<=6G5Fa-j&6yio{}wmo^aD54 zZ2eIK>CsO8KBfbIXt_KWy_^hAR0X zSKs-e6|@BqO-wKT0tg=3Z32)#RzV0EFGH7>p*BJ22f{*7HWwjiw{9V*Q&C~~^rSFs zt6pwn7J+U1L|}B^T80=!;Zt)_7>&iE2$rmp#SnZ;<{*aPTj{oCNKG7u(PeRHq9Jjp zTp0+?2FQK*GBmjX(o<1_ zRL&_Oc)4gq2|it?4CReghUw~uGSrK`3W67Z8dVUyHf@Db!LJ7?d+I^T zy?T(cnm(j=TOZQnHGuSj4IsTY29TcSHmKdCZ7^y-ZiAY%Fobr?H-tX9XbAOUwH@+O zx*hVuxdZCp)DAdC(+=1}+z9sYGlD%vj9^^u+X*MD`#T{OE@MdLgfZlx&KM>nzFm-> z$1>Ei3<;aSr#>byYFkWT465yhlrQault-2!B~#cV*c3kPHHG6U?}3yfmZ7m_NZkxR zjW&Z+#>^1B0BdXx{jbm*>Y05nf^W{E_QE*&wij~hXaQ~c!~$wfVIS00!ZNh54@$Iq zKeTH8e(0TREFt$tEfKslIcN!ETKfPTBj*6j2dq}m3Qkrqj;gI-?p${e@|S!N!Hcr1 ztzlm`YZ#$z)-YxyY@mmovw@WRY#^ShErjB1p&XO8a9k}r$USNY@xCp$nH_=|so@ZO zDq;_NoU?}0(47KZe7{*}xVQAm=M-UqtmAR_}9pWfLrJQpFE{vv+K)hW?A%8hXAr&Sk$i4kC zRO1BWi0>Gb^2{-)$?jvYhk`R~i*SZ`Z=K;&z2i`)$;V;am*X(bx4S^yr@25Wms}vd zgD0T>)t!Lc3!Q{deNRHl9Va0@F;^H1zOGRBUCV7UZm@5#8yt7Q4dN-Cg4&2Z1>4@8 zg7mhy!(~&9J8XOF4#(Z>0VyYWK)g2|@aZN`*cRal`;K|Sr>du6-!^h1y-~3q8?h z8Orj7S?ZfF^e`7qs&f z=)2}YP|9mT$W9vNkXzB2GpJrLpv-sy*@)58x&W=3d;!_BhC1)3-oNk--5LyT31Q5Zg)6hC`uJ*|0QROVelHREcnTkumzRyxA`LA46?2g%4%|Iz8Y|fR5g!0`z<&vJN9M8wvRE zx{0z~Ld0kmT9}B@Ml+)jZbscgv?UUe#PBky5{wq2wGps|pW1S)2vr9yG*z&e9_<=({SPg>N_6XGWIe|1IF;MLD)cwtmPobV@;zzju4w4l zW7$9enQ8(WWJ(r5!A)taQQ;gYMSBf+xVw&kx@{ryCf6byVpK9B-%aZ3!AF=fK!aR# zOolZP(E0m_7&gAhRnR-^DFO8)5c!e+8Xvt?3m&U<642IKM1@9pd*b09>K!M8;7^i{ z@PTIpboLTS3LOk46KJ@vf)G;={i)rC7`!^5MJulaGaR>{BHvNc)cf} z=uJeN*4;`z)blJD&jFB7q3h230hbwE8pnoKi{T9ZeS;T$z|Kw+P;FTf@Bf-**HbWT z#SBg_?kOUNv13yQ+P{A$pn4C90_d%)2q%rFuL(odhJdF(;7^r~r_ygY4RHAvfo3?2 zknRgWX#aQoVdx8-(4yPbmO4P>IS@{Pj?nB6fSXPdLEU>uWb|M*F5KT&R*BMeSpAET zT!tvn_}f$2_@Nq*odsm+Ro?KMrgA)39;5=UT9@#Vex)M9v`{ng*pspgbe{%)^eXpY zK*h2VQ8b{Fz=ft3!jQR827*q25lt_H>8ri~w8*YThYAo4OaqEJpd}AnOMeqtw|obd z2yMJcO(jKc!0JcvNgKUOMX_P{@KP+?ERH1z{|a98!c{~L%_~7TX;U5TCMMMUKC+fc zi;IZ*Q{U%3?h08n>jt7maud-zeFz`AWJ2cnSE+wrGsH`$R7qYU>M%m&LsR<6cseX? z1af5ZEz2Q>Zpnj#)KlH4TYyLnTV6DM1sG&GcFnR&UDg7HMu0zhw`>z4qJ*cQLtQIE zpeZDd|N6ClfVT=LM3$Z*5!5pakw81DiK~;_adeCL89y1IKQDm3fezhV41IMQ*64Je z1MYeyh^X~*M3pwDP%olEJ4+F6rXx~Blu`?(|9?smP8ts*CNW-t;0QcTFniMHB~e+b z@S@ZkYOvf`tr^oi3fQH(n-bmlvx-DCz5+z}*fa2r-9i$=Poq66O|+o;=T*=_imt2DG8P8MhU<1V@0znsJqD?kCXS0UTkpoo+lcEzIWNRvc{J$_|WO zKMsb|2JF#Gac2vvn*uaMy(b6#@CYH$w9O9g>p=JP0d2q8N<=e!$%5#wPW&15_5hUq z26%>UBcdbDL_xGFlZ<%-q3zd}QozzCz!LS#qJRJS8&S0d$aKI3%@UYj7%%R^I5nt4 zxM)iJ`&@j9f4c)ndN&ard`gg{`VtNG?_=TVQ%9jG5q;SS=h-@ts5QXXo(v{C4Gc{! z4F5aRdF&z8gn*1c4eYjsZpa%JRPN-_6XlTU6EVOP@m551a}z;`7HTw8G;C`zYyi3y z1SII)+U_7tZgA*4KI=Nb?tEUz7k2O3v6f#t>2ctiwicB==? z11B+2-8G41w5cAhw1lpKXNn$}?Fv%yfcxjG^-DIupe4|n9-YS8Ph+THUR9+T$+UY- z`w1YAJV2)pI71&Ib=|^8J&d0BuMEvr3>YV#1yMjC@+qkPS3#aH>u(kUf*$}u9lEhA zd@*059+;21+{PnqVVh}*EDHm}$+iDo4Ym6dQR%19F2%`U*o~n?v@IEt!W8r9323@} zk%&4bA_COY^zoc7+zi_L!~bQU9d&9(IBAhXJu@CPEdtSzeTj&6$ACN)ScLFk1VuVQ zN8cDCs@;i5U`>V8*tWR+Z|)JMsZC*r^QBwBp?X1DqhDwil@ZbTdvNk{O9S1rDu^h% z0?=f}c0A2qEHbO2rr7~NLY+=YviGUxS4Qnq;5sHA zk#^A1{Tv$tPRgrM2athjA-ny{@q0{QupdB3-M_?RV0hiXJQyb$luzQJb0EX9`2w@r)#EeC-oCF=rJz$YAWffiXI7Aw zlN1{onT&|iM&a4u%tYORApbMZF|I_FL3Gj<;51a9ZrL5wi%TK^L-eyWaA1JSM>}2* zJ(K`;Pt^GrPYYl*<yoHrFAFG?Vl-X|)mn8Gk@#J)K+@c z#L)uy(sZ~HL=t+v4CEx*EfH{KrbrF=7%iKkgP@ZT=tLjzJIJKJ0^ZmJo%@HpmNroM zU&o`;uSO>j5?W$P0>V{8k?O$g!_>=3LdSav66iniuw9O7?kaAY5V~j>H23klES(jA zp8>!R{R%0T7xidFv}hdUJdRPMM)6so7BM=AX=_mC%{qX58~ zT)`~8LLW7+Ld2+>ur<`vUa_UIHuq^4s@t#vPUt6sohle7;QUuSDU+@Pk?*Tfn>Eou zO;Bk#c^w+5G!G2=dJ_qKnulP??C%3Kx#?tN)lwiK8U`020+ zTmSZ|ooD<|ga(XB*e(a0#Yy^Wqx-M|$ZKYTzN|nLX=YV+`4$m)r0Pm*la*b0K(lFYV$pXzG}zw zdRLFa7iyKI7)VMVSTdd@bTST+qA|>y$UH~AaMlE%(NDMAy-<}}M4ra5Z+?LN2*4T! z!_udNGd?8LvkPCTt!`2%^95+s(;w(L%Rfs(#cv@JG-})#MN;ViWgkd=5_A?l;s^9R zLy$zb#=#w^X(15bJm4>%2gisKY5JiN)0df6u>@nT1O(;jh+R5QLhGf83V+#aI2RDp z3_|Cs5Ct?m7<{Mm?{*f+1ct5&rimkn?#x7ZY4ra-agbg?Mxi8Zbp}o-WkJbT@f>lb z={9?Tm24`5L6R|AIbM4{@{xC8xt1R8yBxfoQh2{&$@v*Vrb zK-gnIaQaf?Vmt|L?uE-tWw54U(n=(ure(P6MRxpPr!J~NfP&WiLJ-_aKAaE88bOd;{A>Cj~`83A1qrZxv+P3t9(fA8767VQC0UDjHAIp9_ z0jBc-E&3IOLJ_bn*btx!y$F$p`}c89^b^)#F$py(z>9K!A3{W5%=lJDLT~pGc~Jr< zWNoYmsJ#Ck&5d|buIF$kut>G^*9wef-5P`o)7uAXdO1@=!iuvZ#$;i1Yd552o(~=# zs3)PP@)21K|7ikf>U}_ygd6>nfDmb6oIL#b4Ye4c2BfB+E+QXMhm=S6v=G_Qm*-(v z6#;K&v}(lJF(ZH(TZ3ZPW)rIq9E2)bN4gWkzR)-uivqk4}(scr!5 z(rKjsJ`ac9eOgCIXjdBip+SIJ7&tqQy8^Y+XGfj??%jJG=0;{Y%QDYe5J4 z^uYCvM6D+3pu@prR+?OYpAJJmDX4v?WkziKF3*2UxeJhN1tjU$?qdtIDOZ+yTnx6> zaB>Q3p-zm{350$}#kNRw1pMthB1kq&q~Ca|J{bVco-w-%cWvpQ^vYzn1Ok&Z1<`O)|Z~MBG zpbf@wpss!Bde^TZW7YDgN?dR918Y3LfE+6hc5w8Q)NW8y?f@edqhK*sQh=NJ?}H`i z=Z`2}GPQOnj2?W97Xq@@I;%YfQrrQ&$kFj}R0t$LAh2ROto4XdYY7)b$XFS)wFedm z3c)1KBq@pO6C6e%Li3=%zvZUS)g01fG&mEH#)6JaUH4n8Cu2!+M?553N-a=1DgcfN zL>^8hMWz4xKk5k5B_nYIRR`ERludOOx0Qx4i?jL7J%azu`%?9_R+ z>$`yswEz?JxxmO6_ovQV2>ZWfT#V3ekRh3Vnvl^G0Kl5AfuDC6&CD>a!FSQv1b#^V zRY524lNm4uy@31{p*1zi#8Be|oJ8n0^&;x8YdZmpVsx(DWka*CIQju> zHficgZnK?_1i1SEE`3NP+mTUTRq$g~>N7Z8H&`+;owO&TGmr5Vvk@D^^CVzuJMe=7 zJ-4HW$>^&rIG@{t;vHknN%Y(^xQAMN1Ze5Hk*Vc=A#|Affx2k}9*gr|z4lUnLF)xf z>Cp+m>P}S`UI}pD1XelV+vUH@TSyiltL{lg6|(RIqbIcfCUv{c0f>py5#tE}4$*>0 YaWSu=wl^>}Frii}7#;?KX^7$f0K34gYXATM From 7a7ab4cc7ce62cafbdf0f082209f38dff022d4d1 Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Wed, 23 Jan 2013 23:48:00 -0500 Subject: [PATCH 0322/1949] * Fixed a compiler error due to wring rebase --- .../main/scala/net/liftweb/http/SHtml.scala | 31 ++++--------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala b/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala index 27bc060d13..8cab6bdbab 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala @@ -944,15 +944,15 @@ trait SHtml { * @param default -- the default value (or Empty if no default value) * @param func -- the function to execute when a new selection is made */ - def ajaxEditableSelect(opts: Seq[(String, String)], deflt: Box[String], - f: String => JsCmd, attrs: ElemAttr*): Elem = { - + def ajaxEditableSelect(opts: Seq[(String, String)], deflt: Box[String], + f: String => JsCmd, attrs: ElemAttr*): Elem = { + val id = attrs.collectFirst { case BasicElemAttr(name, value) if name == "id" => value } getOrElse nextFuncName val attributes = if(attrs.contains(BasicElemAttr("id", id))) attrs else BasicElemAttr("id", id) +: attrs val textOpt = nextFuncName val options = opts :+ (textOpt , "New Element") - var _options = options - + var _options = options + lazy val func: (String) => JsCmd = (select: String) => { def text(in: String): JsCmd = { _options = (in, in) +: _options @@ -960,27 +960,8 @@ trait SHtml { } if (select == textOpt) Replace(id, ajaxText("", text(_), attributes: _*)) & Focus(id) else f(select) } - - ajaxSelect(options, deflt, func, attributes: _*) -======= - val id = nextFuncName - val textOpt = nextFuncName - val idAttr = Seq(ElemAttr.pairToBasic("id", id)) - val options = opts :+ (textOpt , "New Element") - var _options = options - - def addId(elem: Elem) = (idAttr.foldLeft(elem)(_ % _)) - lazy val func: (String) => JsCmd = (select: String) => { - def text(in: String): JsCmd = { - _options = (in, in) +: _options - Replace(id, addId({ajaxSelect(_options, Some(in), func, attrs: _*)})) - } - if (select == textOpt) Replace(id, addId({ajaxText("", text(_), attrs: _*)})) & Focus(id) else f(select) - } - - addId({ajaxSelect(options, deflt, func, attrs: _*)}) ->>>>>>> Adding ajaxEditableSelect to SHtml. Allows users to dynamically add new selections into an ajaxSelect. + ajaxSelect(options, deflt, func, attributes: _*) } def ajaxSelect(opts: Seq[(String, String)], deflt: Box[String], From 0c6e61f5b359b9e5424b5e5b730eca4c1984225e Mon Sep 17 00:00:00 2001 From: David Pollak Date: Thu, 31 Jan 2013 11:10:49 -0800 Subject: [PATCH 0323/1949] Added clearAttrs method to S --- web/webkit/src/main/scala/net/liftweb/http/S.scala | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/web/webkit/src/main/scala/net/liftweb/http/S.scala b/web/webkit/src/main/scala/net/liftweb/http/S.scala index e25a32373b..9092ca08d1 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/S.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/S.scala @@ -2037,6 +2037,20 @@ trait S extends HasParams with Loggable { def ~(prefix: String, key: String): Option[NodeSeq] = apply(prefix, key).toOption.map(Text(_)) } + /** + * Sometimes, in the course of eager evaluation, it becomes necessary + * to clear attribtues so they do not polute the eagerly evaluated stuff. + * When you need to clear the attributes, wrap your code block in clearAttrs + * and have fun. + * + * @param f the call-by-name code block to run where the attributes are clear + * @tparam T the return type of the code block + * @return the return value of the code block + */ + def clearAttrs[T](f: => T):T = { + _attrs.doWith((Null, Nil))(f) + } + /** * Temporarily adds the given attributes to the current set, then executes the given function. * From d065598296ffe95632dd1c550ff41a9d055b8d52 Mon Sep 17 00:00:00 2001 From: David Pollak Date: Tue, 12 Feb 2013 10:16:56 -0800 Subject: [PATCH 0324/1949] First step to a revised 3.0 --- build.sbt | 6 +- liftsh | 2 +- .../scala/net/liftweb/jpa/RequestVarEM.scala | 65 --- persistence/ldap/.gitignore | 1 - persistence/ldap/README | 31 -- .../net/liftweb/ldap/LDAPProtoUser.scala | 203 --------- .../scala/net/liftweb/ldap/LdapVendor.scala | 408 ------------------ .../ldap/src/test/resources/ldap.properties | 5 - .../ldap/src/test/resources/logback-test.xml | 13 - .../scala/net/liftweb/ldap/LdapSpec.scala | 177 -------- project/Build.scala | 22 +- project/Dependencies.scala | 2 +- project/Developers.scala | 2 +- .../main/scala/net/liftweb/http}/Wizard.scala | 30 +- .../scala/net/liftweb/http}/WizardSpec.scala | 0 15 files changed, 24 insertions(+), 943 deletions(-) delete mode 100644 persistence/jpa/src/main/scala/net/liftweb/jpa/RequestVarEM.scala delete mode 100644 persistence/ldap/.gitignore delete mode 100644 persistence/ldap/README delete mode 100644 persistence/ldap/src/main/scala/net/liftweb/ldap/LDAPProtoUser.scala delete mode 100644 persistence/ldap/src/main/scala/net/liftweb/ldap/LdapVendor.scala delete mode 100644 persistence/ldap/src/test/resources/ldap.properties delete mode 100644 persistence/ldap/src/test/resources/logback-test.xml delete mode 100644 persistence/ldap/src/test/scala/net/liftweb/ldap/LdapSpec.scala rename web/{wizard/src/main/scala/net/liftweb/wizard => webkit/src/main/scala/net/liftweb/http}/Wizard.scala (95%) rename web/{wizard/src/test/scala/net/liftweb/wizard => webkit/src/test/scala/net/liftweb/http}/WizardSpec.scala (100%) diff --git a/build.sbt b/build.sbt index 204a396651..4af4c58ba5 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ import Dependencies._ organization in ThisBuild := "net.liftweb" -version in ThisBuild := "2.5-SNAPSHOT" +version in ThisBuild := "3.0-SNAPSHOT" homepage in ThisBuild := Some(url("http://www.liftweb.net")) @@ -12,7 +12,9 @@ startYear in ThisBuild := Some(2006) organizationName in ThisBuild := "WorldWide Conferencing, LLC" -crossScalaVersions in ThisBuild := Seq("2.10.0", "2.9.2", "2.9.1-1", "2.9.1") +scalaVersion := "2.10.0" + +crossScalaVersions in ThisBuild := Seq("2.10.0") libraryDependencies in ThisBuild <++= scalaVersion {sv => Seq(specs2(sv), scalacheck) } diff --git a/liftsh b/liftsh index 0db10227b4..91995c5fcc 100755 --- a/liftsh +++ b/liftsh @@ -6,7 +6,7 @@ if test -f ~/.liftsh.config; then fi # Internal options, always specified -INTERNAL_OPTS="-Dfile.encoding=UTF-8 -Xmx768m -noverify -XX:ReservedCodeCacheSize=96m -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC -XX:MaxPermSize=512m" +INTERNAL_OPTS="-Dfile.encoding=UTF-8 -Xmx1768m -noverify -XX:ReservedCodeCacheSize=96m -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC -XX:MaxPermSize=512m" # Add 64bit specific option exec java -version 2>&1 | grep -q "64-Bit" && INTERNAL_OPTS="${INTERNAL_OPTS} -XX:+UseCompressedOops -XX:ReservedCodeCacheSize=128m" diff --git a/persistence/jpa/src/main/scala/net/liftweb/jpa/RequestVarEM.scala b/persistence/jpa/src/main/scala/net/liftweb/jpa/RequestVarEM.scala deleted file mode 100644 index cdd71f719e..0000000000 --- a/persistence/jpa/src/main/scala/net/liftweb/jpa/RequestVarEM.scala +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2006-2011 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 jpa - -import javax.persistence.EntityManager - -import net.liftweb.http.TransientRequestVar - -import org.scala_libs.jpa.{ScalaEMFactory, ScalaEntityManager} - -/** - * This trait provides specific functionality for the Lift web framework - * by using a Lift RequestVar to hold the underlying EM. This - * allows you to use a singleton for EM access. You must mix in some - * other class to provide the actual ScalaEMFactory functionality. - * Example usage would be: - * - *

    - * - * object Model extends LocalEMF("test") with RequestVarEM - * - *

    - * - * @author Derek Chen-Becker - */ -trait RequestVarEM extends ScalaEntityManager with ScalaEMFactory { - /** - * Provides the request var that holds the underlying EntityManager - * for each request. - */ - object emVar extends TransientRequestVar[EntityManager](openEM()) { - this.registerGlobalCleanupFunc(ignore => closeEM(this.is)) - - override def __nameSalt = net.liftweb.util.Helpers.randomString(10) - } - - // Must be provided to properly implement ScalaEntityManager - protected def em = emVar.is - val factory = this - - /** - * Returns the current underlying EntityManager. Generally - * you shouldn't need to do this unless you're using some very - * advanced or propietary functionality on the EM. - * - * @return The underlying EM - */ - def getUnderlying : EntityManager = em -} - diff --git a/persistence/ldap/.gitignore b/persistence/ldap/.gitignore deleted file mode 100644 index f78c92ab69..0000000000 --- a/persistence/ldap/.gitignore +++ /dev/null @@ -1 +0,0 @@ -server-work/ diff --git a/persistence/ldap/README b/persistence/ldap/README deleted file mode 100644 index d1efa240c5..0000000000 --- a/persistence/ldap/README +++ /dev/null @@ -1,31 +0,0 @@ -This module provides a LDAPVendor class to perform search and bind operations against a LDAP Server, -and a base class to authentificate LDAP users (using the LDAPVendor) - -1: SimpleLDAPVendor -SimpleLDAPVendor extends LDAPVendor class and provides a simple and functional LDAPVendor, -that only needs to provide a parameters var to define the LDAP Server properties . - -SimpleLDAPVendor.parameters = () => Map("ldap.url" -> "ldap://localhost", - "ldap.base" -> "dc=company,dc=com", - "ldap.userName" -> "cn=query,dc=company,dc=com", - "ldap.password" -> "password") - -or - -SimpleLDAPVendor.parameters = () => SimpleLDAPVendor.parametersFromFile("/some/directory/ldap.properties") - -2: LDAPProtoUser -Base class of LDAP users - - We can define : - - - loginErrorMessage = Message displayed when user auth failed, default = "Unable to login with : %s" - - ldapUserSearch = LDAP search sentence to search user object using login and password, default = (uid=%s) - - rolesSearchFilter = LDAP search filter to get the user roles, default value = (&(objectClass=groupOfNames)(member=%s)) - - rolesNameRegex = Regular expression to get the role name from his dn (maybe we should get object cn attribute or something ?) - - - - We can override setRoles function if we want to define roles search manually - - - diff --git a/persistence/ldap/src/main/scala/net/liftweb/ldap/LDAPProtoUser.scala b/persistence/ldap/src/main/scala/net/liftweb/ldap/LDAPProtoUser.scala deleted file mode 100644 index dbb9a5f3e1..0000000000 --- a/persistence/ldap/src/main/scala/net/liftweb/ldap/LDAPProtoUser.scala +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright 2010-2011 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 ldap - -import javax.naming.directory.{Attributes} -import scala.util.matching.{Regex} -import scala.xml.{Elem, NodeSeq} -import net.liftweb.http.{LiftResponse, RedirectResponse, S, SessionVar} -import net.liftweb.http.js.{JsCmds} -import net.liftweb.mapper.{BaseOwnedMappedField, - MappedString, - MetaMegaProtoUser, - MegaProtoUser} -import net.liftweb.sitemap.{Menu} -import net.liftweb.util.{Helpers} -import net.liftweb.common.{Box, Empty, Full} - -import Helpers._ - -import scala.util.matching.{Regex} -import scala.xml.{Elem, NodeSeq} - -trait MetaLDAPProtoUser[ModelType <: LDAPProtoUser[ModelType]] extends MetaMegaProtoUser[ModelType] { - self: ModelType => - - override def signupFields: List[FieldPointerType] = uid :: - cn :: dn :: Nil - - override def fieldOrder: List[FieldPointerType] = uid :: - cn :: dn :: Nil - - /** - * The menu item for creating the user/sign up (make this "Empty" to disable) - */ - override def createUserMenuLoc: Box[Menu] = Empty - - /** - * The menu item for lost password (make this "Empty" to disable) - */ - override def lostPasswordMenuLoc: Box[Menu] = Empty - - /** - * The menu item for resetting the password (make this "Empty" to disable) - */ - override def resetPasswordMenuLoc: Box[Menu] = Empty - - /** - * The menu item for changing password (make this "Empty" to disable) - */ - override def changePasswordMenuLoc: Box[Menu] = Empty - - /** - * The menu item for validating a user (make this "Empty" to disable) - */ - override def validateUserMenuLoc: Box[Menu] = Empty - - override def editUserMenuLoc: Box[Menu] = Empty - - /** - * User search sentence - */ - def ldapUserSearch: String = "(uid=%s)" - - /** - * Error messages - */ - def loginErrorMessage: String = "Unable to login with : %s" - - def commonNameAttributeName = "cn" - def uidAttributeName = "uid" - - override def loginXhtml : Elem = { -
    - - - - - - - - - - - - - -
    {S.?("log.in")}
    Username
    Password
     
    -
    - } - - def ldapVendor: LDAPVendor = new LDAPVendor - - override def login : NodeSeq = { - if (S.post_?) { - if (!ldapLogin(S.param("username").openOr(""), - S.param("password").openOr(""))) - S.error(loginErrorMessage.format(S.param("username").openOr(""))) - } - - Helpers.bind("user", loginXhtml, - "name" -> (JsCmds.FocusOnLoad()), - "password" -> (), - "submit" -> ()) - } - - def ldapLogin(username: String, password: String): Boolean = { - def _getUserAttributes(dn: String) = ldapVendor.attributesFromDn(dn) - - val users = ldapVendor.search(ldapUserSearch.format(username)) - - if (users.size >= 1) { - val userDn = users(0) - if (ldapVendor.bindUser(userDn, password)) { - val completeDn = userDn + "," + ldapVendor.ldapBaseDn.vend //configure().get("ldap.base").getOrElse("") - logUserIn(this) - - bindAttributes(_getUserAttributes(completeDn)) - - setRoles(completeDn, ldapVendor) - S.redirectTo(homePage) - } - else return false - } - else return false - - return true - } - - def bindAttributes(attrs: Attributes) = { - for { - theCn <- Box !! attrs.get(commonNameAttributeName).get - theUid <- Box !! attrs.get(uidAttributeName).get - } - { - cn(theCn.toString) - uid(theUid.toString) - } - } -} - -trait LDAPProtoUser[T <: LDAPProtoUser[T]] extends MegaProtoUser[T] { - self: T => - /** - * User Roles LDAP search filter - */ - def rolesSearchFilter: String = "(&(objectclass=groupofnames)(member=%s))" - - /** - * Regular expression to get user roles names - */ - def rolesNameRegex = ".*cn=(.[^,]*),ou=.*" - - object ldapRoles extends SessionVar[List[String]](List()) - - override def getSingleton: MetaLDAPProtoUser[T] - - object uid extends MappedString(this, 64) { - override def dbIndexed_? = true - } - - object dn extends MappedString(this, 64) { - override def dbIndexed_? = true - } - - object cn extends MappedString(this, 64) { - override def dbIndexed_? = true - } - - def getRoles: List[String] = { - return ldapRoles.get - } - - def setRoles(userDn: String, ldapVendor: LDAPVendor) { - def getGroupNameFromDn(dn: String): String = { - val regex = new Regex(rolesNameRegex) - - val regex(groupName) = dn - return groupName - } - - // Search for user roles - val filter = rolesSearchFilter.format(userDn) - - val groups = ldapVendor.search(filter) - groups foreach { g => ldapRoles.set(ldapRoles.get :+ getGroupNameFromDn(g)) } - } -} - diff --git a/persistence/ldap/src/main/scala/net/liftweb/ldap/LdapVendor.scala b/persistence/ldap/src/main/scala/net/liftweb/ldap/LdapVendor.scala deleted file mode 100644 index b205d90a4b..0000000000 --- a/persistence/ldap/src/main/scala/net/liftweb/ldap/LdapVendor.scala +++ /dev/null @@ -1,408 +0,0 @@ -/* - * Copyright 2010-2011 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 ldap - -import java.io.{InputStream, FileInputStream} -import java.util.{Hashtable, Properties} - -import javax.naming.{AuthenticationException,CommunicationException,Context,NamingException} -import javax.naming.directory.{Attributes, BasicAttributes, SearchControls} -import javax.naming.ldap.{InitialLdapContext,LdapName} - -import scala.collection.mutable.ListBuffer -import scala.collection.JavaConversions._ - -import util.{ControlHelpers,Props,SimpleInjector,ThreadGlobal} -import common._ - -/** - * This class provides functionality to allow us to search and - * bind (authenticate) a username from a ldap server. - * - * To configure the LDAP Vendor parameters, use one of the configure - * methods to provide a Map of string parameters. - * - * The primary parameters (with defaults) are: - *
      - *
    • ldap.url - The LDAP Server url : "ldap://localhost"
    • - *
    • ldap.base - The base DN from the LDAP Server : ""
    • - *
    • ldap.userName - The LDAP user dn to perform search operations : ""
    • - *
    • ldap.password - The LDAP user password : ""
    • - *
    • ldap.authType - The schema to use for authentication : "simple"
    • - *
    • ldap.initial_context_factory - the factory class to use for - * initial contexts : "com.sun.jndi.ldap.LdapCtxFactory"
    • - *
    • - *
    - * - * Optionally, you can set the following parameters to control context testing - * and reconnect attempts: - * - *
      - *
    • lift-ldap.testLookup - A DN to attempt to look up to validate the - * current context. Defaults to no testing
    • - *
    • lift-ldap.retryInterval - How many milliseconds to wait between connection - * attempts due to communications failures. Defaults to 5000
    • - *
    • lift-ldap.maxRetries - The maxiumum number of attempts to make to set up - * the context before aborting. Defaults to 6
    • - *
    - * - * In addition to configuration via a Map or Properties file, fine-grained control - * over behaviors can be specified via Inject values corresponding to each - * of the properties. - * - * To use LDAPVendor, you can simply create an object extending it - * and configure: - * - *
    - * object myLdap extends LDAPVendor
    - * myLdap.configure()
    - * 
    - * - */ -class LDAPVendor extends Loggable with SimpleInjector { - // =========== Constants =============== - final val KEY_URL = "ldap.url" - final val KEY_BASE_DN = "ldap.base" - final val KEY_USER = "ldap.userName" - final val KEY_PASSWORD = "ldap.password" - final val KEY_AUTHTYPE = "ldap.authType" - final val KEY_FACTORY = "ldap.initial_context_factory" - final val KEY_LOOKUP = "lift-ldap.testLookup" - final val KEY_RETRY_INTERVAL = "lift-ldap.retryInterval" - final val KEY_MAX_RETRIES = "lift-ldap.maxRetries" - - final val DEFAULT_URL = "ldap://localhost" - final val DEFAULT_BASE_DN = "" - final val DEFAULT_USER = "" - final val DEFAULT_PASSWORD = "" - final val DEFAULT_AUTHTYPE = "simple" - final val DEFAULT_FACTORY = "com.sun.jndi.ldap.LdapCtxFactory" - final val DEFAULT_LOOKUP = Empty - final val DEFAULT_RETRY_INTERVAL = 5000 - final val DEFAULT_MAX_RETRIES = 6 - - - - /** - * Configure straight from the Props object. This allows - * you to use Lift's run modes for different LDAP configuration. - */ - def configure() { - configure(Props.props) - } - - /** - * Configure from the given file. The file is expected - * to be in a format parseable by java.util.Properties - */ - def configure(filename : String) { - val stream = new FileInputStream(filename) - configure(stream) - stream.close() - } - - /** - * Configure from the given input stream. The stream is expected - * to be in a format parseable by java.util.Properties - */ - def configure(stream : InputStream) { - val p = new Properties() - p.load(stream) - - configure(propertiesToMap(p)) - } - - /** - * Configure from the given Map[String,String] - */ - def configure(props : Map[String,String]) { - internal_config = processConfig(props) - } - - /** - * This controls the URL used to connect to the LDAP - * server - */ - val ldapUrl = new Inject[String](DEFAULT_URL){} - - /** - * This controls the base DN used for searcheds - */ - val ldapBaseDn = new Inject[String](DEFAULT_BASE_DN){} - - /** - * This controls the username used to bind for - * searches (not authentication) - */ - val ldapUser = new Inject[String](DEFAULT_USER){} - - /** - * This controls the password used to bind for - * searches (not authentication) - */ - val ldapPassword = new Inject[String](DEFAULT_PASSWORD){} - - /** - * This controls the type of authentication to - * use. - */ - val ldapAuthType = new Inject[String](DEFAULT_AUTHTYPE){} - - /** - * This controls the factory used to obtain an - * InitialContext - */ - val ldapFactory = new Inject[String](DEFAULT_FACTORY){} - - /** - * This can be set to test the InitialContext on each LDAP - * operation. It should be set to a search DN. - */ - val testLookup = new Inject[Box[String]](Empty){} - - /** - * This sets the interval between connection attempts - * on the InitialContext. The default is 5 seconds - */ - val retryInterval = new Inject[Long](5000){} - - /** - * This sets the maximum number of connection - * attempts before giving up. The default is 6 - */ - val retryMaxCount = new Inject[Int](6){} - - /** - * This sets the Directory SearchControls instance - * that is used to refine searches on the provider. - */ - val searchControls = new Inject[SearchControls](defaultSearchControls){} - - /** - * The default SearchControls to use: search the - * base DN with a sub-tree scope, and return the - * "cn" attribute. - */ - def defaultSearchControls() : SearchControls = { - val constraints = new SearchControls() - constraints.setSearchScope(SearchControls.SUBTREE_SCOPE) - constraints.setReturningAttributes(Array("cn")) - return constraints - } - - /** - * The configuration to use for connecting to the - * provider. It should be set via the configure methods - */ - private var internal_config : Map[String,String] = Map.empty - - /** - * The configuration to use for connecting to the - * provider. It should be set via the configure methods - */ - def configuration = internal_config - - /** - * This method checks the configuration and sets defaults for any - * properties that are required. It also processes any of the - * optional configuration propertes related to context testing - * and retries. - * - * This method is intended to be called during update of the default - * configuration, not during granular override of the config. - */ - def processConfig(input : Map[String,String]) : Map[String,String] = { - var currentConfig = input - - def setIfEmpty(name : String, newVal : String) = - if (currentConfig.get(name).isEmpty) { - currentConfig += (name -> newVal) - } - - // Verify the minimum config - setIfEmpty(KEY_URL, DEFAULT_URL) - setIfEmpty(KEY_BASE_DN, DEFAULT_BASE_DN) - setIfEmpty(KEY_USER, DEFAULT_USER) - setIfEmpty(KEY_PASSWORD, DEFAULT_PASSWORD) - setIfEmpty(KEY_AUTHTYPE, DEFAULT_AUTHTYPE) - setIfEmpty(KEY_FACTORY, DEFAULT_FACTORY) - - // Set individual properties - ldapUrl.default.set(currentConfig(KEY_URL)) - ldapBaseDn.default.set(currentConfig(KEY_BASE_DN)) - ldapUser.default.set(currentConfig(KEY_USER)) - ldapPassword.default.set(currentConfig(KEY_PASSWORD)) - ldapAuthType.default.set(currentConfig(KEY_AUTHTYPE)) - ldapFactory.default.set(currentConfig(KEY_FACTORY)) - - // Process the optional configuration properties - currentConfig.get(KEY_LOOKUP).foreach{ - prop => testLookup.default.set(Full(prop)) - } - - ControlHelpers.tryo { - currentConfig.get(KEY_RETRY_INTERVAL).foreach{ - prop => retryInterval.default.set(prop.toLong) - } - } - - ControlHelpers.tryo { - currentConfig.get(KEY_MAX_RETRIES).foreach{ - prop => retryMaxCount.default.set(prop.toInt) - } - } - - currentConfig - } - - protected def propertiesToMap(props: Properties) : Map[String,String] = { - Map.empty ++ props - } - - // =========== Code ==================== - - /** - * Obtains a (possibly cached) InitialContext - * instance based on the currently set parameters. - */ - def initialContext = getInitialContext() - - def attributesFromDn(dn: String): Attributes = - initialContext.getAttributes(dn) - - /** - * Searches the base DN for entities matching the given filter. - */ - def search(filter: String): List[String] = { - logger.debug("Searching for '%s'".format(filter)) - - val resultList = new ListBuffer[String]() - - val searchResults = initialContext.search(ldapBaseDn.vend, - filter, - searchControls.vend) - - while(searchResults.hasMore()) { - resultList += searchResults.next().getName - } - - return resultList.reverse.toList - } - - /** - * Attempts to authenticate the given DN against the configured - * LDAP provider. - */ - def bindUser(dn: String, password: String) : Boolean = { - logger.debug("Attempting to bind user '%s'".format(dn)) - - try { - val username = dn + "," + ldapBaseDn.vend - - ldapUser.doWith(username) { - ldapPassword.doWith(password) { - val ctx = openInitialContext() - ctx.close() - } - } - - logger.info("Successfully authenticated " + dn) - true - } catch { - case ae : AuthenticationException => { - logger.warn("Authentication failed for '%s' : %s".format(dn, ae.getMessage)) - false - } - } - } - - // This caches the context for the current thread - private[this] final val currentInitialContext = new ThreadGlobal[InitialLdapContext]() - - /** - * This method attempts to fetch the cached InitialLdapContext for the - * current thread. If there isn't a current context, open a new one. If a - * test DN is configured, the connection (cached or new) will be validated - * by performing a lookup on the test DN. - */ - protected def getInitialContext() : InitialLdapContext = { - val maxAttempts = retryMaxCount.vend - var attempts = 0 - - var context : Box[InitialLdapContext] = Empty - - while (context.isEmpty && attempts < maxAttempts) { - try { - context = (currentInitialContext.box, testLookup.vend) match { - // If we don't want to test an existing context, just return it - case (Full(ctxt), Empty) => Full(ctxt) - case (Full(ctxt), Failure(_,_,_)) => Full(ctxt) - case (Full(ctxt), Full(test)) => { - logger.debug("Testing InitialContext prior to returning") - ctxt.lookup(test) - Full(ctxt) - } - case (Empty | Failure(_,_,_) ,_) => { - // We'll just allocate a new InitialContext to the thread - currentInitialContext(openInitialContext()) - - // Setting context to Empty here forces one more iteration in case a test - // DN has been configured - Empty - } - } - } catch { - case commE : CommunicationException => { - logger.error(("Communications failure on attempt %d while " + - "verifying InitialContext: %s").format(attempts + 1, commE.getMessage)) - - // The current context failed, so clear it - currentInitialContext(null) - - // We sleep before retrying - Thread.sleep(retryInterval.vend) - attempts += 1 - } - } - } - - // We have a final check on the context before returning - context match { - case Full(ctxt) => ctxt - case Empty | Failure(_,_,_) => throw new CommunicationException("Failed to connect to '%s' after %d attempts". - format(ldapUrl.vend, attempts)) - } - } - - /** - * This method does the actual work of setting up the environment and constructing - * the InitialLdapContext. - */ - protected def openInitialContext () : InitialLdapContext = { - logger.debug("Obtaining an initial context from '%s'".format(ldapUrl.vend)) - - var env = new Hashtable[String, String]() - env.put(Context.PROVIDER_URL, ldapUrl.vend) - env.put(Context.SECURITY_AUTHENTICATION, ldapAuthType.vend) - env.put(Context.SECURITY_PRINCIPAL, ldapUser.vend) - env.put(Context.SECURITY_CREDENTIALS, ldapPassword.vend) - env.put(Context.INITIAL_CONTEXT_FACTORY, ldapFactory.vend) - new InitialLdapContext(env, null) - } -} - diff --git a/persistence/ldap/src/test/resources/ldap.properties b/persistence/ldap/src/test/resources/ldap.properties deleted file mode 100644 index 1eae51d5e4..0000000000 --- a/persistence/ldap/src/test/resources/ldap.properties +++ /dev/null @@ -1,5 +0,0 @@ -# LDAP -ldap.url = ldap://localhost -ldap.base = dc=sample_company,dc=com -ldap.userName = cn=admin,dc=sample_company,dc=com -ldap.password = password diff --git a/persistence/ldap/src/test/resources/logback-test.xml b/persistence/ldap/src/test/resources/logback-test.xml deleted file mode 100644 index bfc78d1d7a..0000000000 --- a/persistence/ldap/src/test/resources/logback-test.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - %d [%thread] %level %logger - %m%n - - - - - - - - - diff --git a/persistence/ldap/src/test/scala/net/liftweb/ldap/LdapSpec.scala b/persistence/ldap/src/test/scala/net/liftweb/ldap/LdapSpec.scala deleted file mode 100644 index 8933bb741a..0000000000 --- a/persistence/ldap/src/test/scala/net/liftweb/ldap/LdapSpec.scala +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright 2010-2011 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 ldap - -import java.io.File - -import javax.naming.CommunicationException - -import org.apache.mina.util.AvailablePortFinder -import org.apache.directory.server.core.DefaultDirectoryService -import org.apache.directory.server.core.partition.impl.btree.jdbm.{JdbmIndex,JdbmPartition} -import org.apache.directory.server.ldap.LdapServer -import org.apache.directory.server.protocol.shared.transport.TcpTransport -import org.apache.directory.server.xdbm.Index -import org.apache.directory.server.core.entry.ServerEntry -import org.apache.directory.shared.ldap.name.LdapDN - -import org.specs2.mutable.Specification -import org.specs2.specification.{ AfterExample, BeforeExample } - -import common._ -import util.Helpers.tryo - - -/** - * Systems under specification for Ldap. - */ -object LdapSpec extends Specification with AfterExample with BeforeExample { - "LDAP Specification".title - sequential - - val ROOT_DN = "dc=ldap,dc=liftweb,dc=net" - - // Thanks to Francois Armand for pointing this utility out! - val service_port = AvailablePortFinder.getNextAvailable(40000) - val service = new DefaultDirectoryService - val ldap = new LdapServer - - lazy val workingDir = Box.legacyNullTest(System.getProperty("apacheds.working.dir")) - - /* - * The following is taken from: - * http://directory.apache.org/apacheds/1.5/41-embedding-apacheds-into-an-application.html - * http://stackoverflow.com/questions/1560230/running-apache-ds-embedded-in-my-application - */ - def before = { - (try { - // Disable changelog - service.getChangeLog.setEnabled(false) - - // Configure a working directory if we have one set, otherwise fail - // because we don't want it using current directory under SBT - workingDir match { - case Full(d) => - val dir = new java.io.File(d) - dir.mkdirs - service.setWorkingDirectory(dir) - case _ => failure("No working dir set for ApacheDS!") - } - - // Set up a partition - val partition = new JdbmPartition - partition.setId("lift-ldap") - partition.setSuffix(ROOT_DN) - service.addPartition(partition) - - // Index attributes (gnarly type due to poor type inferencing) - val indices : java.util.Set[Index[_,ServerEntry]] = new java.util.HashSet() - - List("objectClass", "ou", "uid", "sn").foreach { - attr : String => indices.add(new JdbmIndex(attr)) - } - - partition.setIndexedAttributes(indices) - - // Set up the transport to use our "available" port - ldap.setTransports(new TcpTransport(service_port)) - ldap.setDirectoryService(service) - - service.startup() - - // Inject the root entry if it does not already exist - if ( !service.getAdminSession().exists(partition.getSuffixDn)) { - val rootEntry = service.newEntry(new LdapDN(ROOT_DN)) - rootEntry.add( "objectClass", "top", "domain", "extensibleObject" ); - rootEntry.add( "dc", "ldap" ); - service.getAdminSession().add( rootEntry ); - } - - addTestData() - - ldap.start() - - }) must not(throwAn[Exception]).orSkip - } - - "LDAPVendor" should { - object myLdap extends LDAPVendor - - myLdap.configure(Map("ldap.url" -> "ldap://localhost:%d/".format(service_port), - "ldap.base" -> "dc=ldap,dc=liftweb,dc=net")) - - "handle simple lookups" in { - myLdap.search("objectClass=person") must_== List("cn=Test User") - } - - "handle simple authentication" in { - myLdap.bindUser("cn=Test User", "letmein") must_== true - } - - "attempt reconnects" in { - object badLdap extends LDAPVendor - badLdap.configure() - - // Make sure that we use a port where LDAP won't live - badLdap.ldapUrl.doWith("ldap://localhost:2") { - // Let's not make this spec *too* slow - badLdap.retryInterval.doWith(1000) { - badLdap.search("objectClass=person") must throwA[CommunicationException] - } - } - } - } - - - def after = { - ldap.stop() - service.shutdown() - - // Clean up the working directory - def deleteTree(f : File) { - // First, delete any children if this is a directory - if (f.isDirectory) { - f.listFiles.foreach(deleteTree) - } - f.delete() - } - - tryo { - workingDir.foreach { dir => - deleteTree(new File(dir)) - } - } - } - - def addTestData() { - val username = new LdapDN("cn=Test User," + ROOT_DN) - if (! service.getAdminSession().exists(username)) { - // Add a test user. This will be used for searching and binding - val entry = service.newEntry(username) - entry.add("objectClass", "person", "organizationalPerson") - entry.add("cn", "Test User") - entry.add("sn", "User") - /* LDAP Schema for userPassword is octet string, so we - * need to use getBytes. If you just pass in a straight String, - * ApacheDS sets userPassword to null :( */ - entry.add("userPassword", "letmein".getBytes()) - service.getAdminSession.add(entry) - } - } -} - diff --git a/project/Build.scala b/project/Build.scala index efcd53cc92..5a9789b8f6 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1,5 +1,5 @@ /* - * Copyright 2012 WorldWide Conferencing, LLC + * Copyright 2012-2013 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. @@ -76,7 +76,7 @@ object BuildDef extends Build { // Web Projects // ------------ lazy val web: Seq[ProjectReference] = - Seq(testkit, webkit, wizard) + Seq(testkit, webkit) lazy val testkit = webProject("testkit") @@ -98,16 +98,12 @@ object BuildDef extends Build { System.setProperty("net.liftweb.webapptest.src.test.webapp", (src / "webapp").absString) }) - lazy val wizard = - webProject("wizard") - .dependsOn(webkit, db) - .settings(description := "Wizard Library") // Persistence Projects // -------------------- lazy val persistence: Seq[ProjectReference] = - Seq(db, proto, jpa, mapper, record, squeryl_record, mongodb, mongodb_record, ldap) + Seq(db, proto, mapper, record, squeryl_record, mongodb, mongodb_record) lazy val db = persistenceProject("db") @@ -118,11 +114,6 @@ object BuildDef extends Build { persistenceProject("proto") .dependsOn(webkit) - lazy val jpa = - persistenceProject("jpa") - .dependsOn(webkit) - .settings(libraryDependencies ++= Seq(scalajpa, persistence_api)) - lazy val mapper = persistenceProject("mapper") .dependsOn(db, proto) @@ -156,13 +147,6 @@ object BuildDef extends Build { .dependsOn(record, mongodb) .settings(parallelExecution in Test := false) - lazy val ldap = - persistenceProject("ldap") - .dependsOn(mapper) - .settings(libraryDependencies += apacheds, - initialize in Test <<= (crossTarget in Test) { ct => - System.setProperty("apacheds.working.dir", (ct / "apacheds").absolutePath) - }) def coreProject = liftProject("core") _ def webProject = liftProject("web") _ diff --git a/project/Dependencies.scala b/project/Dependencies.scala index caf678f420..5a5f912212 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -1,5 +1,5 @@ /* - * Copyright 2011 WorldWide Conferencing, LLC + * Copyright 2011-2013 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. diff --git a/project/Developers.scala b/project/Developers.scala index 1290378802..7c0146d683 100644 --- a/project/Developers.scala +++ b/project/Developers.scala @@ -1,5 +1,5 @@ /* - * Copyright 2012 WorldWide Conferencing, LLC + * Copyright 2013 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. diff --git a/web/wizard/src/main/scala/net/liftweb/wizard/Wizard.scala b/web/webkit/src/main/scala/net/liftweb/http/Wizard.scala similarity index 95% rename from web/wizard/src/main/scala/net/liftweb/wizard/Wizard.scala rename to web/webkit/src/main/scala/net/liftweb/http/Wizard.scala index 3e49a801ed..38d1c9ba65 100644 --- a/web/wizard/src/main/scala/net/liftweb/wizard/Wizard.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Wizard.scala @@ -1,5 +1,5 @@ /* - * Copyright 2009-2011 WorldWide Conferencing, LLC + * Copyright 2009-2013 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. @@ -15,7 +15,7 @@ */ package net.liftweb -package wizard +package http import net.liftweb._ import http._ @@ -24,14 +24,13 @@ import JsCmds._ import common._ import util._ -import db._ import Helpers._ import scala.xml._ import scala.reflect.Manifest object WizardRules extends Factory with FormVendor { - val dbConnectionsForTransaction: FactoryMaker[List[ConnectionIdentifier]] = - new FactoryMaker[List[ConnectionIdentifier]](() => Nil) {} + val dbConnectionsForTransaction: FactoryMaker[List[LoanWrapper]] = + new FactoryMaker[List[LoanWrapper]](() => Nil) {} private def m[T](implicit man: Manifest[T]): Manifest[T] = man @@ -39,7 +38,7 @@ object WizardRules extends Factory with FormVendor { private object currentWizards extends SessionVar[Set[String]](Set()) - private[wizard] def registerWizardSession(): String = { + private[http] def registerWizardSession(): String = { S.synchronizeForSession { val ret = Helpers.nextFuncName currentWizards.set(currentWizards.is + ret) @@ -47,12 +46,12 @@ object WizardRules extends Factory with FormVendor { } } - private[wizard] def isValidWizardSession(id: String): Boolean = + private[http] def isValidWizardSession(id: String): Boolean = S.synchronizeForSession { currentWizards.is.contains(id) } - private[wizard] def deregisterWizardSession(id: String) { + private[http] def deregisterWizardSession(id: String) { S.synchronizeForSession { currentWizards.set(currentWizards.is - id) } @@ -295,9 +294,9 @@ trait Wizard extends StatefulSnippet with Factory with ScreenWizardRendered { } } - class WizardSnapshot(private[wizard] val screenVars: Map[String, (NonCleanAnyVar[_], Any)], + class WizardSnapshot(private[http] val screenVars: Map[String, (NonCleanAnyVar[_], Any)], val currentScreen: Box[Screen], - private[wizard] val snapshot: Box[WizardSnapshot], + private[http] val snapshot: Box[WizardSnapshot], private val firstScreen: Boolean) extends Snapshot { def restore() { registerThisSnippet(); @@ -322,7 +321,7 @@ trait Wizard extends StatefulSnippet with Factory with ScreenWizardRendered { _screenList = _screenList ::: List(screen) } - def dbConnections: List[ConnectionIdentifier] = WizardRules.dbConnectionsForTransaction.vend + def dbConnections: List[LoanWrapper] = WizardRules.dbConnectionsForTransaction.vend /** * The ordered list of Screens @@ -394,7 +393,7 @@ trait Wizard extends StatefulSnippet with Factory with ScreenWizardRendered { nextScreen match { case Empty => - def useAndFinish(in: List[ConnectionIdentifier]) { + def useAndFinish(in: List[LoanWrapper]) { in match { case Nil => { WizardRules.deregisterWizardSession(CurrentSession.is) @@ -407,8 +406,7 @@ trait Wizard extends StatefulSnippet with Factory with ScreenWizardRendered { } } - case x :: xs => DB.use(x) { - conn => + case x :: xs => x.apply { useAndFinish(xs) } } @@ -546,7 +544,7 @@ trait Wizard extends StatefulSnippet with Factory with ScreenWizardRendered { def postFinish() { } - private[wizard] def enterScreen() { + private[http] def enterScreen() { if (!_touched) { _touched.set(true) localSetup() @@ -599,7 +597,7 @@ trait Wizard extends StatefulSnippet with Factory with ScreenWizardRendered { } - private[wizard] object WizardVarHandler { + private[http] object WizardVarHandler { def get[T](name: String): Box[T] = ScreenVars.is.get(name).map(_._2.asInstanceOf[T]) diff --git a/web/wizard/src/test/scala/net/liftweb/wizard/WizardSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/WizardSpec.scala similarity index 100% rename from web/wizard/src/test/scala/net/liftweb/wizard/WizardSpec.scala rename to web/webkit/src/test/scala/net/liftweb/http/WizardSpec.scala From 7964a6fa37c784f90511be32d1e3b3ab4eed35ed Mon Sep 17 00:00:00 2001 From: David Pollak Date: Tue, 12 Feb 2013 18:32:44 -0800 Subject: [PATCH 0325/1949] First bits of Lift 3.0 --- .../scala/net/liftweb/actor/LAFuture.scala | 19 ++ .../net/liftweb/builtin/snippet/Comet.scala | 2 +- .../scala/net/liftweb/http/CometActor.scala | 12 +- .../scala/net/liftweb/http/LiftMerge.scala | 22 +- .../scala/net/liftweb/http/LiftRules.scala | 57 +++- .../scala/net/liftweb/http/LiftServlet.scala | 6 +- .../scala/net/liftweb/http/LiftSession.scala | 313 +++++++++++++++++- .../src/main/scala/net/liftweb/http/S.scala | 9 +- .../http/auth/HttpAuthentication.scala | 2 +- .../net/liftweb/http/js/JsCommands.scala | 8 +- .../liftweb/http/provider/HTTPProvider.scala | 15 +- .../servlet/HTTPResponseServlet.scala | 2 +- .../containers/Jetty6AsyncProvider.scala | 2 +- .../containers/Jetty7AsyncProvider.scala | 4 +- .../containers/Servlet30AsyncProvider.scala | 2 +- .../net/liftweb/http/rest/RestHelper.scala | 32 ++ 16 files changed, 471 insertions(+), 36 deletions(-) diff --git a/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala b/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala index 1599280b63..7f074b7c03 100644 --- a/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala +++ b/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala @@ -128,6 +128,25 @@ class LAFuture[T] /*extends Future[T]*/ { final class AbortedFutureException() extends Exception("Aborted Future") object LAFuture { + /** + * Create an LAFuture from a function that + * will be applied on a separate thread. The LAFuture + * is returned immediately and the value may be obtained + * by calling `get` + * + * @param f the function that computes the value of the future + * @tparam T the type + * @return an LAFuture that will yield its value when the value has been computed + */ + def apply[T](f: () => T): LAFuture[T] = { + val ret = new LAFuture[T] + LAScheduler.execute(() => { + ret.satisfy(f()) + }) + ret + } + + /** * Collect all the future values into the aggregate future * The returned future will be satisfied when all the diff --git a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Comet.scala b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Comet.scala index c846cbcedc..ef17401185 100644 --- a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Comet.scala +++ b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Comet.scala @@ -60,7 +60,7 @@ object Comet extends DispatchSnippet with LazyLoggable { private def buildSpan(timeb: Box[Long], xml: NodeSeq, cometActor: LiftCometActor, spanId: String): NodeSeq = Elem(cometActor.parentTag.prefix, cometActor.parentTag.label, cometActor.parentTag.attributes, - cometActor.parentTag.scope, Group(xml)) % + cometActor.parentTag.scope, cometActor.parentTag.minimizeEmpty, xml :_*) % (new UnprefixedAttribute("id", Text(spanId), Null)) % (timeb.filter(_ > 0L).map(time => (new PrefixedAttribute("lift", "when", Text(time.toString), Null))) openOr Null) diff --git a/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala b/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala index 6b6c88a3f2..cee889fbda 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala @@ -526,7 +526,7 @@ trait CometActor extends LiftActor with LiftCometActor with BindHelpers { private val logger = Logger(classOf[CometActor]) val uniqueId = Helpers.nextFuncName private var spanId = uniqueId - private var lastRenderTime = Helpers.nextNum + @volatile private var lastRenderTime = Helpers.nextNum /** * If we're going to cache the last rendering, here's the @@ -534,6 +534,12 @@ trait CometActor extends LiftActor with LiftCometActor with BindHelpers { */ private[this] var _realLastRendering: RenderOut = _ + /** + * Get the current render clock for the CometActor + * @return + */ + def renderClock: Long = lastRenderTime + /** * The last rendering (cached or not) */ @@ -725,9 +731,9 @@ trait CometActor extends LiftActor with LiftCometActor with BindHelpers { /** * Creates the span element acting as the real estate for comet rendering. */ - def buildSpan(time: Long, xml: NodeSeq): NodeSeq = { + def buildSpan(time: Long, xml: NodeSeq): Elem = { Elem(parentTag.prefix, parentTag.label, parentTag.attributes, - parentTag.scope, Group(xml)) % + parentTag.scope, parentTag.minimizeEmpty, xml :_*) % new UnprefixedAttribute("id", Text(spanId), if (time > 0L) { diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala index 7b8b6fdae6..445e2d9f86 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala @@ -144,11 +144,11 @@ private[http] trait LiftMerge { 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 e: Elem if e.label == "form" => Elem(v.prefix, v.label, fixAttrs(v.attributes, "action", v.attributes, true), v.scope, e.minimizeEmpty, _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, e.minimizeEmpty, _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, e.minimizeEmpty, _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, e.minimizeEmpty,_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, e.minimizeEmpty, _fixHtml(v.child, inHtml, inHead, justHead, inBody, justBody, bodyHead, bodyTail, doMergy): _*) case c: Comment if stripComments => NodeSeq.Empty case _ => v } @@ -183,6 +183,12 @@ private[http] trait LiftMerge { headChildren += nl } + for { + e <- S.cometAtEnd() + } { + _fixHtml(e, true, false, false, true, false, false, true, false) + } + // Appends ajax stript to body if (LiftRules.autoIncludeAjaxCalc.vend().apply(this)) { bodyChildren += @@ -233,12 +239,12 @@ private[http] trait LiftMerge { } htmlKids += nl - htmlKids += Elem(headTag.prefix, headTag.label, headTag.attributes, headTag.scope, headChildren.toList: _*) + htmlKids += Elem(headTag.prefix, headTag.label, headTag.attributes, headTag.scope, headTag.minimizeEmpty, headChildren.toList: _*) htmlKids += nl - htmlKids += Elem(bodyTag.prefix, bodyTag.label, bodyTag.attributes, bodyTag.scope, bodyChildren.toList: _*) + htmlKids += Elem(bodyTag.prefix, bodyTag.label, bodyTag.attributes, bodyTag.scope, bodyTag.minimizeEmpty, bodyChildren.toList: _*) htmlKids += nl - val tmpRet = Elem(htmlTag.prefix, htmlTag.label, htmlTag.attributes, htmlTag.scope, htmlKids.toList: _*) + val tmpRet = Elem(htmlTag.prefix, htmlTag.label, htmlTag.attributes, htmlTag.scope, htmlTag.minimizeEmpty, htmlKids.toList: _*) val ret: Node = if (Props.devMode) { LiftRules.xhtmlValidator.toList.flatMap(_(tmpRet)) match { diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 2957892575..fb8c35d1cb 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -36,6 +36,7 @@ import java.util.concurrent.{ConcurrentHashMap => CHash} import scala.reflect.Manifest import java.util.concurrent.atomic.AtomicInteger +import actor.LAFuture class LiftRulesJBridge { def liftRules: LiftRules = LiftRules @@ -70,6 +71,42 @@ object LiftRulesMocker { */ final case class StatelessReqTest(path: List[String], httpReq: HTTPRequest) +/** + * Sometimes we're going to have to surface more data from one of these requests + * than we might like (for example, extra info about continuing the computation on + * a different thread), so we'll start off right by having an Answer trait + * that will have some subclasses and implicit conversions + */ +sealed trait DataAttributeProcessorAnswer + +/** + * The companion object that has the implicit conversions + */ +object DataAttributeProcessorAnswer { + implicit def nodesToAnswer(in: NodeSeq): DataAttributeProcessorAnswer = DataAttributeProcessorAnswerNodes(in) + implicit def nodeFuncToAnswer(in: () => NodeSeq): DataAttributeProcessorAnswer = DataAttributeProcessorAnswerFork(in) + implicit def nodeFutureToAnswer(in: LAFuture[NodeSeq]): DataAttributeProcessorAnswer = DataAttributeProcessorAnswerFuture(in) + implicit def setNodeToAnswer(in: Seq[Node]): DataAttributeProcessorAnswer = DataAttributeProcessorAnswerNodes(in) +} + +/** + * Yep... just a bunch of nodes. + * @param nodes + */ +final case class DataAttributeProcessorAnswerNodes(nodes: NodeSeq) extends DataAttributeProcessorAnswer + +/** + * A function that returns a bunch of nodes... run it on a different thread + * @param nodeFunc + */ +final case class DataAttributeProcessorAnswerFork(nodeFunc: () => NodeSeq) extends DataAttributeProcessorAnswer + +/** + * A future that returns nodes... run them on a different thread + * @param nodeFuture the future of the NodeSeq + */ +final case class DataAttributeProcessorAnswerFuture(nodeFuture: LAFuture[NodeSeq]) extends DataAttributeProcessorAnswer + /** * The Lift configuration singleton */ @@ -87,6 +124,12 @@ object LiftRules extends LiftRulesMocker { type DispatchPF = PartialFunction[Req, () => Box[LiftResponse]]; + /** + * A partial function that allows processing of any attribute on an Elem + * if the attribute begins with "data-" + */ + type DataAttributeProcessor = PartialFunction[(String, String, Elem), DataAttributeProcessorAnswer] + /** * The test between the path of a request and whether that path * should result in stateless servicing of that path @@ -423,7 +466,7 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { val ret: Box[String] = for{ url <- Box !! LiftRules.getClass.getResource("/" + cn + ".class") - val newUrl = new java.net.URL(url.toExternalForm.split("!")(0) + "!" + "/META-INF/MANIFEST.MF") + newUrl = new java.net.URL(url.toExternalForm.split("!")(0) + "!" + "/META-INF/MANIFEST.MF") str <- tryo(new String(readWholeStream(newUrl.openConnection.getInputStream), "UTF-8")) ma <- """Implementation-Version: (.*)""".r.findFirstMatchIn(str) } yield ma.group(1) @@ -436,7 +479,7 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { val ret: Box[Date] = for{ url <- Box !! LiftRules.getClass.getResource("/" + cn + ".class") - val newUrl = new java.net.URL(url.toExternalForm.split("!")(0) + "!" + "/META-INF/MANIFEST.MF") + newUrl = new java.net.URL(url.toExternalForm.split("!")(0) + "!" + "/META-INF/MANIFEST.MF") str <- tryo(new String(readWholeStream(newUrl.openConnection.getInputStream), "UTF-8")) ma <- """Built-Time: (.*)""".r.findFirstMatchIn(str) asLong <- asLong(ma.group(1)) @@ -836,6 +879,14 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { new FactoryMaker(() => () => DefaultRoutines.resourceForCurrentReq()) {} + /** + * Ever wanted to add custom attribute processing to Lift? Here's your chance. + * Every attribute with the data- prefix will be tested against this + * RulesSeq and if there's a match, then use the rule process. Simple, easy, cool. + */ + val dataAttributeProcessor: RulesSeq[DataAttributeProcessor] = new RulesSeq() + + /** * There may be times when you want to entirely control the templating process. You can insert * a function to this factory that will do your custom template resolution. If the PartialFunction @@ -925,7 +976,7 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { val sm = smf() _sitemap = Full(sm) for (menu <- sm.menus; - val loc = menu.loc; + loc = menu.loc; rewrite <- loc.rewritePF) LiftRules.statefulRewrite.append(PerRequestPF(rewrite)) _sitemap diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala index 4806b49181..58d1f96625 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala @@ -81,7 +81,7 @@ class LiftServlet extends Loggable { logger.debug("Destroyed Lift handler.") // super.destroy } catch { - case e => logger.error("Destruction failure", e) + case e: Exception => logger.error("Destruction failure", e) } } @@ -162,7 +162,7 @@ class LiftServlet extends Loggable { handleGenericContinuation(theReq, resp, sesBox, func); true // we have to return true to hold onto the request case e if e.getClass.getName.endsWith("RetryRequest") => throw e - case e => logger.info("Request for " + req.request.uri + " failed " + e.getMessage, e); throw e + case e: Throwable => logger.info("Request for " + req.request.uri + " failed " + e.getMessage, e); throw e } } @@ -531,7 +531,7 @@ class LiftServlet extends Loggable { } } catch { case foc: LiftFlowOfControlException => throw foc - case e => S.runExceptionHandlers(requestState, e) + case e: Exception => S.runExceptionHandlers(requestState, e) } } diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index d1bb00662a..dc858fdaaa 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -20,6 +20,7 @@ package http import java.lang.reflect.{Method} import collection.mutable.{HashMap, ListBuffer} +import js.JE.{JsRaw, AnonFunc} import xml._ import common._ @@ -31,6 +32,7 @@ import http.js.{JsCmd, AjaxInfo} import builtin.snippet._ import js._ import provider._ +import json.JsonAST object LiftSession { @@ -669,7 +671,7 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, else soFar } catch { - case exception => + case exception: Exception => (valid, pair :: invalid) } } @@ -1228,7 +1230,7 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, case e: LiftFlowOfControlException => throw e - case e => S.runExceptionHandlers(request, e) + case e: Exception => S.runExceptionHandlers(request, e) } @@ -1994,6 +1996,192 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, private object _lastFoundSnippet extends ThreadGlobal[String] + private object DataAttrNode { + def unapply(in: Node): Option[DataAttributeProcessorAnswer] = { + in match { + case e: Elem => + + val attrs = e.attributes + + val rules = LiftRules.dataAttributeProcessor.toList + + e.attributes.toStream.flatMap { + case UnprefixedAttribute(key, value, _) if key.toLowerCase().startsWith("data-") => + val nk = key.substring(5).toLowerCase() + val vs = value.text + val a2 = attrs.filter{ + case UnprefixedAttribute(k2, _, _) => k2 != key + case _ => true + } + + NamedPF.applyBox((nk, vs, new Elem(e.prefix, e.label, a2, e.scope, e.minimizeEmpty, e.child :_*)), rules) + case _ => None + }.headOption + + case _ => None + } + } + } + + /** + * Pass in a LiftActor and get a JavaScript expression (function(x) {...}) that + * represents an asynchronous Actor message from the client to the server. + * + * The Actor should respond to a message in the form of a JsonAST.JValue. + * + * This method requires the session be stateful + * + * In general, your Actor should be a subclass of ScopedLiftActor because + * that way you'll have the scope of the current session. + * + * @param in the Actor to send messages to. + * + * @return a JsExp that contains a function that can be called with a parameter + * and when the function is called, the parameter is JSON serialized and sent to + * the server + */ + def clientActorFor(in: LiftActor): JsExp = { + testStatefulFeature{ + AnonFunc("x", + SHtml.jsonCall(JsRaw("x"), (p: JsonAST.JValue) => { + in ! p + JsCmds.Noop + }).cmd) + + } + } + + /** + * Pass in a LiftActor and get a JavaScript expression (function(x) {...}) that + * represents an asynchronous Actor message from the client to the server. + * + * The Actor should respond to a message in the form of a JsonAST.JValue. + * + * This method requires the session be stateful + * + * In general, your Actor should be a subclass of ScopedLiftActor because + * that way you'll have the scope of the current session. + * + * @param in the Actor to send messages to. + * @param xlate a function that will take the JsonAST.JValue and convert it + * into a representation that can be sent to the Actor (probably + * deserialize it into a case class.) If the translation succeeds, + * the translated message will be sent to the actor. If the + * translation fails, an error will be logged and the raw + * JsonAST.JValue will be sent to the actor + * + * + * @return a JsExp that contains a function that can be called with a parameter + * and when the function is called, the parameter is JSON serialized and sent to + * the server + */ + def clientActorFor(in: LiftActor, xlate: JsonAST.JValue => Box[Any]): JsExp = { + testStatefulFeature{ + AnonFunc("x", + SHtml.jsonCall(JsRaw("x"), (p: JsonAST.JValue) => { + in.!(xlate(p) match { + case Full(v) => v + case Empty => logger.error("Failed to deserialize JSON message "+p); p + case Failure(msg, _, _) => logger.error("Failed to deserialize JSON message "+p+". Error "+msg); p + }) + JsCmds.Noop + }).cmd) + + } + } + + + /** + * Create a Actor that will take messages on the server and then send them to the client. So, from the + * server perspective, it's just an Async message send. From the client perspective, they get a function + * called each time the message is sent to the server. + * + * If the message sent to the LiftActor is a JsCmd or JsExp, then the code is sent directly to the + * client and executed on the client. + * + * If the message is a JsonAST.JValue, it's turned into a JSON string, sent to the client and + * the client calls the function named in the `toCall` parameter with the value. + * + * If the message is anything else, we attempt to JSON serialize the message and if it + * can be JSON serialized, it's sent over the wire and passed to the `toCall` function on the server + * @return + */ + def serverActorForClient(toCall: String): LiftActor = { + testStatefulFeature{ + val ca = new CometActor { + /** + * It's the main method to override, to define what is rendered by the CometActor + * + * There are implicit conversions for a bunch of stuff to + * RenderOut (including NodeSeq). Thus, if you don't declare the return + * turn to be something other than RenderOut and return something that's + * coercible into RenderOut, the compiler "does the right thing"(tm) for you. + *
    + * There are implicit conversions for NodeSeq, so you can return a pile of + * XML right here. There's an implicit conversion for NodeSeq => NodeSeq, + * so you can return a function (e.g., a CssBindFunc) that will convert + * the defaultHtml to the correct output. There's an implicit conversion + * from JsCmd, so you can return a pile of JavaScript that'll be shipped + * to the browser.
    + * Note that the render method will be called each time a new browser tab + * is opened to the comet component or the comet component is otherwise + * accessed during a full page load (this is true if a partialUpdate + * has occurred.) You may want to look at the fixedRender method which is + * only called once and sets up a stable rendering state. + */ + def render: RenderOut = NodeSeq.Empty + + + + override def lifespan = Full(120) + + override def hasOuter = false + + override def parentTag =
    + + override def lowPriority: PartialFunction[Any, Unit] = { + case jsCmd: JsCmd => partialUpdate(JsCmds.JsTry(jsCmd, false)) + case jsExp: JsExp => partialUpdate(JsCmds.JsTry(jsExp.cmd, false)) + case jv: JsonAST.JValue => { + val s: String = json.pretty(json.render(jv)) + partialUpdate(JsCmds.JsTry(JsRaw(toCall+"("+s+")").cmd, false)) + } + case x: AnyRef => { + println("Hey we got the message "+x) + import json._ + implicit val formats = Serialization.formats(NoTypeHints) + + val ser: Box[String] = Helpers.tryo(Serialization.write(x)) + + ser.foreach(s => partialUpdate(JsCmds.JsTry(JsRaw(toCall+"("+s+")").cmd, false))) + + } + + case _ => // this will never happen because the message is boxed + + } + } + + synchronized { + asyncComponents(ca.theType -> ca.name) = ca + asyncById(ca.uniqueId) = ca + } + + ca.callInitCometActor(this, Full(Helpers.nextFuncName), Full(Helpers.nextFuncName), NodeSeq.Empty, Map.empty) + + + + ca ! PerformSetupComet2(Empty) + + val node: Elem = ca.buildSpan(ca.renderClock, NodeSeq.Empty) + + S.addCometAtEnd(node) + + ca + } + } + + /** * Processes the surround tag and other lift tags * @@ -2006,6 +2194,18 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, case Group(nodes) => Group(processSurroundAndInclude(page, nodes)) + + case elem@DataAttrNode(toDo) => toDo match { + case DataAttributeProcessorAnswerNodes(nodes) => + processSurroundAndInclude(page, nodes) + + case DataAttributeProcessorAnswerFork(nodeFunc) => + processOrDefer(true)(processSurroundAndInclude(page, nodeFunc())) + case DataAttributeProcessorAnswerFuture(nodeFuture) => + processOrDefer(true)(processSurroundAndInclude(page, + nodeFuture.get(15000).openOr(NodeSeq.Empty))) + } + case elem @ SnippetNode(element, kids, isLazy, attrs, snippetName) if snippetName != _lastFoundSnippet.value => processOrDefer(isLazy) { S.withCurrentSnippetNodeSeq(elem) { @@ -2024,7 +2224,7 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, case v: Elem => Elem(v.prefix, v.label, processAttributes(v.attributes, this.allowAttributeProcessing.is), - v.scope, processSurroundAndInclude(page, v.child): _*) + v.scope, v.minimizeEmpty, processSurroundAndInclude(page, v.child): _*) case pcd: scala.xml.PCData => pcd case text: Text => text @@ -2425,7 +2625,7 @@ private object SnippetNode { } yield { val (par, nonLift) = liftAttrsAndParallel(elm.attributes) val newElm = new Elem(elm.prefix, elm.label, - nonLift, elm.scope, elm.child: _*) + nonLift, elm.scope, elm.minimizeEmpty, elm.child: _*) (newElm, newElm, par || (lift.find { case up: UnprefixedAttribute if up.key == "parallel" => true @@ -2442,3 +2642,108 @@ private object SnippetNode { } } } + +/** + * A LiftActor that runs in the scope of the current Session, repleat with SessionVars, etc. + * In general, you'll want to use a ScopedLiftActor when you do stuff with clientActorFor, etc. + * so that you have the session scope + * + */ +trait ScopedLiftActor extends LiftActor with LazyLoggable { + /** + * The session captured when the instance is created. It should be correct if the instance is created + * in the scope of a request + */ + protected val _session: LiftSession = S.session openOr new LiftSession("", Helpers.nextFuncName, Empty) + + /** + * The render version of the page that this was created in the scope of + */ + protected val _uniqueId: String = RenderVersion.get + + /** + * The session associated with this actor. By default it's captured at the time of instantiation, but + * that doesn't always work, so you might have to override this method + * @return + */ + def session: LiftSession = _session + + + /** + * The unique page ID of the page that this Actor was created in the scope of + * @return + */ + def uniqueId: String = _uniqueId + + /** + * Compose the Message Handler function. By default, + * composes highPriority orElse mediumPriority orElse internalHandler orElse + * lowPriority orElse internalHandler. But you can change how + * the handler works if doing stuff in highPriority, mediumPriority and + * lowPriority is not enough. + */ + protected def composeFunction: PartialFunction[Any, Unit] = composeFunction_i + + private def composeFunction_i: PartialFunction[Any, Unit] = { + // if we're no longer running don't pass messages to the other handlers + // just pass them to our handlers + highPriority orElse mediumPriority orElse + lowPriority + } + + /** + * Handle messages sent to this Actor before the + */ + def highPriority: PartialFunction[Any, Unit] = Map.empty + + def lowPriority: PartialFunction[Any, Unit] = Map.empty + + def mediumPriority: PartialFunction[Any, Unit] = Map.empty + + protected override def messageHandler = { + val what = composeFunction + val myPf: PartialFunction[Any, Unit] = new PartialFunction[Any, Unit] { + def apply(in: Any): Unit = + S.initIfUninitted(session) { + RenderVersion.doWith(uniqueId) { + S.functionLifespan(true) { + try { + what.apply(in) + } catch { + case e if exceptionHandler.isDefinedAt(e) => exceptionHandler(e) + case e: Exception => reportError("Message dispatch for " + in, e) + } + if (S.functionMap.size > 0) { + session.updateFunctionMap(S.functionMap, uniqueId, millis) + S.clearFunctionMap + } + } + } + } + + def isDefinedAt(in: Any): Boolean = + S.initIfUninitted(session) { + RenderVersion.doWith(uniqueId) { + S.functionLifespan(true) { + try { + what.isDefinedAt(in) + } catch { + case e if exceptionHandler.isDefinedAt(e) => exceptionHandler(e); false + case e: Exception => reportError("Message test for " + in, e); false + } + } + } + } + } + + myPf + } + + /** + * How to report an error that occurs during message dispatch + */ + protected def reportError(msg: String, exception: Exception) { + logger.error(msg, exception) + } + +} diff --git a/web/webkit/src/main/scala/net/liftweb/http/S.scala b/web/webkit/src/main/scala/net/liftweb/http/S.scala index 9092ca08d1..e235b86abd 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/S.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/S.scala @@ -362,6 +362,8 @@ trait S extends HasParams with Loggable { */ private object _tailTags extends TransientRequestVar(new ListBuffer[Elem]) + private object _cometTags extends TransientRequestVar(new ListBuffer[Elem]) + private object p_queryLog extends TransientRequestVar(new ListBuffer[(String, Long)]) private object p_notice extends TransientRequestVar(new ListBuffer[(NoticeType.Value, NodeSeq, Box[String])]) @@ -541,7 +543,7 @@ trait S extends HasParams with Loggable { // TODO: Is this used anywhere? - DCB def templateFromTemplateAttr: Box[NodeSeq] = for (templateName <- attr("template") ?~ "Template Attribute missing"; - val tmplList = templateName.roboSplit("/"); + tmplList = templateName.roboSplit("/"); template <- Templates(tmplList) ?~ "couldn't find template") yield template @@ -788,6 +790,11 @@ trait S extends HasParams with Loggable { */ def atEndOfBody(): List[Elem] = _tailTags.is.toList + + def addCometAtEnd(elem: Elem): Unit = _cometTags.is += elem + + def cometAtEnd(): List[Elem] = _cometTags.is.toList + /** * Sometimes it's helpful to accumute JavaScript as part of servicing * a request. For example, you may want to accumulate the JavaScript diff --git a/web/webkit/src/main/scala/net/liftweb/http/auth/HttpAuthentication.scala b/web/webkit/src/main/scala/net/liftweb/http/auth/HttpAuthentication.scala index f6c7bcb673..1f5c448f30 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/auth/HttpAuthentication.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/auth/HttpAuthentication.scala @@ -112,7 +112,7 @@ case class HttpDigestAuthentication(realmName: String)(func: PartialFunction[(St try { Schedule.schedule (this, CheckAndPurge, 5 seconds) } catch { - case e => logger.error("Couldn't start NonceWatcher ping", e) + case e: Exception => logger.error("Couldn't start NonceWatcher ping", e) } } diff --git a/web/webkit/src/main/scala/net/liftweb/http/js/JsCommands.scala b/web/webkit/src/main/scala/net/liftweb/http/js/JsCommands.scala index 924d26945b..46cbc6ebbd 100755 --- a/web/webkit/src/main/scala/net/liftweb/http/js/JsCommands.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/js/JsCommands.scala @@ -261,9 +261,9 @@ object JE { /** * gets the element by ID */ - case class ElemById(id: String, then: String*) extends JsExp { + case class ElemById(id: String, thenStr: String*) extends JsExp { override def toJsCmd = "document.getElementById(" + id.encJs + ")" + ( - if (then.isEmpty) "" else then.mkString(".", ".", "") + if (thenStr.isEmpty) "" else thenStr.mkString(".", ".", "") ) } @@ -781,9 +781,9 @@ object JsCmds { * Assigns the value of 'right' to the members of the element * having this 'id', chained by 'then' sequences */ - case class SetElemById(id: String, right: JsExp, then: String*) extends JsCmd { + case class SetElemById(id: String, right: JsExp, thenStr: String*) extends JsCmd { def toJsCmd = "if (document.getElementById(" + id.encJs + ")) {document.getElementById(" + id.encJs + ")" + ( - if (then.isEmpty) "" else then.mkString(".", ".", "") + if (thenStr.isEmpty) "" else thenStr.mkString(".", ".", "") ) + " = " + right.toJsCmd + ";};" } diff --git a/web/webkit/src/main/scala/net/liftweb/http/provider/HTTPProvider.scala b/web/webkit/src/main/scala/net/liftweb/http/provider/HTTPProvider.scala index 189f868428..abf75f7289 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/provider/HTTPProvider.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/provider/HTTPProvider.scala @@ -87,8 +87,17 @@ trait HTTPProvider { preBoot b.boot } catch { - case e => - logger.error("Failed to Boot! Your application may not run properly", e); + case e: Exception => + logger.error("------------------------------------------------------------------") + logger.error("------------------------------------------------------------------") + logger.error("------------------------------------------------------------------") + logger.error("------------------------------------------------------------------") + logger.error("********** Failed to Boot! Your application may not run properly", e); + logger.error("------------------------------------------------------------------") + logger.error("------------------------------------------------------------------") + logger.error("------------------------------------------------------------------") + logger.error("------------------------------------------------------------------") + logger.error("------------------------------------------------------------------") } finally { postBoot @@ -114,7 +123,7 @@ trait HTTPProvider { LiftRules.templateCache = Full(InMemoryCache(500)) } } catch { - case _ => logger.error("LiftWeb core resource bundle for locale " + Locale.getDefault() + ", was not found ! ") + case _: Exception => logger.error("LiftWeb core resource bundle for locale " + Locale.getDefault() + ", was not found ! ") } finally { LiftRules.bootFinished() } diff --git a/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPResponseServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPResponseServlet.scala index cd079ce590..c466d5221f 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPResponseServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPResponseServlet.scala @@ -43,7 +43,7 @@ class HTTPResponseServlet(resp: HttpServletResponse) extends HTTPResponse { val cook30 = cookie.asInstanceOf[{def setHttpOnly(b: Boolean): Unit}] cook30.setHttpOnly(bv) } catch { - case e => // swallow.. the exception will be thrown for Servlet 2.5 containers but work for servlet + case e: Exception => // swallow.. the exception will be thrown for Servlet 2.5 containers but work for servlet // 3.0 containers } } diff --git a/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/containers/Jetty6AsyncProvider.scala b/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/containers/Jetty6AsyncProvider.scala index b5684bcd6f..b5a9cf855b 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/containers/Jetty6AsyncProvider.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/containers/Jetty6AsyncProvider.scala @@ -50,7 +50,7 @@ object Jetty6AsyncProvider extends AsyncProviderMeta { val isPending = cci.getMethod("isPending") (true, (cc), (meth), (getObj), (setObj), (suspend), resume, isPending) } catch { - case e => (false, null, null, null, null, null, null, null) + case e: Exception => (false, null, null, null, null, null, null, null) } } diff --git a/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/containers/Jetty7AsyncProvider.scala b/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/containers/Jetty7AsyncProvider.scala index ab553de8a1..8ccff77793 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/containers/Jetty7AsyncProvider.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/containers/Jetty7AsyncProvider.scala @@ -54,7 +54,7 @@ object Jetty7AsyncProvider extends AsyncProviderMeta { val isResumed = cci.getMethod("isResumed") (true, (cc), (meth), (getAttribute), (setAttribute), (suspend), setTimeout, resume, isExpired, isResumed) } catch { - case e => + case e: Exception => (false, null, null, null, null, null, null, null, null, null) } } @@ -128,7 +128,7 @@ class Jetty7AsyncProvider(req: HTTPRequest) extends ServletAsyncProvider { resumeMeth.invoke(cont) true } catch { - case e => setAttribute.invoke(cont, "__liftCometState", null) + case e: Exception => setAttribute.invoke(cont, "__liftCometState", null) false } } diff --git a/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/containers/Servlet30AsyncProvider.scala b/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/containers/Servlet30AsyncProvider.scala index 878c4e3f1f..f3a24579af 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/containers/Servlet30AsyncProvider.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/containers/Servlet30AsyncProvider.scala @@ -48,7 +48,7 @@ object Servlet30AsyncProvider extends AsyncProviderMeta { val isSupported = cc.getMethod("isAsyncSupported") (true, cc, asyncClass, startAsync, getResponse, complete, isSupported) } catch { - case e => + case e: Exception => (false, null, null, diff --git a/web/webkit/src/main/scala/net/liftweb/http/rest/RestHelper.scala b/web/webkit/src/main/scala/net/liftweb/http/rest/RestHelper.scala index ff7c638c7a..eb8448b6d5 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/rest/RestHelper.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/rest/RestHelper.scala @@ -20,6 +20,7 @@ package rest import net.liftweb._ +import actor.LAFuture import json._ import common._ import util._ @@ -520,6 +521,37 @@ trait RestHelper extends LiftRules.DispatchPF { protected implicit def thingToResp[T](in: T)(implicit c: T => LiftResponse): () => Box[LiftResponse] = () => Full(c(in)) + + /** + * If we're returning a future, then automatically turn the request into an Async request + * @param in the LAFuture of the response type + * @param c the implicit conversion from T to LiftResponse + * @tparam T the type + * @return Nothing + */ + protected implicit def futureToResponse[T](in: LAFuture[T])(implicit c: T => LiftResponse): + () => Box[LiftResponse] = () => { + RestContinuation.async(reply => { + in.foreach(t => reply.apply(c(t))) + }) + } + + /** + * If we're returning a future, then automatically turn the request into an Async request + * @param in the LAFuture of the response type + * @param c the implicit conversion from T to LiftResponse + * @tparam T the type + * @return Nothing + */ + protected implicit def futureBoxToResponse[T](in: LAFuture[Box[T]])(implicit c: T => LiftResponse): + () => Box[LiftResponse] = () => { + RestContinuation.async(reply => { + in.foreach(t => reply.apply{ + boxToResp(t).apply() openOr NotFoundResponse() + }) + }) + } + /** * Turn a Box[T] into the return type expected by * DispatchPF. Note that this method will return From 9b77ed65f0826e4b0264e20bdbcbb1f14b9c088d Mon Sep 17 00:00:00 2001 From: David Pollak Date: Wed, 13 Feb 2013 13:56:25 -0800 Subject: [PATCH 0326/1949] Added Tag Processing --- .../scala/net/liftweb/http/LiftRules.scala | 29 ++++++++++++++++++- .../scala/net/liftweb/http/LiftSession.scala | 29 +++++++++++++++---- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index fb8c35d1cb..aaf7289f22 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -128,7 +128,12 @@ object LiftRules extends LiftRulesMocker { * A partial function that allows processing of any attribute on an Elem * if the attribute begins with "data-" */ - type DataAttributeProcessor = PartialFunction[(String, String, Elem), DataAttributeProcessorAnswer] + type DataAttributeProcessor = PartialFunction[(String, String, Elem, LiftSession), DataAttributeProcessorAnswer] + + /** + * The pattern/PartialFunction for matching tags in Lift + */ + type TagProcessor = PartialFunction[(String, Elem, LiftSession), DataAttributeProcessorAnswer] /** * The test between the path of a request and whether that path @@ -886,6 +891,28 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { */ val dataAttributeProcessor: RulesSeq[DataAttributeProcessor] = new RulesSeq() + /** + * Ever wanted to match on *any* arbitrary tag in your HTML and process it + * any way you wanted? Well, here's your chance, dude. You can capture any + * tag and do anything you want with it. + * + * Note that this set of PartialFunctions is run for **EVERY** node + * in the DOM so make sure it runs *FAST*. + * + * Also, no subsequent processing of the returned NodeSeq is done (no + * LiftSession.processSurroundAndInclude()) so evaluate everything + * you want to. + * + * But do avoid infinite loops, so make sure the PartialFunction actually + * returns true *only* when you're going to return a modified node. + * + * An example might be: + * + * + * case ("script", e, session) if e.getAttribute("data-serverscript").isDefined => ... + */ + val tagProcessor: RulesSeq[TagProcessor] = new RulesSeq() + /** * There may be times when you want to entirely control the templating process. You can insert diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index dc858fdaaa..819a92a787 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -1997,14 +1997,14 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, private object _lastFoundSnippet extends ThreadGlobal[String] private object DataAttrNode { + val rules = LiftRules.dataAttributeProcessor.toList + def unapply(in: Node): Option[DataAttributeProcessorAnswer] = { in match { - case e: Elem => + case e: Elem if !rules.isEmpty => val attrs = e.attributes - val rules = LiftRules.dataAttributeProcessor.toList - e.attributes.toStream.flatMap { case UnprefixedAttribute(key, value, _) if key.toLowerCase().startsWith("data-") => val nk = key.substring(5).toLowerCase() @@ -2014,8 +2014,8 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, case _ => true } - NamedPF.applyBox((nk, vs, new Elem(e.prefix, e.label, a2, e.scope, e.minimizeEmpty, e.child :_*)), rules) - case _ => None + NamedPF.applyBox((nk, vs, new Elem(e.prefix, e.label, a2, e.scope, e.minimizeEmpty, e.child :_*), LiftSession.this), rules) + case _ => Empty }.headOption case _ => None @@ -2023,6 +2023,18 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, } } + private object TagProcessingNode { + val rules = LiftRules.tagProcessor.toList + + def unapply(in: Node): Option[DataAttributeProcessorAnswer] = { + in match { + case e: Elem if !rules.isEmpty => + NamedPF.applyBox((e.label, e, LiftSession.this), rules) + case _ => None + } + } + } + /** * Pass in a LiftActor and get a JavaScript expression (function(x) {...}) that * represents an asynchronous Actor message from the client to the server. @@ -2194,6 +2206,13 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, case Group(nodes) => Group(processSurroundAndInclude(page, nodes)) + case elem@TagProcessingNode(toDo) => toDo match { + case DataAttributeProcessorAnswerNodes(nodes) => nodes + case DataAttributeProcessorAnswerFork(nodeFunc) => + processOrDefer(true)(nodeFunc()) + case DataAttributeProcessorAnswerFuture(nodeFuture) => + processOrDefer(true)(nodeFuture.get(15000).openOr(NodeSeq.Empty)) + } case elem@DataAttrNode(toDo) => toDo match { case DataAttributeProcessorAnswerNodes(nodes) => From f14302a7e585d8b7a95ab7d7e797e668a7aef6f1 Mon Sep 17 00:00:00 2001 From: David Pollak Date: Wed, 13 Feb 2013 15:28:27 -0800 Subject: [PATCH 0327/1949] Snippets processed before Tags --- .../scala/net/liftweb/http/LiftSession.scala | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index 819a92a787..87fb37fea1 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -2206,14 +2206,6 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, case Group(nodes) => Group(processSurroundAndInclude(page, nodes)) - case elem@TagProcessingNode(toDo) => toDo match { - case DataAttributeProcessorAnswerNodes(nodes) => nodes - case DataAttributeProcessorAnswerFork(nodeFunc) => - processOrDefer(true)(nodeFunc()) - case DataAttributeProcessorAnswerFuture(nodeFuture) => - processOrDefer(true)(nodeFuture.get(15000).openOr(NodeSeq.Empty)) - } - case elem@DataAttrNode(toDo) => toDo match { case DataAttributeProcessorAnswerNodes(nodes) => processSurroundAndInclude(page, nodes) @@ -2241,6 +2233,14 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, } } + case elem@TagProcessingNode(toDo) => toDo match { + case DataAttributeProcessorAnswerNodes(nodes) => nodes + case DataAttributeProcessorAnswerFork(nodeFunc) => + processOrDefer(true)(nodeFunc()) + case DataAttributeProcessorAnswerFuture(nodeFuture) => + processOrDefer(true)(nodeFuture.get(15000).openOr(NodeSeq.Empty)) + } + case v: Elem => Elem(v.prefix, v.label, processAttributes(v.attributes, this.allowAttributeProcessing.is), v.scope, v.minimizeEmpty, processSurroundAndInclude(page, v.child): _*) From 5943e5d9bb2655ede9a1d25e4539fbdf31f33239 Mon Sep 17 00:00:00 2001 From: David Pollak Date: Wed, 13 Feb 2013 16:03:38 -0800 Subject: [PATCH 0328/1949] A helper to remove an attribute from an Elem --- .../scala/net/liftweb/util/BindHelpers.scala | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/core/util/src/main/scala/net/liftweb/util/BindHelpers.scala b/core/util/src/main/scala/net/liftweb/util/BindHelpers.scala index b902197f35..a2e4349bc0 100644 --- a/core/util/src/main/scala/net/liftweb/util/BindHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/BindHelpers.scala @@ -147,6 +147,23 @@ trait BindHelpers { case _ => elem } + + /** + * Remove an attribute from the element + * + * @param name the name of the attribute to remove + * @param elem the element + * @return the element sans the named attribute + */ + def removeAttribute(name: String, elem: Elem): Elem = { + val a = elem.attributes.filter{ + case up: UnprefixedAttribute => up.key != name + case _ => true + } + + elem.copy(attributes = a) + } + /** * Adds a css class to the existing class tag of an Elem or create * the class attribute @@ -1255,7 +1272,7 @@ trait BindHelpers { new Elem(e.prefix, e.label, new UnprefixedAttribute("id", id, meta), - e.scope, e.child :_*) + e.scope, e.minimizeEmpty, e.child :_*) } case x => x From 093f56b029e1fbf7c9f6c3553ab2cbbb2bf6ac2a Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 27 Jan 2013 11:56:07 -0400 Subject: [PATCH 0329/1949] Fix the field() method in LiftScreen that uses an underlying field so it incorporates passes in validations as well as the validations on the underlying field. --- contributors.md | 6 ++++++ .../main/scala/net/liftweb/http/LiftScreen.scala | 14 ++++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/contributors.md b/contributors.md index 012ed3e785..69d792ba3d 100644 --- a/contributors.md +++ b/contributors.md @@ -120,3 +120,9 @@ Gregory Flanagan ### Email: ### gregmflanagan at gmail dot com + +### Name: ### +Chris Gaudreau + +### Email: ### +webmaster at crystala dot net \ No newline at end of file diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala index 1427243ff6..2a415b31f5 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala @@ -477,13 +477,11 @@ trait AbstractScreen extends Factory { override def helpAsHtml = newHelp or underlying.helpAsHtml - override def validate: List[FieldError] = underlying.validate + override def validate = underlying.validate ::: super.validate - /* override def validations = stuff.collect { - case AVal(f) => f - }.toList ::: underlying.validations - */ + case AVal(f) => f.asInstanceOf[ValueType => List[FieldError]] + }.toList override def setFilter = stuff.collect { case AFilter(f) => f @@ -581,7 +579,11 @@ trait AbstractScreen extends Factory { override def helpAsHtml = newHelp or underlying.flatMap(_.helpAsHtml) - override def validate: List[FieldError] = underlying.toList.flatMap(_.validate) + override def validate = underlying.toList.flatMap(_.validate) ::: super.validate + + override def validations = stuff.collect { + case AVal(f) => f.asInstanceOf[ValueType => List[FieldError]] + }.toList override def setFilter = stuff.collect { case AFilter(f) => f From c1a256728c57f58aabb424b6662020a3302c2a08 Mon Sep 17 00:00:00 2001 From: Naftoli Gugenheim Date: Thu, 7 Feb 2013 20:38:07 -0500 Subject: [PATCH 0330/1949] Rename ComputeTransformRules->CanBind, #{computeTransform->apply} --- .../main/scala/net/liftweb/util/CssSel.scala | 158 +++++++++--------- 1 file changed, 77 insertions(+), 81 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/CssSel.scala b/core/util/src/main/scala/net/liftweb/util/CssSel.scala index 2f03275a8d..9d68384263 100644 --- a/core/util/src/main/scala/net/liftweb/util/CssSel.scala +++ b/core/util/src/main/scala/net/liftweb/util/CssSel.scala @@ -724,86 +724,84 @@ final class CssJBridge { } -trait ComputeTransformRules[-T] { - def name: String = "ComputeTransformRule" - def computeTransform(it: => T, ns: NodeSeq): Seq[NodeSeq] +trait CanBind[-T] { + def apply(it: => T)(ns: NodeSeq): Seq[NodeSeq] } -object ComputeTransformRules { - implicit def stringTransform: ComputeTransformRules[String] = new ComputeTransformRules[String] { - def computeTransform(str: => String, ns: NodeSeq): Seq[NodeSeq] = { +object CanBind { + implicit def stringTransform: CanBind[String] = new CanBind[String] { + def apply(str: => String)(ns: NodeSeq): Seq[NodeSeq] = { val s = str List(if (null eq s) NodeSeq.Empty else Text(s)) } } - implicit def bindableTransform: ComputeTransformRules[Bindable] = new ComputeTransformRules[Bindable] { - def computeTransform(str: => Bindable, ns: NodeSeq): Seq[NodeSeq] = List(str.asHtml) + implicit def bindableTransform: CanBind[Bindable] = new CanBind[Bindable] { + def apply(str: => Bindable)(ns: NodeSeq): Seq[NodeSeq] = List(str.asHtml) } - implicit def numberTransform[T <: java.lang.Number]: ComputeTransformRules[T] = new ComputeTransformRules[java.lang.Number] { - def computeTransform(str: => java.lang.Number, ns: NodeSeq): Seq[NodeSeq] = { + implicit def numberTransform[T <: java.lang.Number]: CanBind[T] = new CanBind[java.lang.Number] { + def apply(str: => java.lang.Number)(ns: NodeSeq): Seq[NodeSeq] = { val num = str List(if (null eq num) NodeSeq.Empty else Text(num.toString)) } } - implicit def doubleTRansform: ComputeTransformRules[Double] = new ComputeTransformRules[Double] { - def computeTransform(str: => Double, ns: NodeSeq): Seq[NodeSeq] = List(Text(str.toString)) + implicit def doubleTRansform: CanBind[Double] = new CanBind[Double] { + def apply(str: => Double)(ns: NodeSeq): Seq[NodeSeq] = List(Text(str.toString)) } - implicit def jsCmdTransform: ComputeTransformRules[ToJsCmd] = new ComputeTransformRules[ToJsCmd] { - def computeTransform(str: => ToJsCmd, ns: NodeSeq): Seq[NodeSeq] = List(Text(str.toJsCmd)) + implicit def jsCmdTransform: CanBind[ToJsCmd] = new CanBind[ToJsCmd] { + def apply(str: => ToJsCmd)(ns: NodeSeq): Seq[NodeSeq] = List(Text(str.toJsCmd)) } - implicit def jsCmdPairTransform: ComputeTransformRules[(_, ToJsCmd)] = new ComputeTransformRules[(_, ToJsCmd)] { - def computeTransform(str: => (_, ToJsCmd), ns: NodeSeq): Seq[NodeSeq] = List(Text(str._2.toJsCmd)) + implicit def jsCmdPairTransform: CanBind[(_, ToJsCmd)] = new CanBind[(_, ToJsCmd)] { + def apply(str: => (_, ToJsCmd))(ns: NodeSeq): Seq[NodeSeq] = List(Text(str._2.toJsCmd)) } - implicit def intTransform: ComputeTransformRules[Int] = new ComputeTransformRules[Int] { - def computeTransform(str: => Int, ns: NodeSeq): Seq[NodeSeq] = List(Text(str.toString)) + implicit def intTransform: CanBind[Int] = new CanBind[Int] { + def apply(str: => Int)(ns: NodeSeq): Seq[NodeSeq] = List(Text(str.toString)) } - implicit def stringPromoteTransform: ComputeTransformRules[StringPromotable] = new ComputeTransformRules[StringPromotable] { - def computeTransform(str: => StringPromotable, ns: NodeSeq): Seq[NodeSeq] = List(Text(str.toString)) + implicit def stringPromoteTransform: CanBind[StringPromotable] = new CanBind[StringPromotable] { + def apply(str: => StringPromotable)(ns: NodeSeq): Seq[NodeSeq] = List(Text(str.toString)) } - implicit def symbolTransform: ComputeTransformRules[Symbol] = new ComputeTransformRules[Symbol] { - def computeTransform(str: => Symbol, ns: NodeSeq): Seq[NodeSeq] = List(Text(str.name)) + implicit def symbolTransform: CanBind[Symbol] = new CanBind[Symbol] { + def apply(str: => Symbol)(ns: NodeSeq): Seq[NodeSeq] = List(Text(str.name)) } - implicit def longTransform: ComputeTransformRules[Long] = new ComputeTransformRules[Long] { - def computeTransform(str: => Long, ns: NodeSeq): Seq[NodeSeq] = List(Text(str.toString)) + implicit def longTransform: CanBind[Long] = new CanBind[Long] { + def apply(str: => Long)(ns: NodeSeq): Seq[NodeSeq] = List(Text(str.toString)) } - implicit def boolTransform: ComputeTransformRules[Boolean] = new ComputeTransformRules[Boolean] { - def computeTransform(str: => Boolean, ns: NodeSeq): Seq[NodeSeq] = List(Text(str.toString)) + implicit def boolTransform: CanBind[Boolean] = new CanBind[Boolean] { + def apply(str: => Boolean)(ns: NodeSeq): Seq[NodeSeq] = List(Text(str.toString)) } - - implicit def toNodeSeqTransform[T](implicit f: T => NodeSeq): ComputeTransformRules[T] = new ComputeTransformRules[T] { - override def name: String = "def toNodeSeqTransform[T]" - def computeTransform(param: => T, ns: NodeSeq): Seq[NodeSeq] = List(f(param)) + class CanBindNodeSeqTransform[T](f: T => NodeSeq) extends CanBind[T] { + def apply(param: => T)(ns: NodeSeq): Seq[NodeSeq] = List(f(param)) } + implicit def toNodeSeqTransform[T](implicit f: T => NodeSeq): CanBind[T] = new CanBindNodeSeqTransform[T](f) - implicit def nodeSeqFuncTransform[A](implicit view: A => NodeSeq => NodeSeq): ComputeTransformRules[A] = new ComputeTransformRules[A] { - def computeTransform(func: =>A, ns: NodeSeq): Seq[NodeSeq] = List(view(func)(ns)) + implicit def nodeSeqFuncTransform[A](implicit view: A => NodeSeq => NodeSeq): CanBind[A] = new CanBind[A] { + def apply(func: =>A)(ns: NodeSeq): Seq[NodeSeq] = List(view(func)(ns)) } - implicit def nodeSeqSeqFuncTransform: ComputeTransformRules[NodeSeq => Seq[Node]] = new ComputeTransformRules[NodeSeq => Seq[Node]] { - def computeTransform(func: => NodeSeq => Seq[Node], ns: NodeSeq): Seq[NodeSeq] = Helpers.ensureUniqueId(List(func(ns))) + implicit def nodeSeqSeqFuncTransform: CanBind[NodeSeq => Seq[Node]] = new CanBind[NodeSeq => Seq[Node]] { + def apply(func: => NodeSeq => Seq[Node])(ns: NodeSeq): Seq[NodeSeq] = Helpers.ensureUniqueId(List(func(ns))) } - implicit def nodeFuncTransform: ComputeTransformRules[NodeSeq => Node] = new ComputeTransformRules[NodeSeq => Node] { - def computeTransform(func: => NodeSeq => Node, ns: NodeSeq): Seq[NodeSeq] = List(func(ns)) + implicit def nodeFuncTransform: CanBind[NodeSeq => Node] = new CanBind[NodeSeq => Node] { + def apply(func: => NodeSeq => Node)(ns: NodeSeq): Seq[NodeSeq] = List(func(ns)) } - implicit def iterableNodeTransform[NST](implicit f2: NST => NodeSeq): ComputeTransformRules[Iterable[NST]] = - new ComputeTransformRules[Iterable[NST]] { - def computeTransform(info: => Iterable[NST], ns: NodeSeq): Seq[NodeSeq] = + implicit def iterableNodeTransform[NST](implicit f2: NST => NodeSeq): CanBind[Iterable[NST]] = + new CanBind[Iterable[NST]] { + def apply(info: => Iterable[NST])(ns: NodeSeq): Seq[NodeSeq] = { val i = info i match { @@ -813,71 +811,69 @@ object ComputeTransformRules { } } - implicit def boxNodeTransform[NST](implicit f2: NST => NodeSeq): ComputeTransformRules[Box[NST]] = - new ComputeTransformRules[Box[NST]] { - def computeTransform(info: => Box[NST], ns: NodeSeq): Seq[NodeSeq] = info.toList.map(f2) + implicit def boxNodeTransform[NST](implicit f2: NST => NodeSeq): CanBind[Box[NST]] = + new CanBind[Box[NST]] { + def apply(info: => Box[NST])(ns: NodeSeq): Seq[NodeSeq] = info.toList.map(f2) } - implicit def optionNodeTransform[NST](implicit f2: NST => NodeSeq): ComputeTransformRules[Option[NST]] = - new ComputeTransformRules[Option[NST]] { - def computeTransform(info: => Option[NST], ns: NodeSeq): Seq[NodeSeq] = info.toList.map(f2) + implicit def optionNodeTransform[NST](implicit f2: NST => NodeSeq): CanBind[Option[NST]] = + new CanBind[Option[NST]] { + def apply(info: => Option[NST])(ns: NodeSeq): Seq[NodeSeq] = info.toList.map(f2) } - - implicit def iterableStringTransform[T[_]](implicit f: T[String] => Iterable[String]): ComputeTransformRules[T[String]] = - new ComputeTransformRules[T[String]] { - def computeTransform(info: => T[String], ns: NodeSeq): Seq[NodeSeq] = f(info).toSeq.map(a => Text(a)) + implicit def iterableStringTransform[T[_]](implicit f: T[String] => Iterable[String]): CanBind[T[String]] = + new CanBind[T[String]] { + def apply(info: => T[String])(ns: NodeSeq): Seq[NodeSeq] = f(info).toSeq.map(a => Text(a)) } - implicit def iterableNumberTransform[T[_], N <: java.lang.Number](implicit f: T[N] => Iterable[N]): ComputeTransformRules[T[N]] = - new ComputeTransformRules[T[N]] { - def computeTransform(info: => T[N], ns: NodeSeq): Seq[NodeSeq] = f(info).toSeq.flatMap(a => + implicit def iterableNumberTransform[T[_], N <: java.lang.Number](implicit f: T[N] => Iterable[N]): CanBind[T[N]] = + new CanBind[T[N]] { + def apply(info: => T[N])(ns: NodeSeq): Seq[NodeSeq] = f(info).toSeq.flatMap(a => if (a eq null) Nil else List(Text(a.toString))) } - implicit def iterableDouble[T[Double]](implicit f: T[Double] => Iterable[Double]): ComputeTransformRules[T[Double]] = - new ComputeTransformRules[T[Double]] { - def computeTransform(info: => T[Double], ns: NodeSeq): Seq[NodeSeq] = f(info).toSeq.flatMap(a => + implicit def iterableDouble[T[Double]](implicit f: T[Double] => Iterable[Double]): CanBind[T[Double]] = + new CanBind[T[Double]] { + def apply(info: => T[Double])(ns: NodeSeq): Seq[NodeSeq] = f(info).toSeq.flatMap(a => if (a equals null) Nil else List(Text(a.toString))) } - implicit def iterableBindableTransform[T[_]](implicit f: T[Bindable] => Iterable[Bindable]): ComputeTransformRules[T[Bindable]] = - new ComputeTransformRules[T[Bindable]] { - def computeTransform(info: => T[Bindable], ns: NodeSeq): Seq[NodeSeq] = Helpers.ensureUniqueId(f(info).toSeq.map(_.asHtml)) + implicit def iterableBindableTransform[T[_]](implicit f: T[Bindable] => Iterable[Bindable]): CanBind[T[Bindable]] = + new CanBind[T[Bindable]] { + def apply(info: => T[Bindable])(ns: NodeSeq): Seq[NodeSeq] = Helpers.ensureUniqueId(f(info).toSeq.map(_.asHtml)) } implicit def iterableStringPromotableTransform[T[_], PM](implicit f: T[PM] => Iterable[PM], - prom: PM => StringPromotable): - ComputeTransformRules[T[PM]] = - new ComputeTransformRules[T[PM]] { - def computeTransform(info: => T[PM], ns: NodeSeq): Seq[NodeSeq] = f(info).toSeq.map(a => Text(prom(a).toString)) + prom: PM => StringPromotable): CanBind[T[PM]] = + new CanBind[T[PM]] { + def apply(info: => T[PM])(ns: NodeSeq): Seq[NodeSeq] = f(info).toSeq.map(a => Text(prom(a).toString)) } - implicit def iterableNodeFuncTransform[T[_], F <: NodeSeq => NodeSeq](implicit f: T[F] => Iterable[F]): ComputeTransformRules[T[F]] = - new ComputeTransformRules[T[F]] { - def computeTransform(info: => T[F], ns: NodeSeq): Seq[NodeSeq] = Helpers.ensureUniqueId(f(info).toSeq.map(_.apply(ns))) + implicit def iterableNodeFuncTransform[T[_], F <: NodeSeq => NodeSeq](implicit f: T[F] => Iterable[F]): CanBind[T[F]] = + new CanBind[T[F]] { + def apply(info: => T[F])(ns: NodeSeq): Seq[NodeSeq] = Helpers.ensureUniqueId(f(info).toSeq.map(_.apply(ns))) } - implicit def funcIterableTransform[T[_], F <: NodeSeq](implicit f: T[F] => Iterable[F]): ComputeTransformRules[NodeSeq => T[F]] = - new ComputeTransformRules[NodeSeq => T[F]] { - def computeTransform(info: => NodeSeq => T[F], ns: NodeSeq): Seq[NodeSeq] = Helpers.ensureUniqueId(f(info(ns)).toSeq) + implicit def funcIterableTransform[T[_], F <: NodeSeq](implicit f: T[F] => Iterable[F]): CanBind[NodeSeq => T[F]] = + new CanBind[NodeSeq => T[F]] { + def apply(info: => NodeSeq => T[F])(ns: NodeSeq): Seq[NodeSeq] = Helpers.ensureUniqueId(f(info(ns)).toSeq) } - implicit def stringFuncTransform: ComputeTransformRules[NodeSeq => String] = - new ComputeTransformRules[NodeSeq => String] { - def computeTransform(info: => NodeSeq => String, ns: NodeSeq): Seq[NodeSeq] = List(Text(info(ns))) + implicit def stringFuncTransform: CanBind[NodeSeq => String] = + new CanBind[NodeSeq => String] { + def apply(info: => NodeSeq => String)(ns: NodeSeq): Seq[NodeSeq] = List(Text(info(ns))) } - implicit def stringIterFuncTransform[T[_]](implicit f: T[String] => Iterable[String]): ComputeTransformRules[NodeSeq => T[String]] = - new ComputeTransformRules[NodeSeq => T[String]] { - def computeTransform(info: => NodeSeq => T[String], ns: NodeSeq): Seq[NodeSeq] = f(info(ns)).toSeq.map(Text(_)) + implicit def stringIterFuncTransform[T[_]](implicit f: T[String] => Iterable[String]): CanBind[NodeSeq => T[String]] = + new CanBind[NodeSeq => T[String]] { + def apply(info: => NodeSeq => T[String])(ns: NodeSeq): Seq[NodeSeq] = f(info(ns)).toSeq.map(Text(_)) } - implicit def iterableConstFuncTransform: ComputeTransformRules[IterableConst] = - new ComputeTransformRules[IterableConst] { - def computeTransform(info: => IterableConst, ns: NodeSeq): Seq[NodeSeq] = info.constList(ns) + implicit def iterableConstFuncTransform: CanBind[IterableConst] = + new CanBind[IterableConst] { + def apply(info: => IterableConst)(ns: NodeSeq): Seq[NodeSeq] = info.constList(ns) } } @@ -896,15 +892,15 @@ final case class ToCssBindPromoter(stringSelector: Box[String], css: Box[CssSele * @tparam T the type of it * @return the function that will transform an incoming DOM based on the transform rules */ - def #>[T](it: => T)(implicit computer: ComputeTransformRules[T]): CssSel = { + def #>[T](it: => T)(implicit computer: CanBind[T]): CssSel = { css match { case Full(EnclosedSelector(a, b)) => null (ToCssBindPromoter(stringSelector, Full(a))).#>(nsFunc(ns => { ToCssBindPromoter(stringSelector, Full(b)).#>(it)(computer)(ns) - })) // (ComputeTransformRules.nodeSeqFuncTransform) + })) // (CanBind.nodeSeqFuncTransform) case _ => new CssBindImpl(stringSelector, css) { - def calculate(in: NodeSeq): Seq[NodeSeq] = computer.computeTransform(it, in) + def calculate(in: NodeSeq): Seq[NodeSeq] = computer(it)(in) } } } @@ -917,5 +913,5 @@ final case class ToCssBindPromoter(stringSelector: Box[String], css: Box[CssSele * @tparam T the type of it * @return the function that will transform an incoming DOM based on the transform rules */ - def replaceWith[T](it: => T)(implicit computer: ComputeTransformRules[T]): CssSel = this.#>(it)(computer) + def replaceWith[T](it: => T)(implicit computer: CanBind[T]): CssSel = this.#>(it)(computer) } From dfe47b254c60e3cac56b714d9d56204118ca9136 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 10 Feb 2013 10:01:24 -0500 Subject: [PATCH 0331/1949] Deprecate S.getHeader in favor of S.getResponseHeader. Also deprecate S.getHeaders in favor of S.getResponseHeaders, as well as adding an S.setResponseHeader (though S.setHeader is not deprecated as it is clear that it refers to the response). --- .../src/main/scala/net/liftweb/http/S.scala | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/S.scala b/web/webkit/src/main/scala/net/liftweb/http/S.scala index 9092ca08d1..8deae8e5b9 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/S.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/S.scala @@ -1482,17 +1482,28 @@ trait S extends HasParams with Loggable { rh.headers = rh.headers + (name -> value) ) } + /** + * Synonym for S.setHeader. Exists to provide the converse to + * S.getResponseHeader. + */ + def setResponseHeader(name: String, value: String) { + setHeader(name, value) + } + + @deprecated("2.5", "Use S.getResponseHeaders instead for clarity.") + def getHeaders(in: List[(String, String)]): List[(String, String)] = { /** * Returns the currently set HTTP response headers as a List[(String, String)]. To retrieve - * a specific response header, use S.getHeader. If you want to get request headers (those - * sent by the client), use Req.getHeaders or S.getRequestHeader. + * a specific response header, use S.getResponseHeader. If you want to + * get request headers (those sent by the client), use Req.getHeaders + * or S.getRequestHeader. * * @see # setHeader ( String, String ) - * @see # getHeader ( String ) + * @see # getResponseHeader ( String ) * @see # getRequestHeader ( String ) */ - def getHeaders(in: List[(String, String)]): List[(String, String)] = { + def getResponseHeaders(in: List[(String, String)]): List[(String, String)] = { Box.legacyNullTest(_responseHeaders.value).map( rh => rh.headers.iterator.toList ::: @@ -1500,6 +1511,10 @@ trait S extends HasParams with Loggable { ).openOr(Nil) } + @deprecated("2.5", "Use S.getResponseHeader instead for clarity.") + def getHeader(name: String): Box[String] = { + getResponseHeader(name) + } /** * Returns the current set value of the given HTTP response header as a Box. If * you want a request header, use Req.getHeader or S.getRequestHeader. @@ -1508,10 +1523,10 @@ trait S extends HasParams with Loggable { * @return A Full(value) or Empty if the header isn't set * * @see # setHeader ( String, String ) - * @see # getHeaders ( List[ ( String, String ) ] ) + * @see # getResponseHeaders ( List[ ( String, String ) ] ) * @see # getRequestHeader ( String ) */ - def getHeader(name: String): Box[String] = { + def getResponseHeader(name: String): Box[String] = { Box.legacyNullTest(_responseHeaders.value).map( rh => Box(rh.headers.get(name)) ).openOr(Empty) From c93528eac5e4acfc702f43dae86fb74019740407 Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Thu, 14 Feb 2013 23:48:33 -0500 Subject: [PATCH 0332/1949] fixed compiler error due to missing closing } --- web/webkit/src/main/scala/net/liftweb/http/S.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/web/webkit/src/main/scala/net/liftweb/http/S.scala b/web/webkit/src/main/scala/net/liftweb/http/S.scala index 8deae8e5b9..37a099ce99 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/S.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/S.scala @@ -1493,6 +1493,8 @@ trait S extends HasParams with Loggable { @deprecated("2.5", "Use S.getResponseHeaders instead for clarity.") def getHeaders(in: List[(String, String)]): List[(String, String)] = { + getResponseHeaders(in) + } /** * Returns the currently set HTTP response headers as a List[(String, String)]. To retrieve * a specific response header, use S.getResponseHeader. If you want to From fbdfaf177eea9d0d07a1809b45e2fea851ac8483 Mon Sep 17 00:00:00 2001 From: David Pollak Date: Tue, 19 Feb 2013 12:13:36 -0800 Subject: [PATCH 0333/1949] Fixed some bugs in the Html5 writing routines --- .../scala/net/liftweb/util/HtmlParser.scala | 166 +++++++++--------- 1 file changed, 86 insertions(+), 80 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/HtmlParser.scala b/core/util/src/main/scala/net/liftweb/util/HtmlParser.scala index 1d5f759f4b..ff6eabb6c9 100644 --- a/core/util/src/main/scala/net/liftweb/util/HtmlParser.scala +++ b/core/util/src/main/scala/net/liftweb/util/HtmlParser.scala @@ -37,7 +37,7 @@ trait Html5Writer { */ protected def writeAttributes(m: MetaData, writer: Writer) { m match { - case null => + case null => case Null => case md if (null eq md.value) => writeAttributes(md.next, writer) case up: UnprefixedAttribute => { @@ -54,21 +54,21 @@ trait Html5Writer { case '<' => writer.append("<") case c if c >= ' ' && c.toInt <= 127 => writer.append(c) case c if c == '\u0085' => - case c => { - val str = Integer.toHexString(c) - writer.append("&#x") - writer.append("0000".substring(str.length)) - writer.append(str) - writer.append(';') - } + case c => { + val str = Integer.toHexString(c) + writer.append("&#x") + writer.append("0000".substring(str.length)) + writer.append(str) + writer.append(';') + } } - + pos += 1 } - - writer.append('"') - writeAttributes(up.next, writer) + writer.append('"') + + writeAttributes(up.next, writer) } case pa: PrefixedAttribute => { @@ -100,12 +100,12 @@ trait Html5Writer { pos += 1 } - writer.append('"') + writer.append('"') } - writeAttributes(pa.next, writer) + writeAttributes(pa.next, writer) } - + case x => writeAttributes(x.next, writer) } } @@ -127,7 +127,7 @@ trait Html5Writer { case '\n' => sb.append('\n') case '\r' => sb.append('\r') case '\t' => sb.append('\t') - case c => + case c => if (reverse) { HtmlEntities.revMap.get(c) match { case Some(str) => { @@ -135,15 +135,15 @@ trait Html5Writer { sb.append(str) sb.append(';') } - case _ => - if (c >= ' ' && - c != '\u0085' && - !(c >= '\u007f' && c <= '\u0095')) sb.append(c) + case _ => + if (c >= ' ' && + c != '\u0085' && + !(c >= '\u007f' && c <= '\u0095')) sb.append(c) } } else { - if (c >= ' ' && - c != '\u0085' && - !(c >= '\u007f' && c <= '\u0095')) sb.append(c) + if (c >= ' ' && + c != '\u0085' && + !(c >= '\u007f' && c <= '\u0095')) sb.append(c) } } @@ -187,14 +187,14 @@ trait Html5Writer { case a: Atom[_] if a.getClass eq classOf[Atom[_]] => escape(a.data.toString, writer, !convertAmp) - + case Comment(comment) if !stripComment => { writer.append("") } - - case er: EntityRef => + + case er: EntityRef if convertAmp => HtmlEntities.entMap.get(er.entityName) match { case Some(chr) if chr.toInt >= 128 => writer.append(chr) case _ => { @@ -203,25 +203,32 @@ trait Html5Writer { writer.append(sb) } } - + + + case er: EntityRef => + val sb = new StringBuilder() + er.buildString(sb) + writer.append(sb) + + case x: SpecialNode => { val sb = new StringBuilder() x.buildString(sb) writer.append(sb) } - + case g: Group => for (c <- g.nodes) write(c, writer, stripComment, convertAmp) - - case e: Elem if (null eq e.prefix) && - Html5Constants.nonReplaceable_?(e.label) => { + + case e: Elem if (null eq e.prefix) && + Html5Constants.nonReplaceable_?(e.label) => { writer.append('<') writer.append(e.label) writeAttributes(e.attributes, writer) writer.append(">") e.child match { - case null => + case null => case seq => seq.foreach { case Text(str) => writer.append(str) case pc: PCData => { @@ -245,15 +252,15 @@ trait Html5Writer { writer.append(e.label) writer.append('>') } - - case e: Elem if (null eq e.prefix) && - Html5Constants.voidTag_?(e.label) => { + + case e: Elem if (null eq e.prefix) && + Html5Constants.voidTag_?(e.label) => { writer.append('<') writer.append(e.label) writeAttributes(e.attributes, writer) writer.append(">") } - + /* case e: Elem if ((e.child eq null) || e.child.isEmpty) => { @@ -266,7 +273,7 @@ trait Html5Writer { writeAttributes(e.attributes, writer) writer.append(" />") }*/ - + case e: Elem => { writer.append('<') if (null ne e.prefix) { @@ -285,7 +292,7 @@ trait Html5Writer { writer.append(e.label) writer.append('>') } - + case _ => // dunno what it is, but ignore it } } @@ -293,20 +300,20 @@ trait Html5Writer { object Html5Constants { val voidTags: Set[String] = Set("area", - "base", - "br", - "col", - "command", - "embed", - "hr", - "img", - "input", - "keygen", - "link", - "meta", - "param", - "source", - "wbr") + "base", + "br", + "col", + "command", + "embed", + "hr", + "img", + "input", + "keygen", + "link", + "meta", + "param", + "source", + "wbr") /** * Is the tag a void tag? @@ -318,14 +325,14 @@ object Html5Constants { */ def nonReplaceable_?(t: String): Boolean = (t equalsIgnoreCase "script") || - (t equalsIgnoreCase "style") + (t equalsIgnoreCase "style") } /** * A utility that supports parsing of HTML5 file. * The Parser hooks up nu.validator.htmlparser - * to + * to */ trait Html5Parser { /** @@ -344,7 +351,7 @@ trait Html5Parser { /* override def createNode (pre: String, label: String, attrs: MetaData, scope: NamespaceBinding, children: List[Node]) : Elem = { if (pre == "lift" && label == "head") { - super.createNode(null, label, attrs, scope, children) + super.createNode(null, label, attrs, scope, children) } else { super.createNode(pre, label, attrs, scope, children) } @@ -356,9 +363,9 @@ trait Html5Parser { if (text.length() > 0) { hStack.push(createText(text)) } - } - buffer.setLength(0) - } + } + buffer.setLength(0) + } } saxer.scopeStack.push(TopScope) @@ -368,11 +375,11 @@ trait Html5Parser { hp.parse(is) saxer.scopeStack.pop - + in.close() saxer.rootElem match { case null => Empty - case e: Elem => + case e: Elem => AutoInsertedBody.unapply(e) match { case Some(x) => Full(x) case _ => Full(e) @@ -383,40 +390,40 @@ trait Html5Parser { } private object AutoInsertedBody { - def checkHead(n: Node): Boolean = + def checkHead(n: Node): Boolean = n match { case e: Elem => { e.label == "head" && e.prefix == null && - e.attributes == Null && - e.child.length == 0 + e.attributes == Null && + e.child.length == 0 } case _ => false } - - def checkBody(n: Node): Boolean = + + def checkBody(n: Node): Boolean = n match { case e: Elem => { e.label == "body" && e.prefix == null && - e.attributes == Null && - e.child.length >= 1 && - e.child(0).isInstanceOf[Elem] + e.attributes == Null && + e.child.length >= 1 && + e.child(0).isInstanceOf[Elem] } case _ => false } - + def unapply(n: Node): Option[Elem] = n match { case e: Elem => { if (e.label == "html" && e.prefix == null && - e.attributes == Null && - e.child.length == 2 && - checkHead(e.child(0)) && - checkBody(e.child(1))) { - Some(e.child(1).asInstanceOf[Elem].child(0).asInstanceOf[Elem]) - } else { - None - } + e.attributes == Null && + e.child.length == 2 && + checkHead(e.child(0)) && + checkBody(e.child(1))) { + Some(e.child(1).asInstanceOf[Elem].child(0).asInstanceOf[Elem]) + } else { + None + } } - + case _ => None } } @@ -426,7 +433,6 @@ trait Html5Parser { * will be returned on successful parsing, otherwise * a Failure. */ - def parse(str: String): Box[Elem] = + def parse(str: String): Box[Elem] = parse(new ByteArrayInputStream(str.getBytes("UTF-8"))) } - From 5ba3bfe743b2c8da4884c1e1cd0186c43c81b6d4 Mon Sep 17 00:00:00 2001 From: David Pollak Date: Tue, 19 Feb 2013 12:13:36 -0800 Subject: [PATCH 0334/1949] Fixed some bugs in the Html5 writing routines --- .../scala/net/liftweb/util/HtmlParser.scala | 166 +++++++++--------- 1 file changed, 86 insertions(+), 80 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/HtmlParser.scala b/core/util/src/main/scala/net/liftweb/util/HtmlParser.scala index 1d5f759f4b..ff6eabb6c9 100644 --- a/core/util/src/main/scala/net/liftweb/util/HtmlParser.scala +++ b/core/util/src/main/scala/net/liftweb/util/HtmlParser.scala @@ -37,7 +37,7 @@ trait Html5Writer { */ protected def writeAttributes(m: MetaData, writer: Writer) { m match { - case null => + case null => case Null => case md if (null eq md.value) => writeAttributes(md.next, writer) case up: UnprefixedAttribute => { @@ -54,21 +54,21 @@ trait Html5Writer { case '<' => writer.append("<") case c if c >= ' ' && c.toInt <= 127 => writer.append(c) case c if c == '\u0085' => - case c => { - val str = Integer.toHexString(c) - writer.append("&#x") - writer.append("0000".substring(str.length)) - writer.append(str) - writer.append(';') - } + case c => { + val str = Integer.toHexString(c) + writer.append("&#x") + writer.append("0000".substring(str.length)) + writer.append(str) + writer.append(';') + } } - + pos += 1 } - - writer.append('"') - writeAttributes(up.next, writer) + writer.append('"') + + writeAttributes(up.next, writer) } case pa: PrefixedAttribute => { @@ -100,12 +100,12 @@ trait Html5Writer { pos += 1 } - writer.append('"') + writer.append('"') } - writeAttributes(pa.next, writer) + writeAttributes(pa.next, writer) } - + case x => writeAttributes(x.next, writer) } } @@ -127,7 +127,7 @@ trait Html5Writer { case '\n' => sb.append('\n') case '\r' => sb.append('\r') case '\t' => sb.append('\t') - case c => + case c => if (reverse) { HtmlEntities.revMap.get(c) match { case Some(str) => { @@ -135,15 +135,15 @@ trait Html5Writer { sb.append(str) sb.append(';') } - case _ => - if (c >= ' ' && - c != '\u0085' && - !(c >= '\u007f' && c <= '\u0095')) sb.append(c) + case _ => + if (c >= ' ' && + c != '\u0085' && + !(c >= '\u007f' && c <= '\u0095')) sb.append(c) } } else { - if (c >= ' ' && - c != '\u0085' && - !(c >= '\u007f' && c <= '\u0095')) sb.append(c) + if (c >= ' ' && + c != '\u0085' && + !(c >= '\u007f' && c <= '\u0095')) sb.append(c) } } @@ -187,14 +187,14 @@ trait Html5Writer { case a: Atom[_] if a.getClass eq classOf[Atom[_]] => escape(a.data.toString, writer, !convertAmp) - + case Comment(comment) if !stripComment => { writer.append("") } - - case er: EntityRef => + + case er: EntityRef if convertAmp => HtmlEntities.entMap.get(er.entityName) match { case Some(chr) if chr.toInt >= 128 => writer.append(chr) case _ => { @@ -203,25 +203,32 @@ trait Html5Writer { writer.append(sb) } } - + + + case er: EntityRef => + val sb = new StringBuilder() + er.buildString(sb) + writer.append(sb) + + case x: SpecialNode => { val sb = new StringBuilder() x.buildString(sb) writer.append(sb) } - + case g: Group => for (c <- g.nodes) write(c, writer, stripComment, convertAmp) - - case e: Elem if (null eq e.prefix) && - Html5Constants.nonReplaceable_?(e.label) => { + + case e: Elem if (null eq e.prefix) && + Html5Constants.nonReplaceable_?(e.label) => { writer.append('<') writer.append(e.label) writeAttributes(e.attributes, writer) writer.append(">") e.child match { - case null => + case null => case seq => seq.foreach { case Text(str) => writer.append(str) case pc: PCData => { @@ -245,15 +252,15 @@ trait Html5Writer { writer.append(e.label) writer.append('>') } - - case e: Elem if (null eq e.prefix) && - Html5Constants.voidTag_?(e.label) => { + + case e: Elem if (null eq e.prefix) && + Html5Constants.voidTag_?(e.label) => { writer.append('<') writer.append(e.label) writeAttributes(e.attributes, writer) writer.append(">") } - + /* case e: Elem if ((e.child eq null) || e.child.isEmpty) => { @@ -266,7 +273,7 @@ trait Html5Writer { writeAttributes(e.attributes, writer) writer.append(" />") }*/ - + case e: Elem => { writer.append('<') if (null ne e.prefix) { @@ -285,7 +292,7 @@ trait Html5Writer { writer.append(e.label) writer.append('>') } - + case _ => // dunno what it is, but ignore it } } @@ -293,20 +300,20 @@ trait Html5Writer { object Html5Constants { val voidTags: Set[String] = Set("area", - "base", - "br", - "col", - "command", - "embed", - "hr", - "img", - "input", - "keygen", - "link", - "meta", - "param", - "source", - "wbr") + "base", + "br", + "col", + "command", + "embed", + "hr", + "img", + "input", + "keygen", + "link", + "meta", + "param", + "source", + "wbr") /** * Is the tag a void tag? @@ -318,14 +325,14 @@ object Html5Constants { */ def nonReplaceable_?(t: String): Boolean = (t equalsIgnoreCase "script") || - (t equalsIgnoreCase "style") + (t equalsIgnoreCase "style") } /** * A utility that supports parsing of HTML5 file. * The Parser hooks up nu.validator.htmlparser - * to + * to */ trait Html5Parser { /** @@ -344,7 +351,7 @@ trait Html5Parser { /* override def createNode (pre: String, label: String, attrs: MetaData, scope: NamespaceBinding, children: List[Node]) : Elem = { if (pre == "lift" && label == "head") { - super.createNode(null, label, attrs, scope, children) + super.createNode(null, label, attrs, scope, children) } else { super.createNode(pre, label, attrs, scope, children) } @@ -356,9 +363,9 @@ trait Html5Parser { if (text.length() > 0) { hStack.push(createText(text)) } - } - buffer.setLength(0) - } + } + buffer.setLength(0) + } } saxer.scopeStack.push(TopScope) @@ -368,11 +375,11 @@ trait Html5Parser { hp.parse(is) saxer.scopeStack.pop - + in.close() saxer.rootElem match { case null => Empty - case e: Elem => + case e: Elem => AutoInsertedBody.unapply(e) match { case Some(x) => Full(x) case _ => Full(e) @@ -383,40 +390,40 @@ trait Html5Parser { } private object AutoInsertedBody { - def checkHead(n: Node): Boolean = + def checkHead(n: Node): Boolean = n match { case e: Elem => { e.label == "head" && e.prefix == null && - e.attributes == Null && - e.child.length == 0 + e.attributes == Null && + e.child.length == 0 } case _ => false } - - def checkBody(n: Node): Boolean = + + def checkBody(n: Node): Boolean = n match { case e: Elem => { e.label == "body" && e.prefix == null && - e.attributes == Null && - e.child.length >= 1 && - e.child(0).isInstanceOf[Elem] + e.attributes == Null && + e.child.length >= 1 && + e.child(0).isInstanceOf[Elem] } case _ => false } - + def unapply(n: Node): Option[Elem] = n match { case e: Elem => { if (e.label == "html" && e.prefix == null && - e.attributes == Null && - e.child.length == 2 && - checkHead(e.child(0)) && - checkBody(e.child(1))) { - Some(e.child(1).asInstanceOf[Elem].child(0).asInstanceOf[Elem]) - } else { - None - } + e.attributes == Null && + e.child.length == 2 && + checkHead(e.child(0)) && + checkBody(e.child(1))) { + Some(e.child(1).asInstanceOf[Elem].child(0).asInstanceOf[Elem]) + } else { + None + } } - + case _ => None } } @@ -426,7 +433,6 @@ trait Html5Parser { * will be returned on successful parsing, otherwise * a Failure. */ - def parse(str: String): Box[Elem] = + def parse(str: String): Box[Elem] = parse(new ByteArrayInputStream(str.getBytes("UTF-8"))) } - From b49f5399d3fbed93032d4e01cfd1b748ed5a59cf Mon Sep 17 00:00:00 2001 From: David Pollak Date: Tue, 19 Feb 2013 12:38:33 -0800 Subject: [PATCH 0335/1949] Simply Lift --- docs/simply_lift.md | 547 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 547 insertions(+) create mode 100644 docs/simply_lift.md diff --git a/docs/simply_lift.md b/docs/simply_lift.md new file mode 100644 index 0000000000..844a3f9488 --- /dev/null +++ b/docs/simply_lift.md @@ -0,0 +1,547 @@ +# Simply Lift + +> David Pollak +> February 19, 2013 +>
    +> Copyright © 2010-2013 by David Pollak +>
    Creative Commons License +>
    +> Simply Lift by http://simply.liftweb.net is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
    Based on a work at https://github.com/lift/framework. + + +## The Lift Web Framework + +### Introduction + +The Lift Web Framework provides web application developers tools to make writing security, interacting, scalable web applications easier than with any other web framework. After reading Part I of this book, you should understand Lift's core concepts and be able to write Lift applications. But with anything, practice is important. I have been writing Lift and Scala for 6 years, and even I learn new things about the language and the framework on a weekly basis. Please consider Lift an path and an exploration, rather than an end point. + +“Yo, David, stop yer yappin'. I'm coming from Rails|Spring|Struts|Django and I want to get started super fast with Lift.” See From MVC. + +Lift is built on top of the [Scala](http://scala-lang.org) programming language. Scala runs on the [Java Virtual Machine](http://www.oracle.com/technetwork/java/index.html). Lift applications are typically packaged as [http://en.wikipedia.org/wiki/WAR_(Sun_file_format)||WAR] files and run as a [http://www.oracle.com/technetwork/java/index-jsp-135475.html||J/EE Servlets] or Servlet Filters. This book will provide you with the core concepts you need to successfully write Lift web applications. The book assumes knowledge of Servlets and Servlet containers, the Scala Language (Chapters 1-6 of [http://apress.com/book/view/9781430219897||Beginning Scala] gives you a good grounding in the language), build tools, program editors, web development including HTML and JavaScript, etc. Further, this book will not explore persistence. Lift has additional modules for persisting to relational and non-relational data stores. Lift doesn't distinguish as to how an object is materialized into the address space... Lift can treat any object any old way you want. There are many resources (including [http://exploring.liftweb.net/||Exploring Lift]) that cover ways to persist data from a JVM. + +Lift is different from most web frameworks and it's likely that Lift's differences will present a challenge and a friction if you are familiar with the MVCMVC school of web frameworksThis includes Ruby on Rails, Struts, Java Server Faces, Django, TurboGears, etc.. But Lift is different and Lift's differences give you more power to create interactive applications. Lift's differences lead to more concise web applications. Lift's differences result in more secure and scalable applications. Lift's differences let you be more productive and make maintaining applications easier for the future you or whoever is writing your applications. Please relax and work to understand Lift's differences... and see how you can make best use of Lift's features to build your web applications. + +Lift creates abstractions that allow easier expression of business logic and then maps those abstractions to HTTP and HTML. This approach differs from traditional web frameworks which build abstractions on top of HTTP and HTML and require the developer to bridge between common business logic patterns and the underlying protocol. The difference means that you spend more time thinking about your application and less time thinking about the plumbing. + +I am a “concept learner.” I learn concepts and then apply them over and over again as situations come up. This book focuses a lot on the concepts. If you're a concept learner and like my stream on conciousness style, this book will likely suit you well. On the other hand, it may not. + +Up to date versions of this book are available in PDF form at http://simply.liftweb.net/Simply_Lift.pdf. The source code for this book is available at [https://github.com/dpp/simply_lift||https://github.com/dpp/simply_lift]. + +If you've got questions, feedback, or improvements to this document, please join the conversation on the [http://groups.google.com/group/liftweb||Lift Google Group]. + +I'm a “roll up your sleaves and get your hands dirty with code” kinda guy... so let's build a simple Chat application in Lift. This application will allow us to demonstrate some of Lift's core features as well as giving a “smack in the face” demonstration of how Lift is different. + +### The ubiquitous Chat app + +Writing a multi-user chat application in Lift is super-simple and illustrates many of Lift's core concepts. + +The Source Code can be found at [https://github.com/dpp/simply_lift/tree/master/chat]. + +#### The View + +When writing a Lift app, it's often best to start off with the user interface... build what the user will see and then add behavior to the HTML page. So, let's look at the Lift template that will make up our chat application. + + + +It's a valid HTML page, but there are some hinky looking class attributes. The first one is . The class in this case says “the actual page content is contained by the element with id='main'.” This allows you to have valid HTML pages for each of your templates, but dynamically add “chrome” around the content based on one or more chrome templates. + +Let's look at the
    . It's got a funky class as well: lift:surround?with=default;at=content. This class invokes a snippet which surrounds the
    with the default template and inserts the
    and its children at the element with id “content” in the default template. Or, it wraps the default chrome around the
    . For more on snippets, see [sec:Snippets]. + +Next, we define how we associate dynamic behavior with the list of chat elements:
    . The “comet” snippet looks for a class named Chat that extends CometActor and enables the mechanics of pushing content from the CometActor to the browser when the state of the CometActor changes. + +#### The Chat Comet component + +The [http://en.wikipedia.org/wiki/Actor_model||Actor Model] provides state in functional languages include Erlang. Lift has an Actor library and LiftActors (see [sec:LiftActor]) provides a powerful state and concurrency model. This may all seem abstract, so let's look at the Chat class. + + + +The Chat component has private state, registers with the ChatServer, handles incoming messages and can render itself. Let's look at each of those pieces. + +The private state, like any private state in prototypical object oriented code, is the state that defines the object's behavior. + +registerWith is a method that defines what component to register the Chat component with. Registration is a part of the Listener (or [http://en.wikipedia.org/wiki/Observer_pattern||Observer]) pattern. We'll look at the definition of the ChatServer in a minute. + +The lowPriority method defines how to process incoming messages. In this case, we're Pattern Matching (see [sec:Pattern-Matching]) the incoming message and if it's a Vector[String], then we perform the action of setting our local state to the Vector and re-rendering the component. The re-rendering will force the changes out to any browser that is displaying the component. + +We define how to render the component by defining the CSS to match and the replacement (See [sec:CSS-Selector-Transforms]). We match all the
  • tags of the template and for each message, create an
  • tag with the child nodes set to the message. Additionally, we clear all the elements that have the clearable in the class attribute. + +That's it for the Chat CometActor component. + +#### The ChatServer + +The ChatServer code is: + + + +The ChatServer is defined as an object rather than a class. This makes it a singleton which can be referenced by the name ChatServer anywhere in the application. Scala's singletons differ from Java's static in that the singleton is an instance of an object and that instance can be passed around like any other instance. This is why we can return the ChatServer instance from the registerWith method in that Chat component. + +The ChatServer has private state, a Vector[String] representing the list of chat messages. Note that Scala's type inferencer infers the type of msgs so you do not have to explicitly define it. + +The createUpdate method generates an update to send to listeners. This update is sent when a listener registers with the ChatServer or when the updateListeners() method is invoked. + +Finally, the lowPriority method defines the messages that this component can handle. If the ChatServer receives a String as a message, it appends the String to the Vector of messages and updates listeners. + +#### User Input + +Let's go back to the view and see how the behavior is defined for adding lines to the chat. + +
    defines an input form and the form.ajax snippet turns a form into an Ajax (see [sec:Ajax]) form that will be submitted back to the server without causing a full page load. + +Next, we define the input form element: . It's a plain old input form, but we've told Lift to modify the 's behavior by calling the ChatIn snippet. + +#### Chat In + +The ChatIn snippet (See [sec:Snippets]) is defined as: + + + +The code is very simple. The snippet is defined as a method that associates a function with form element submission, onSubmit. When the element is submitted, be that normal form submission, Ajax, or whatever, the function is applied to the value of the form. In English, when the user submits the form, the function is called with the user's input. + +The function sends the input as a message to the ChatServer and returns JavaScript that sets the value of the input box to a blank string. + +#### Running it + +Running the application is easy. Make sure you've got Java 1.6 or better installed on your machine. Change directories into the chat directory and type sbt update ~jetty-run. The Simple Build Tool will download all necessary dependencies, compile the program and run it. + +You can point a couple of browsers to http://localhost:8080 and start chatting. + +Oh, and for fun, try entering From 511a30eb7998cd678024dd159c13c67eb6406005 Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Mon, 29 Jul 2013 01:38:01 -0400 Subject: [PATCH 0549/1949] Fixed the ToHeadUsages tests related to using the new html5 parser --- .../net/liftweb/webapptest/ToHeadUsages.scala | 7 ++++--- web/webkit/src/test/webapp/basicDiv.html | 8 ++++---- web/webkit/src/test/webapp/deferred.html | 10 +++++----- .../src/test/webapp/htmlFragmentWithHead.html | 11 +++++------ .../src/test/webapp/htmlSnippetWithHead.html | 2 +- .../test/webapp/templates-hidden/default.html | 16 +++++++++------- 6 files changed, 28 insertions(+), 26 deletions(-) diff --git a/web/webkit/src/test/scala/net/liftweb/webapptest/ToHeadUsages.scala b/web/webkit/src/test/scala/net/liftweb/webapptest/ToHeadUsages.scala index 86dc595ff4..0d273dd9ba 100644 --- a/web/webkit/src/test/scala/net/liftweb/webapptest/ToHeadUsages.scala +++ b/web/webkit/src/test/scala/net/liftweb/webapptest/ToHeadUsages.scala @@ -55,7 +55,8 @@ object ToHeadUsages extends Specification { "merge from html fragment" in { jetty.browse( "/htmlFragmentWithHead", html => - html.getElementByXPath("/html/head/script[@id='fromFrag']") must not(beNull when jetty.running)) + html.getElementByXPath("/html/head/script[@id='fromFrag']") must not(beNull when jetty.running) + ) } "merge from html fragment does not include head element in body" in { @@ -135,8 +136,8 @@ object ToHeadUsages extends Specification { (idx >= 0) must_== true } ) - } - */ + }*/ + } "deferred snippets" should { diff --git a/web/webkit/src/test/webapp/basicDiv.html b/web/webkit/src/test/webapp/basicDiv.html index d0bd06eb84..29ac577050 100644 --- a/web/webkit/src/test/webapp/basicDiv.html +++ b/web/webkit/src/test/webapp/basicDiv.html @@ -1,13 +1,13 @@ - +
    bat
    -
    +
    -
    +
    - +
    diff --git a/web/webkit/src/test/webapp/deferred.html b/web/webkit/src/test/webapp/deferred.html index 51703ddf60..57c4cd617b 100644 --- a/web/webkit/src/test/webapp/deferred.html +++ b/web/webkit/src/test/webapp/deferred.html @@ -1,10 +1,10 @@ - - - +
    +
    +
    - +
    - +
    diff --git a/web/webkit/src/test/webapp/htmlFragmentWithHead.html b/web/webkit/src/test/webapp/htmlFragmentWithHead.html index ec053346db..3423b1a65e 100644 --- a/web/webkit/src/test/webapp/htmlFragmentWithHead.html +++ b/web/webkit/src/test/webapp/htmlFragmentWithHead.html @@ -1,7 +1,6 @@ - - - - +

    Welcome to your project!

    - - + + + +
    diff --git a/web/webkit/src/test/webapp/htmlSnippetWithHead.html b/web/webkit/src/test/webapp/htmlSnippetWithHead.html index 71577c1c25..4e5944771f 100644 --- a/web/webkit/src/test/webapp/htmlSnippetWithHead.html +++ b/web/webkit/src/test/webapp/htmlSnippetWithHead.html @@ -1,5 +1,5 @@

    Welcome to your project!

    -

    +

    diff --git a/web/webkit/src/test/webapp/templates-hidden/default.html b/web/webkit/src/test/webapp/templates-hidden/default.html index 532a640123..00f3c5f922 100644 --- a/web/webkit/src/test/webapp/templates-hidden/default.html +++ b/web/webkit/src/test/webapp/templates-hidden/default.html @@ -1,15 +1,17 @@ + - - - - + + + + Lift webapptest - - - + + +
    The main content will get bound here
    + From 0c24719a09c82ca3df0c93407fda035e000a7295 Mon Sep 17 00:00:00 2001 From: David Pollak Date: Mon, 9 Jan 2012 13:56:37 -0800 Subject: [PATCH 0550/1949] Closes #1126. Swallows an inner for Menu.item --- .../src/main/scala/net/liftweb/builtin/snippet/Menu.scala | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Menu.scala b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Menu.scala index 001bea2e6c..ebcb48ce85 100644 --- a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Menu.scala +++ b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Menu.scala @@ -396,15 +396,21 @@ object Menu extends DispatchSnippet { * set the "donthide" attribute on the tag to force it to show text only (same text as normal, * but not in an anchor tag)

    * + * *

    Alternatively, you can set the "linkToSelf" attribute to "true" to force a link. You * can specify your own link text with the tag's contents. Note that case is significant, so * make sure you specify "linkToSelf" and not "linktoself".

    * */ - def item(text: NodeSeq): NodeSeq = { + def item(_text: NodeSeq): NodeSeq = { val donthide = S.attr("donthide").map(Helpers.toBoolean) openOr false val linkToSelf = (S.attr("linkToSelf") or S.attr("linktoself")).map(Helpers.toBoolean) openOr false + val text = ("a" #> ((n: NodeSeq) => n match { + case e: Elem => e.child + case xs => xs + })).apply(_text) + for { name <- S.attr("name").toList } yield { From 2837094721e1cc18652708990f72c6a26315fdab Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Thu, 8 Aug 2013 00:02:47 -0400 Subject: [PATCH 0551/1949] We don't want to use eventually here, as it will keep sending an `Add(44)` message to the actor --- core/actor/src/test/scala/net/liftweb/actor/ActorSpec.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/actor/src/test/scala/net/liftweb/actor/ActorSpec.scala b/core/actor/src/test/scala/net/liftweb/actor/ActorSpec.scala index 4957e4aa49..a0ca287c84 100644 --- a/core/actor/src/test/scala/net/liftweb/actor/ActorSpec.scala +++ b/core/actor/src/test/scala/net/liftweb/actor/ActorSpec.scala @@ -39,6 +39,7 @@ class ActorSpec extends Specification { } private def commonFeatures(actor: LiftActor) = { + sequential "allow setting and getting of a value" in { val a = actor @@ -57,7 +58,7 @@ class ActorSpec extends Specification { "allow adding of a value" in { val a = actor a ! Set(33) - (a !< Add(44)).get(50) must be_==(Full(Answer(77))).eventually(900, 100.milliseconds) + (a !< Add(44)).get(500) must be_==(Full(Answer(77))) } "allow subtracting of a value" in { From b4e97430880ad8c89f8eeb0957a10c7b37b2aae8 Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Thu, 8 Aug 2013 00:06:07 -0400 Subject: [PATCH 0552/1949] updated readme to use 2.5.1 instead of 2.4 --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 200cfc1b68..93b0d1bf3d 100644 --- a/README.md +++ b/README.md @@ -59,10 +59,10 @@ Or, you can add Lift to your `pom.xml` like so: net.liftweb lift-mapper_${scala.version} - 2.4 + 2.5.1 -Where `${scala.version}` is `2.8.0`, `2.8.1`, `2.9.1` etc. +Where `${scala.version}` is `2.9.1` etc. For scala 2.10.x, which is binary compatible, you just use `2.10`, and that will work for `2.10.0` ,`2.10.1` ,`2.10.2` You can [learn more on the wiki](http://www.assembla.com/wiki/show/liftweb/Using_Maven). @@ -128,7 +128,7 @@ The Lift wiki is hosted on Assembla and can be found at [http://www.assembla.com ### ScalaDocs -The ScalaDocs for each release of Lift, in additional to the actual JARs, are available on ScalaTools. You can access the source code–based documentation for releases via the site's homepage or by navigating directly to the URL for the specific release. For instance, the Lift 2.4 release can be accessed at [http://scala-tools.org/mvnsites/liftweb-2.4/](http://scala-tools.org/mvnsites/liftweb-2.4/). +The ScalaDocs for each release of Lift, in additional to the actual JARs, are available on the Liftweb.net site. You can access the source code–based documentation for releases via the site's homepage or by navigating directly to the URL for the specific release. For instance, the Lift 2.5 release can be accessed at [http://liftweb.net/api/25/api/](http://liftweb.net/api/25/api/). ### Cookbook From 6f216ec7ae0732e4641ee55ced39db7eb08acce3 Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Sat, 27 Jul 2013 22:30:10 -0400 Subject: [PATCH 0553/1949] fixed #1291 - SHtml.ajaxSelect doesn't serialize ampersands (&) correctly --- web/webkit/src/main/scala/net/liftweb/http/SHtml.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala b/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala index a9f7858499..86165c99f4 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala @@ -992,7 +992,7 @@ trait SHtml { private def ajaxSelect_*(opts: Seq[SelectableOption[String]], deflt: Box[String], jsFunc: Box[Call], func: AFuncHolder, attrs: ElemAttr*): Elem = { - val raw = (funcName: String, value: String) => JsRaw("'" + funcName + "=' + " + value + ".options[" + value + ".selectedIndex].value") + val raw = (funcName: String, value: String) => JsRaw("'" + funcName + "=' + encodeURIComponent(" + value + ".options[" + value + ".selectedIndex].value)") val key = formFuncName val vals = opts.map(_.value) From 9677270fe6f76c26d8e034c8d5ff5edbe2731189 Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Sun, 28 Jul 2013 22:38:02 -0400 Subject: [PATCH 0554/1949] fixed #1465 - Lift could annotate type of primaryKeyField as a workaround to Scala regression SI-7612 --- .../mapper/src/main/scala/net/liftweb/mapper/Mapper.scala | 2 +- .../main/scala/net/liftweb/mapper/ProtoExtendedSession.scala | 2 +- .../mapper/src/main/scala/net/liftweb/mapper/ProtoTag.scala | 2 +- .../mapper/src/main/scala/net/liftweb/mapper/ProtoUser.scala | 2 +- .../src/test/scala/net/liftweb/mapper/MapperSpecsModel.scala | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/persistence/mapper/src/main/scala/net/liftweb/mapper/Mapper.scala b/persistence/mapper/src/main/scala/net/liftweb/mapper/Mapper.scala index 31cf6bafa8..d59bc5dde6 100644 --- a/persistence/mapper/src/main/scala/net/liftweb/mapper/Mapper.scala +++ b/persistence/mapper/src/main/scala/net/liftweb/mapper/Mapper.scala @@ -335,7 +335,7 @@ trait BaseLongKeyedMapper extends BaseKeyedMapper { trait IdPK /* extends BaseLongKeyedMapper */ { self: BaseLongKeyedMapper => - def primaryKeyField = id + def primaryKeyField: MappedLongIndex[MapperType] = id object id extends MappedLongIndex[MapperType](this.asInstanceOf[MapperType]) } diff --git a/persistence/mapper/src/main/scala/net/liftweb/mapper/ProtoExtendedSession.scala b/persistence/mapper/src/main/scala/net/liftweb/mapper/ProtoExtendedSession.scala index 76ace7ef96..73468299e7 100644 --- a/persistence/mapper/src/main/scala/net/liftweb/mapper/ProtoExtendedSession.scala +++ b/persistence/mapper/src/main/scala/net/liftweb/mapper/ProtoExtendedSession.scala @@ -29,7 +29,7 @@ trait ProtoExtendedSession[T <: ProtoExtendedSession[T]] extends KeyedMapper[Long, T] { self: T => - override def primaryKeyField = id + override def primaryKeyField: MappedLongIndex[T] = id // the primary key for the database object id extends MappedLongIndex(this) diff --git a/persistence/mapper/src/main/scala/net/liftweb/mapper/ProtoTag.scala b/persistence/mapper/src/main/scala/net/liftweb/mapper/ProtoTag.scala index 847dbe4ab9..81775ad079 100644 --- a/persistence/mapper/src/main/scala/net/liftweb/mapper/ProtoTag.scala +++ b/persistence/mapper/src/main/scala/net/liftweb/mapper/ProtoTag.scala @@ -72,7 +72,7 @@ abstract class ProtoTag[MyType <: ProtoTag[MyType]] extends KeyedMapper[Long, My // the primary key for the database object id extends MappedLongIndex(this) - def primaryKeyField = id + def primaryKeyField: MappedLongIndex[MyType] = id object name extends MappedPoliteString(this, 256) { override def setFilter = getSingleton.capify :: super.setFilter diff --git a/persistence/mapper/src/main/scala/net/liftweb/mapper/ProtoUser.scala b/persistence/mapper/src/main/scala/net/liftweb/mapper/ProtoUser.scala index cc11cd9094..9dd169dfce 100644 --- a/persistence/mapper/src/main/scala/net/liftweb/mapper/ProtoUser.scala +++ b/persistence/mapper/src/main/scala/net/liftweb/mapper/ProtoUser.scala @@ -38,7 +38,7 @@ import net.liftweb.proto.{ProtoUser => GenProtoUser} trait ProtoUser[T <: ProtoUser[T]] extends KeyedMapper[Long, T] with UserIdAsString { self: T => - override def primaryKeyField = id + override def primaryKeyField: MappedLongIndex[T] = id /** * The primary key field for the User. You can override the behavior diff --git a/persistence/mapper/src/test/scala/net/liftweb/mapper/MapperSpecsModel.scala b/persistence/mapper/src/test/scala/net/liftweb/mapper/MapperSpecsModel.scala index 8217fa1215..3abddcd23f 100644 --- a/persistence/mapper/src/test/scala/net/liftweb/mapper/MapperSpecsModel.scala +++ b/persistence/mapper/src/test/scala/net/liftweb/mapper/MapperSpecsModel.scala @@ -129,7 +129,7 @@ class SampleModel extends KeyedMapper[Long, SampleModel] { def getSingleton = SampleModel // what's the "meta" server - def primaryKeyField = id + def primaryKeyField: MappedLongIndex[SampleModel] = id object id extends MappedLongIndex(this) From b9e2c9dbd57bcdd3ff494f54becebf7029746558 Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Sun, 28 Jul 2013 00:44:25 -0400 Subject: [PATCH 0555/1949] fixed #1014 - A new CreatedResponse that takes JSON --- .../scala/net/liftweb/http/LiftResponse.scala | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftResponse.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftResponse.scala index faa014b8d9..982a7c6248 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftResponse.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftResponse.scala @@ -18,12 +18,12 @@ package net.liftweb package http import net.liftweb.common._ -import scala.xml.{Node, Group, NodeSeq} +import scala.xml.Node import net.liftweb.util._ import net.liftweb.http.provider._ import js._ import net.liftweb.util.Helpers._ -import net.liftweb.json.{JsonAST, Printer} +import net.liftweb.json.JsonAST import java.io.{OutputStream, OutputStreamWriter, Writer, ByteArrayOutputStream} /** @@ -56,6 +56,29 @@ case class CreatedResponse(xml: Node, mime: String, addlHeaders: List[(String, S def out = xml } +/** + * 201 Created Response + * + * The Json Resource was created. We then return the resource, post-processing, to + * the client. Usually used with HTTP PUT. + */ +object CreatedResponse { + + lazy val jsonPrinter: scala.text.Document => String = + LiftRules.jsonOutputConverter.vend + + def apply(json: JsonAST.JValue, addlHeaders: List[(String, String)]): LiftResponse = { + val headers: List[(String, String)] = S.getResponseHeaders( Nil ) ++ addlHeaders + + new JsonResponse(new JsExp { + lazy val toJsCmd = jsonPrinter(JsonAST.render(json)) + }, headers, Nil, 201) + } + +} + + + /** * 202 response but without body. */ From 47f40fffe9d2f7e53fd689526a2ce3ed57f3c7a6 Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Mon, 29 Jul 2013 00:17:56 -0400 Subject: [PATCH 0556/1949] Added support for html5 templates in the test framework --- .../net/liftweb/http/testing/TestFramework.scala | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/web/testkit/src/main/scala/net/liftweb/http/testing/TestFramework.scala b/web/testkit/src/main/scala/net/liftweb/http/testing/TestFramework.scala index f6fdc704cc..fbf38b125a 100644 --- a/web/testkit/src/main/scala/net/liftweb/http/testing/TestFramework.scala +++ b/web/testkit/src/main/scala/net/liftweb/http/testing/TestFramework.scala @@ -844,12 +844,24 @@ abstract class BaseResponse(override val baseUrl: String, for { b <- body nodeSeq <- PCDataXmlParser(new java.io.ByteArrayInputStream(b)) - xml <- (nodeSeq.toList match { + xml <- nodeSeq.toList match { case (x: Elem) :: _ => Full(x) case _ => Empty - }) + } + } yield xml + + lazy val html5AsXml: Box[Elem] = + for { + b <- body + nodeSeq <- Html5.parse(new java.io.ByteArrayInputStream(b)) + xml <- nodeSeq.toList match { + case (x: Elem) :: _ => Full(x) + case _ => Empty + } } yield xml + + /** * The content type header of the response */ From 5da40b6ca9c40c3fe884dadb6d2ed70388e458d4 Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Mon, 29 Jul 2013 00:19:29 -0400 Subject: [PATCH 0557/1949] fixed #1457 - Make LiftRules use the html5 parser by default --- web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index e50c4514e7..e951b9c992 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -1041,7 +1041,7 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { * LiftRules.htmlProperties.default.set((r: Req) => new Html5Properties(r.userAgent)) */ val htmlProperties: FactoryMaker[Req => HtmlProperties] = - new FactoryMaker(() => (r: Req) => (new OldHtmlProperties(r.userAgent): HtmlProperties)) {} + new FactoryMaker(() => (r: Req) => new Html5Properties(r.userAgent): HtmlProperties) {} /** * How long should we wait for all the lazy snippets to render From 8902124783aaedc8c8e0c6173de4f1f87d5e9d05 Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Mon, 29 Jul 2013 00:19:55 -0400 Subject: [PATCH 0558/1949] Fixed failing tests related to now using html5 as the default format --- .../src/test/scala/net/liftweb/webapptest/OneShot.scala | 6 +++--- web/webkit/src/test/webapp/oneshot.html | 6 ++---- web/webkit/src/test/webapp/templates-hidden/default.html | 2 +- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/web/webkit/src/test/scala/net/liftweb/webapptest/OneShot.scala b/web/webkit/src/test/scala/net/liftweb/webapptest/OneShot.scala index 0b36e25f68..c96692a52a 100644 --- a/web/webkit/src/test/scala/net/liftweb/webapptest/OneShot.scala +++ b/web/webkit/src/test/scala/net/liftweb/webapptest/OneShot.scala @@ -26,8 +26,8 @@ import Helpers._ import java.net.{URL, InetAddress} -import common.Full import snippet.Counter +import net.liftweb.common.Full object OneShot extends Specification with RequestKit { @@ -139,7 +139,7 @@ object OneShot extends Specification with RequestKit { for { resp <- get("/oneshot") - xml <- resp.xml + xml <- resp.html5AsXml span <- (xml \\ "span").filter(x => (x \ "@id").text == "one") in <- (span \\ "input") name <- in \ "@name" @@ -156,7 +156,7 @@ object OneShot extends Specification with RequestKit { for { resp <- get("/oneshot") - xml <- resp.xml + xml <- resp.html5AsXml span <- (xml \\ "span").filter(x => (x \ "@id").text == "two") in <- (span \\ "input") name <- in \ "@name" diff --git a/web/webkit/src/test/webapp/oneshot.html b/web/webkit/src/test/webapp/oneshot.html index c955adfd9a..51729125d1 100644 --- a/web/webkit/src/test/webapp/oneshot.html +++ b/web/webkit/src/test/webapp/oneshot.html @@ -1,4 +1,4 @@ - +
    @@ -6,6 +6,4 @@ - - - +
    diff --git a/web/webkit/src/test/webapp/templates-hidden/default.html b/web/webkit/src/test/webapp/templates-hidden/default.html index 383afce7a0..532a640123 100644 --- a/web/webkit/src/test/webapp/templates-hidden/default.html +++ b/web/webkit/src/test/webapp/templates-hidden/default.html @@ -3,7 +3,7 @@ - + Lift webapptest From 9285b3553fcdcea64bf6015ab49a59c9d90526bb Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Mon, 29 Jul 2013 01:38:01 -0400 Subject: [PATCH 0559/1949] Fixed the ToHeadUsages tests related to using the new html5 parser --- .../net/liftweb/webapptest/ToHeadUsages.scala | 7 ++++--- web/webkit/src/test/webapp/basicDiv.html | 8 ++++---- web/webkit/src/test/webapp/deferred.html | 10 +++++----- .../src/test/webapp/htmlFragmentWithHead.html | 11 +++++------ .../src/test/webapp/htmlSnippetWithHead.html | 2 +- .../test/webapp/templates-hidden/default.html | 16 +++++++++------- 6 files changed, 28 insertions(+), 26 deletions(-) diff --git a/web/webkit/src/test/scala/net/liftweb/webapptest/ToHeadUsages.scala b/web/webkit/src/test/scala/net/liftweb/webapptest/ToHeadUsages.scala index 10fa9b0fe3..ec7a0370ba 100644 --- a/web/webkit/src/test/scala/net/liftweb/webapptest/ToHeadUsages.scala +++ b/web/webkit/src/test/scala/net/liftweb/webapptest/ToHeadUsages.scala @@ -55,7 +55,8 @@ object ToHeadUsages extends Specification { "merge from html fragment" in { jetty.browse( "/htmlFragmentWithHead", html => - html.getElementByXPath("/html/head/script[@id='fromFrag']") must not(beNull when jetty.running)) + html.getElementByXPath("/html/head/script[@id='fromFrag']") must not(beNull when jetty.running) + ) } "merge from html fragment does not include head element in body" in { @@ -136,8 +137,8 @@ object ToHeadUsages extends Specification { (idx >= 0) must_== true } ) - } - */ + }*/ + } "deferred snippets" should { diff --git a/web/webkit/src/test/webapp/basicDiv.html b/web/webkit/src/test/webapp/basicDiv.html index d0bd06eb84..29ac577050 100644 --- a/web/webkit/src/test/webapp/basicDiv.html +++ b/web/webkit/src/test/webapp/basicDiv.html @@ -1,13 +1,13 @@ - +
    bat
    -
    +
    -
    +
    - +
    diff --git a/web/webkit/src/test/webapp/deferred.html b/web/webkit/src/test/webapp/deferred.html index 51703ddf60..57c4cd617b 100644 --- a/web/webkit/src/test/webapp/deferred.html +++ b/web/webkit/src/test/webapp/deferred.html @@ -1,10 +1,10 @@ - - - +
    +
    +
    - +
    - +
    diff --git a/web/webkit/src/test/webapp/htmlFragmentWithHead.html b/web/webkit/src/test/webapp/htmlFragmentWithHead.html index ec053346db..3423b1a65e 100644 --- a/web/webkit/src/test/webapp/htmlFragmentWithHead.html +++ b/web/webkit/src/test/webapp/htmlFragmentWithHead.html @@ -1,7 +1,6 @@ - - - - +

    Welcome to your project!

    - - + + + +
    diff --git a/web/webkit/src/test/webapp/htmlSnippetWithHead.html b/web/webkit/src/test/webapp/htmlSnippetWithHead.html index 71577c1c25..4e5944771f 100644 --- a/web/webkit/src/test/webapp/htmlSnippetWithHead.html +++ b/web/webkit/src/test/webapp/htmlSnippetWithHead.html @@ -1,5 +1,5 @@

    Welcome to your project!

    -

    +

    diff --git a/web/webkit/src/test/webapp/templates-hidden/default.html b/web/webkit/src/test/webapp/templates-hidden/default.html index 532a640123..00f3c5f922 100644 --- a/web/webkit/src/test/webapp/templates-hidden/default.html +++ b/web/webkit/src/test/webapp/templates-hidden/default.html @@ -1,15 +1,17 @@ + - - - - + + + + Lift webapptest - - - + + +
    The main content will get bound here
    + From 58247b87ff3066e6fc5958f8c8b3f83d3451cb22 Mon Sep 17 00:00:00 2001 From: David Pollak Date: Mon, 9 Jan 2012 13:56:37 -0800 Subject: [PATCH 0560/1949] Closes #1126. Swallows an inner
    for Menu.item --- .../src/main/scala/net/liftweb/builtin/snippet/Menu.scala | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Menu.scala b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Menu.scala index 001bea2e6c..ebcb48ce85 100644 --- a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Menu.scala +++ b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Menu.scala @@ -396,15 +396,21 @@ object Menu extends DispatchSnippet { * set the "donthide" attribute on the tag to force it to show text only (same text as normal, * but not in an anchor tag)

    * + * *

    Alternatively, you can set the "linkToSelf" attribute to "true" to force a link. You * can specify your own link text with the tag's contents. Note that case is significant, so * make sure you specify "linkToSelf" and not "linktoself".

    * */ - def item(text: NodeSeq): NodeSeq = { + def item(_text: NodeSeq): NodeSeq = { val donthide = S.attr("donthide").map(Helpers.toBoolean) openOr false val linkToSelf = (S.attr("linkToSelf") or S.attr("linktoself")).map(Helpers.toBoolean) openOr false + val text = ("a" #> ((n: NodeSeq) => n match { + case e: Elem => e.child + case xs => xs + })).apply(_text) + for { name <- S.attr("name").toList } yield { From d8d6201247ca44eeeafdc9ed515f109756a58bc9 Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Thu, 8 Aug 2013 00:02:47 -0400 Subject: [PATCH 0561/1949] We don't want to use eventually here, as it will keep sending an `Add(44)` message to the actor --- core/actor/src/test/scala/net/liftweb/actor/ActorSpec.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/actor/src/test/scala/net/liftweb/actor/ActorSpec.scala b/core/actor/src/test/scala/net/liftweb/actor/ActorSpec.scala index 4957e4aa49..a0ca287c84 100644 --- a/core/actor/src/test/scala/net/liftweb/actor/ActorSpec.scala +++ b/core/actor/src/test/scala/net/liftweb/actor/ActorSpec.scala @@ -39,6 +39,7 @@ class ActorSpec extends Specification { } private def commonFeatures(actor: LiftActor) = { + sequential "allow setting and getting of a value" in { val a = actor @@ -57,7 +58,7 @@ class ActorSpec extends Specification { "allow adding of a value" in { val a = actor a ! Set(33) - (a !< Add(44)).get(50) must be_==(Full(Answer(77))).eventually(900, 100.milliseconds) + (a !< Add(44)).get(500) must be_==(Full(Answer(77))) } "allow subtracting of a value" in { From edd5fca1f3a3854bbc27a1f510d53c2414966cfd Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 12 Aug 2013 14:03:18 -0400 Subject: [PATCH 0562/1949] Revert "Use ThreadLocalRandom in StringHelpers.randomString on Java 7." ThreadLocalRandom turns out not to be a secure RNG, so using it compromises our XSS and other protections for field names. This reverts commit f2de993c8c568c994d4b2a04daf22b21f459b7bf. --- .../net/liftweb/util/StringHelpers.scala | 21 ++----------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/StringHelpers.scala b/core/util/src/main/scala/net/liftweb/util/StringHelpers.scala index b223fee77d..1b3715976a 100644 --- a/core/util/src/main/scala/net/liftweb/util/StringHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/StringHelpers.scala @@ -19,7 +19,6 @@ package util import java.security.SecureRandom import java.util.regex._ -import java.util.Random import java.lang.Character._ import java.lang.{StringBuilder => GoodSB} import scala.xml.NodeSeq @@ -33,23 +32,7 @@ object StringHelpers extends StringHelpers trait StringHelpers { /** random numbers generator */ - private lazy val _slowRandom = new SecureRandom - private lazy val _currentTlrMethod = { - try { - val tlr = Class.forName("java.util.concurrent.ThreadLocalRandom") - Full(tlr.getMethod("current")) - } catch { - case e => - Failure("ThreadLocalRandom is not available.", Full(e), Empty) - } - } - private def withRng[T](block: (Random)=>T) = { - _currentTlrMethod.map { meth => - block(meth.invoke(null).asInstanceOf[Random]) - } openOr { - _slowRandom.synchronized(block(_slowRandom)) - } - } + private val _random = new SecureRandom /** * If str is surrounded by quotes it return the content between the quotes @@ -193,7 +176,7 @@ trait StringHelpers { if (pos >= size) sb else { val randNum = if ((pos % 6) == 0) { - withRng(_.nextInt) + _random.synchronized(_random.nextInt) } else { lastRand } From 1144ba031fd1a2c56cb9c254b4de0c8987d1acc3 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 12 Aug 2013 14:03:18 -0400 Subject: [PATCH 0563/1949] Revert "Use ThreadLocalRandom in StringHelpers.randomString on Java 7." ThreadLocalRandom turns out not to be a secure RNG, so using it compromises our XSS and other protections for field names. This reverts commit f2de993c8c568c994d4b2a04daf22b21f459b7bf. --- .../net/liftweb/util/StringHelpers.scala | 21 ++----------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/StringHelpers.scala b/core/util/src/main/scala/net/liftweb/util/StringHelpers.scala index d83a152fb0..bf6ef1faac 100644 --- a/core/util/src/main/scala/net/liftweb/util/StringHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/StringHelpers.scala @@ -19,7 +19,6 @@ package util import java.security.SecureRandom import java.util.regex._ -import java.util.Random import java.lang.Character._ import java.lang.{StringBuilder => GoodSB} import scala.xml.NodeSeq @@ -33,23 +32,7 @@ object StringHelpers extends StringHelpers trait StringHelpers { /** random numbers generator */ - private lazy val _slowRandom = new SecureRandom - private lazy val _currentTlrMethod = { - try { - val tlr = Class.forName("java.util.concurrent.ThreadLocalRandom") - Full(tlr.getMethod("current")) - } catch { - case e => - Failure("ThreadLocalRandom is not available.", Full(e), Empty) - } - } - private def withRng[T](block: (Random)=>T) = { - _currentTlrMethod.map { meth => - block(meth.invoke(null).asInstanceOf[Random]) - } openOr { - _slowRandom.synchronized(block(_slowRandom)) - } - } + private val _random = new SecureRandom /** * If str is surrounded by quotes it return the content between the quotes @@ -193,7 +176,7 @@ trait StringHelpers { if (pos >= size) sb else { val randNum = if ((pos % 6) == 0) { - withRng(_.nextInt) + _random.synchronized(_random.nextInt) } else { lastRand } From 4bff9c70d14ab08c2406fb824850bb8e412d0179 Mon Sep 17 00:00:00 2001 From: Viktor Hedefalk Date: Thu, 8 Aug 2013 17:48:48 +0200 Subject: [PATCH 0564/1949] Fixed path in Menu --- .../net/liftweb/sitemap/FlexMenuBuilder.scala | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala b/web/webkit/src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala index 106341a03a..aa96544492 100644 --- a/web/webkit/src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala +++ b/web/webkit/src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala @@ -41,12 +41,22 @@ trait FlexMenuBuilder { */ protected def expandAny = false - // This is used to build a MenuItem for a single Loc + /** + * This is used to build a MenuItem for a single Loc + */ protected def buildItemMenu[A](loc: Loc[A], currLoc: Box[Loc[_]], expandAll: Boolean): List[MenuItem] = { + def isInPath(loc: Loc[_]): Boolean = { + if(currLoc == Full(loc)) true else { + val kids = loc.menu.kids + if(kids.isEmpty) false else kids.exists(k => isInPath(k.loc)) + } + } + val kids: List[MenuItem] = if (expandAll) loc.buildKidMenuItems(loc.menu.kids) else Nil - loc.asInstanceOf[StructBuildItem].buildItem(kids, currLoc == Full(loc), currLoc == Full(loc)).toList + loc.asInstanceOf[StructBuildItem].buildItem(kids, currLoc == Full(loc), isInPath(loc)).toList } + /** * Compute the MenuItems to be rendered by looking at the 'item' and 'group' attributes */ From f1b5bcf484d88652b643e9fc349a7f1ed5d3d8ea Mon Sep 17 00:00:00 2001 From: Viktor Hedefalk Date: Thu, 8 Aug 2013 20:18:59 +0200 Subject: [PATCH 0565/1949] removed unnescessary allocations --- .../net/liftweb/sitemap/FlexMenuBuilder.scala | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala b/web/webkit/src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala index aa96544492..d14cd80596 100644 --- a/web/webkit/src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala +++ b/web/webkit/src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala @@ -45,15 +45,18 @@ trait FlexMenuBuilder { * This is used to build a MenuItem for a single Loc */ protected def buildItemMenu[A](loc: Loc[A], currLoc: Box[Loc[_]], expandAll: Boolean): List[MenuItem] = { - def isInPath(loc: Loc[_]): Boolean = { - if(currLoc == Full(loc)) true else { - val kids = loc.menu.kids - if(kids.isEmpty) false else kids.exists(k => isInPath(k.loc)) + val isInPath = currLoc.map { cur => + def isInPath(loc: Loc[_]): Boolean = { + if(cur == loc) true else { + val kids = loc.menu.kids + if(kids.isEmpty) false else kids.exists(k => isInPath(k.loc)) + } } - } + isInPath(loc) + } openOr false val kids: List[MenuItem] = if (expandAll) loc.buildKidMenuItems(loc.menu.kids) else Nil - loc.asInstanceOf[StructBuildItem].buildItem(kids, currLoc == Full(loc), isInPath(loc)).toList + loc.asInstanceOf[StructBuildItem].buildItem(kids, currLoc == Full(loc), isInPath).toList } From 908c37b107337a5c1708780d9a7021be8222ea3b Mon Sep 17 00:00:00 2001 From: Viktor Hedefalk Date: Thu, 8 Aug 2013 20:28:34 +0200 Subject: [PATCH 0566/1949] cleanup --- .../main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala b/web/webkit/src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala index d14cd80596..3ba21e58f8 100644 --- a/web/webkit/src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala +++ b/web/webkit/src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala @@ -46,12 +46,7 @@ trait FlexMenuBuilder { */ protected def buildItemMenu[A](loc: Loc[A], currLoc: Box[Loc[_]], expandAll: Boolean): List[MenuItem] = { val isInPath = currLoc.map { cur => - def isInPath(loc: Loc[_]): Boolean = { - if(cur == loc) true else { - val kids = loc.menu.kids - if(kids.isEmpty) false else kids.exists(k => isInPath(k.loc)) - } - } + def isInPath(loc: Loc[_]): Boolean = (cur == loc) || loc.menu.kids.exists(k => isInPath(k.loc)) isInPath(loc) } openOr false From 9167ff0fafd8543239b1d0ffd4e1435ccc1aa905 Mon Sep 17 00:00:00 2001 From: Viktor Hedefalk Date: Thu, 8 Aug 2013 21:02:28 +0200 Subject: [PATCH 0567/1949] removed unnescessary structural typing hack --- .../src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala b/web/webkit/src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala index 3ba21e58f8..7784697bbb 100644 --- a/web/webkit/src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala +++ b/web/webkit/src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala @@ -23,9 +23,6 @@ import net.liftweb.util.Helpers trait FlexMenuBuilder { - // a hack to use structural typing to get around the private[http] on Loc.buildItem - type StructBuildItem = {def buildItem(kids: List[MenuItem], current: Boolean, path: Boolean): Box[MenuItem]} - /** * Override if you want a link to the current page */ @@ -51,7 +48,7 @@ trait FlexMenuBuilder { } openOr false val kids: List[MenuItem] = if (expandAll) loc.buildKidMenuItems(loc.menu.kids) else Nil - loc.asInstanceOf[StructBuildItem].buildItem(kids, currLoc == Full(loc), isInPath).toList + loc.buildItem(kids, currLoc == Full(loc), isInPath).toList } From dd7efd3a6222250fd0a047e4fe7d2038ff4e0aca Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Wed, 7 Aug 2013 23:18:13 -0400 Subject: [PATCH 0568/1949] closes #1481 - [Breaking change] - Remove ActorWatcher from Lift 2.6 --- project/Build.scala | 1 - project/Dependencies.scala | 1 - .../scala/net/liftweb/http/CometActor.scala | 49 +------------------ 3 files changed, 1 insertion(+), 50 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index 70b80b29a4..4bf981c5f1 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -109,7 +109,6 @@ object BuildDef extends Build { libraryDependencies <++= scalaVersion { sv => Seq(commons_fileupload, servlet_api, specs2(sv).copy(configurations = Some("provided")), jetty6, jwebunit) }, - libraryDependencies <++= scalaVersion { case "2.10.0" => scalaactors::Nil case _ => Nil }, initialize in Test <<= (sourceDirectory in Test) { src => System.setProperty("net.liftweb.webapptest.src.test.webapp", (src / "webapp").absString) }) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 04d54de085..7f33cb69e8 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -51,7 +51,6 @@ object Dependencies { lazy val scalaz7_core: ModuleMap = sv => scalazGroup(sv) % "scalaz-core" % scalaz7Version(sv) cross CVMapping29 lazy val slf4j_api = "org.slf4j" % "slf4j-api" % slf4jVersion lazy val squeryl = "org.squeryl" % "squeryl" % "0.9.5-6" cross CVMapping29 - @deprecated lazy val scalaactors= "org.scala-lang" % "scala-actors" % "2.10.0" // Aliases lazy val mongo_driver = mongo_java_driver diff --git a/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala b/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala index e5c80a85bf..2a14f5794c 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala @@ -19,63 +19,16 @@ package http import net.liftweb.common._ import net.liftweb.actor._ -import scala.collection.mutable.{ListBuffer} import net.liftweb.util.Helpers._ import net.liftweb.util._ import net.liftweb.json._ import scala.xml.{NodeSeq, Text, Elem, Node, Group, Null, PrefixedAttribute, UnprefixedAttribute} -import scala.collection.immutable.TreeMap -import scala.collection.mutable.{HashSet, ListBuffer} +import scala.collection.mutable.ListBuffer import net.liftweb.http.js._ import JsCmds._ import JE._ -import java.util.concurrent.atomic.AtomicLong import java.util.Locale -/** - * An actor that monitors other actors that are linked with it. If a watched - * actor terminates, this actor captures the Exit message, executes failureFuncs - * and resurrects the actor. - */ -object ActorWatcher extends scala.actors.Actor with Loggable { - - import scala.actors.Actor._ - - def act = loop { - react { - case scala.actors.Exit(actor: scala.actors.Actor, why: Throwable) => - failureFuncs.foreach(f => tryo(f(actor, why))) - - case _ => - } - } - - private def startAgain(a: scala.actors.Actor, ignore: Throwable) { - a.start - a ! RelinkToActorWatcher - } - - private def logActorFailure(actor: scala.actors.Actor, why: Throwable) { - logger.warn("The ActorWatcher restarted " + actor + " because " + why, why) - } - - /** - * If there's something to do in addition to starting the actor up, pre-pend the - * actor to this List - */ - @volatile var failureFuncs: List[(scala.actors.Actor, Throwable) => Unit] = logActorFailure _ :: - startAgain _ :: Nil - - this.trapExit = true - this.start -} - -/** - * This is used as an indicator message for linked actors. - * - * @see ActorWatcher - */ -case object RelinkToActorWatcher trait DeltaTrait { def toJs: JsCmd From d7d1cd0dca163a595ce18ee2ecac051ef559b33e Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Thu, 8 Aug 2013 21:22:31 -0400 Subject: [PATCH 0569/1949] Fixed #1473 - SqlServerBaseDriver should set brokenLimit_? to true MicrosoftSQL at least 2008 and greater don't support the LIMIT clause, so now the default driver we offer will work out of the box with newer sql server versions --- persistence/db/src/main/scala/net/liftweb/db/Driver.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/persistence/db/src/main/scala/net/liftweb/db/Driver.scala b/persistence/db/src/main/scala/net/liftweb/db/Driver.scala index 1bfb248eda..8fd76d2278 100644 --- a/persistence/db/src/main/scala/net/liftweb/db/Driver.scala +++ b/persistence/db/src/main/scala/net/liftweb/db/Driver.scala @@ -364,6 +364,8 @@ abstract class SqlServerBaseDriver extends DriverType("Microsoft SQL Server") { // Microsoft doesn't use "COLUMN" syntax when adding a column to a table override def alterAddColumn = "ADD" + override def brokenLimit_? = true + } /** From feb159287d76571dc5a6285f6cf12ef550d9ebd3 Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Thu, 8 Aug 2013 22:34:40 -0400 Subject: [PATCH 0570/1949] Fixed #1157 - BasicTypesHelpers.toBoolean("on") returns false --- .../scala/net/liftweb/util/BasicTypesHelpers.scala | 14 +++----------- .../net/liftweb/util/BasicTypesHelpersSpec.scala | 5 +++-- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/BasicTypesHelpers.scala b/core/util/src/main/scala/net/liftweb/util/BasicTypesHelpers.scala index 0be6a51069..f775740c96 100644 --- a/core/util/src/main/scala/net/liftweb/util/BasicTypesHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/BasicTypesHelpers.scala @@ -200,15 +200,7 @@ trait BasicTypesHelpers { self: StringHelpers with ControlHelpers => case i: Int => i != 0 case lo: Long => lo != 0 case n : Number => n.intValue != 0 - case s : String => { - val sl = s.toLowerCase - if (sl.length == 0) false - else { - if (sl.charAt(0) == 't') true - else if (sl == "yes") true - else toInt(s) != 0 - } - } + case s : String => asBoolean(s) openOr false case None => false case Empty | Failure(_, _, _) => false case Full(n) => toBoolean(n) @@ -231,8 +223,8 @@ object AsBoolean { def unapply(in: String): Option[Boolean] = if (null eq in) None else in.toLowerCase match { - case "t" | "true" | "yes" | "1" => Full(true) - case "f" | "false" | "no" | "0" => Full(false) + case "t" | "true" | "yes" | "1" | "on" => Full(true) + case "f" | "false" | "no" | "0" | "off" => Full(false) case _ => None } } diff --git a/core/util/src/test/scala/net/liftweb/util/BasicTypesHelpersSpec.scala b/core/util/src/test/scala/net/liftweb/util/BasicTypesHelpersSpec.scala index ed3886583e..2b8aed6bd5 100644 --- a/core/util/src/test/scala/net/liftweb/util/BasicTypesHelpersSpec.scala +++ b/core/util/src/test/scala/net/liftweb/util/BasicTypesHelpersSpec.scala @@ -58,10 +58,11 @@ object BasicTypesHelpersSpec extends Specification with DataTables { "" !!false | "string" !!false | "t" !!true | - "total" !!true | + "total" !!false | "T" !!true | - "This" !!true | + "This" !!false | "0" !!false | + "on" !!true | None !!false | Some("t") !!true | Empty !!false | From 55453e474a2fe5e713cf452bf06db6422b8586c4 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 12 Aug 2013 13:54:02 -0400 Subject: [PATCH 0571/1949] Update comments in ListHelpers to refer to Box, not can. --- .../scala/net/liftweb/util/ListHelpers.scala | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/ListHelpers.scala b/core/util/src/main/scala/net/liftweb/util/ListHelpers.scala index 012e1b2359..80ec6b7ff6 100644 --- a/core/util/src/main/scala/net/liftweb/util/ListHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/ListHelpers.scala @@ -107,9 +107,9 @@ trait ListHelpers { } /** - * Returns a Full can with the first element x of the list in + * Returns a Full Box with the first element x of the list in * for which f(x) evaluates to true. If f(x) evaluates to false - * for every x, then an Empty can is returned. + * for every x, then an Empty Box is returned. * * @param in a list of elements to which f can be applied * @param f a function that can be applied to elements of in @@ -121,15 +121,15 @@ trait ListHelpers { /** * Returns the first application of f to an element of in that - * results in a Full can. If f applied to an element of in results - * in an Empty can, then f will be applied to the rest of the - * elements of in until a Full can results. If the list runs out - * then an Empty can is returned. + * results in a Full Box. If f applied to an element of in results + * in an Empty Box, then f will be applied to the rest of the + * elements of in until a Full Box results. If the list runs out + * then an Empty Box is returned. * * @param in a list of elements to which f can be applied * @param f a function that can be applied to elements of in * - * @return a Box containing the first Full can or Empty if f never returns a Full can + * @return a Box containing the first Full Box or Empty if f never returns a Full Box */ def first[B, C](in: Seq[B])(_f: B => Box[C]): Box[C] = { val f: B => Iterable[C] = _f andThen Box.box2Iterable[C] @@ -147,7 +147,7 @@ trait ListHelpers { * * @param key the string to find * - * @return a Full can containing the found value or Empty + * @return a Full Box containing the found value or Empty */ def ciGet(swhat: String): Box[String] = { val what = swhat.toLowerCase From 49dcb89ac2d92982c3b66f2a9927160eb7a9b78b Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Tue, 13 Aug 2013 23:27:21 -0400 Subject: [PATCH 0572/1949] Upgraded to sbt 0.12.4 --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index bb1fd56022..a2a2e1da53 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1,2 +1,2 @@ # Deprecate using build.properties, use -Dsbt.version=... in launcher arg instead -sbt.version=0.12.1 +sbt.version=0.12.4 From bdb72a06f64b81ba6810d36bf794a75a90f39025 Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Thu, 8 Aug 2013 22:21:38 -0400 Subject: [PATCH 0573/1949] Fixed #1485 - Replace console.log for lift_defaultLogError - Lift 3.0 --- .../scala/net/liftweb/http/js/ScriptRenderer.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala b/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala index 644ce81505..dbe3515ec9 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala @@ -91,15 +91,15 @@ object ScriptRenderer { '_failed': false, processMsg: function(evt) {if (this._done || this._failed) return; this._events.push(evt); - for (var v in this._eventFuncs) {try {this._eventFuncs[v](evt);} catch (e) {console.log(e);}}; + for (var v in this._eventFuncs) {try {this._eventFuncs[v](evt);} catch (e) {""" + (LiftRules.jsLogFunc.map(_(JsVar("e")).toJsCmd) openOr "") + """}}; if (evt.done) {this.doneMsg();} else if (evt.success) {this.successMsg(evt.success);} else if (evt.failure) {this.failMsg(evt.failure);}}, successMsg: function(value) {if (this._done || this._failed) return; this._values.push(value); for (var f in this._valueFuncs) {this._valueFuncs[f](value);}}, failMsg: function(msg) {if (this._done || this._failed) return; liftAjax._removeIt(this.guid); this._failed = true; this._failMsg = msg; for (var f in this._failureFuncs) {this._failureFuncs[f](msg);}}, doneMsg: function() {if (this._done || this._failed) return; liftAjax._removeIt(this.guid); this._done = true; for (var f in this._doneFuncs) {this._doneFuncs[f]();}}, - then: function(f) {this._valueFuncs.push(f); for (var v in this._values) {try {f(this._values[v]);} catch (e) {console.log(e);}} return this;}, - fail: function(f) {this._failureFuncs.push(f); if (this._failed) {try {f(this._failMsg);} catch (e) {console.log(e);}}; return this;}, - done: function(f) {this._doneFuncs.push(f); if (this._done) {try {f();} catch (e) {console.log(e);}} return this;}, - onEvent: function(f) {this._eventFuncs.push(f); for (var v in this._events) {try {f(this._events[v]);} catch (e) {console.log(e);}}; return this;}, + then: function(f) {this._valueFuncs.push(f); for (var v in this._values) {try {f(this._values[v]);} catch (e) {""" + (LiftRules.jsLogFunc.map(_(JsVar("e")).toJsCmd) openOr "") + """;}} return this;}, + fail: function(f) {this._failureFuncs.push(f); if (this._failed) {try {f(this._failMsg);} catch (e) {""" + (LiftRules.jsLogFunc.map(_(JsVar("e")).toJsCmd) openOr "") + """;}}; return this;}, + done: function(f) {this._doneFuncs.push(f); if (this._done) {try {f();} catch (e) {""" + (LiftRules.jsLogFunc.map(_(JsVar("e")).toJsCmd) openOr "") + """;}} return this;}, + onEvent: function(f) {this._eventFuncs.push(f); for (var v in this._events) {try {f(this._events[v]);} catch (e) {""" + (LiftRules.jsLogFunc.map(_(JsVar("e")).toJsCmd) openOr "") + """;}}; return this;}, map: function(f) {var ret = new liftAjax.Promise(); this.done(function() {ret.doneMsg();}); this.fail(function (m) {ret.failMsg(m);}); this.then(function (v) {ret.successMsg(f(v));}); return ret;} }; }, From a3f2853a6b7ba35bbb64efbc9b6099b48f91f1ff Mon Sep 17 00:00:00 2001 From: Viktor Hedefalk Date: Thu, 8 Aug 2013 17:48:48 +0200 Subject: [PATCH 0574/1949] Fixed path in Menu --- .../net/liftweb/sitemap/FlexMenuBuilder.scala | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala b/web/webkit/src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala index 106341a03a..aa96544492 100644 --- a/web/webkit/src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala +++ b/web/webkit/src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala @@ -41,12 +41,22 @@ trait FlexMenuBuilder { */ protected def expandAny = false - // This is used to build a MenuItem for a single Loc + /** + * This is used to build a MenuItem for a single Loc + */ protected def buildItemMenu[A](loc: Loc[A], currLoc: Box[Loc[_]], expandAll: Boolean): List[MenuItem] = { + def isInPath(loc: Loc[_]): Boolean = { + if(currLoc == Full(loc)) true else { + val kids = loc.menu.kids + if(kids.isEmpty) false else kids.exists(k => isInPath(k.loc)) + } + } + val kids: List[MenuItem] = if (expandAll) loc.buildKidMenuItems(loc.menu.kids) else Nil - loc.asInstanceOf[StructBuildItem].buildItem(kids, currLoc == Full(loc), currLoc == Full(loc)).toList + loc.asInstanceOf[StructBuildItem].buildItem(kids, currLoc == Full(loc), isInPath(loc)).toList } + /** * Compute the MenuItems to be rendered by looking at the 'item' and 'group' attributes */ From 0b19bd6082a8c3f7be0e4e59b72d1d72745e81f2 Mon Sep 17 00:00:00 2001 From: Viktor Hedefalk Date: Thu, 8 Aug 2013 20:18:59 +0200 Subject: [PATCH 0575/1949] removed unnescessary allocations --- .../net/liftweb/sitemap/FlexMenuBuilder.scala | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala b/web/webkit/src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala index aa96544492..d14cd80596 100644 --- a/web/webkit/src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala +++ b/web/webkit/src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala @@ -45,15 +45,18 @@ trait FlexMenuBuilder { * This is used to build a MenuItem for a single Loc */ protected def buildItemMenu[A](loc: Loc[A], currLoc: Box[Loc[_]], expandAll: Boolean): List[MenuItem] = { - def isInPath(loc: Loc[_]): Boolean = { - if(currLoc == Full(loc)) true else { - val kids = loc.menu.kids - if(kids.isEmpty) false else kids.exists(k => isInPath(k.loc)) + val isInPath = currLoc.map { cur => + def isInPath(loc: Loc[_]): Boolean = { + if(cur == loc) true else { + val kids = loc.menu.kids + if(kids.isEmpty) false else kids.exists(k => isInPath(k.loc)) + } } - } + isInPath(loc) + } openOr false val kids: List[MenuItem] = if (expandAll) loc.buildKidMenuItems(loc.menu.kids) else Nil - loc.asInstanceOf[StructBuildItem].buildItem(kids, currLoc == Full(loc), isInPath(loc)).toList + loc.asInstanceOf[StructBuildItem].buildItem(kids, currLoc == Full(loc), isInPath).toList } From 5b3580bd0d159dc2b410bc302ccadfe7176819ee Mon Sep 17 00:00:00 2001 From: Viktor Hedefalk Date: Thu, 8 Aug 2013 20:28:34 +0200 Subject: [PATCH 0576/1949] cleanup --- .../main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala b/web/webkit/src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala index d14cd80596..3ba21e58f8 100644 --- a/web/webkit/src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala +++ b/web/webkit/src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala @@ -46,12 +46,7 @@ trait FlexMenuBuilder { */ protected def buildItemMenu[A](loc: Loc[A], currLoc: Box[Loc[_]], expandAll: Boolean): List[MenuItem] = { val isInPath = currLoc.map { cur => - def isInPath(loc: Loc[_]): Boolean = { - if(cur == loc) true else { - val kids = loc.menu.kids - if(kids.isEmpty) false else kids.exists(k => isInPath(k.loc)) - } - } + def isInPath(loc: Loc[_]): Boolean = (cur == loc) || loc.menu.kids.exists(k => isInPath(k.loc)) isInPath(loc) } openOr false From 3e5940efb759dbe9c694e7a16d113dad1505cc55 Mon Sep 17 00:00:00 2001 From: Viktor Hedefalk Date: Thu, 8 Aug 2013 21:02:28 +0200 Subject: [PATCH 0577/1949] removed unnescessary structural typing hack --- .../src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala b/web/webkit/src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala index 3ba21e58f8..7784697bbb 100644 --- a/web/webkit/src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala +++ b/web/webkit/src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala @@ -23,9 +23,6 @@ import net.liftweb.util.Helpers trait FlexMenuBuilder { - // a hack to use structural typing to get around the private[http] on Loc.buildItem - type StructBuildItem = {def buildItem(kids: List[MenuItem], current: Boolean, path: Boolean): Box[MenuItem]} - /** * Override if you want a link to the current page */ @@ -51,7 +48,7 @@ trait FlexMenuBuilder { } openOr false val kids: List[MenuItem] = if (expandAll) loc.buildKidMenuItems(loc.menu.kids) else Nil - loc.asInstanceOf[StructBuildItem].buildItem(kids, currLoc == Full(loc), isInPath).toList + loc.buildItem(kids, currLoc == Full(loc), isInPath).toList } From 969cb2e858b5609029bd152087cf6971d1bb91d5 Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Thu, 8 Aug 2013 21:22:31 -0400 Subject: [PATCH 0578/1949] Fixed #1473 - SqlServerBaseDriver should set brokenLimit_? to true MicrosoftSQL at least 2008 and greater don't support the LIMIT clause, so now the default driver we offer will work out of the box with newer sql server versions --- persistence/db/src/main/scala/net/liftweb/db/Driver.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/persistence/db/src/main/scala/net/liftweb/db/Driver.scala b/persistence/db/src/main/scala/net/liftweb/db/Driver.scala index 1bfb248eda..8fd76d2278 100644 --- a/persistence/db/src/main/scala/net/liftweb/db/Driver.scala +++ b/persistence/db/src/main/scala/net/liftweb/db/Driver.scala @@ -364,6 +364,8 @@ abstract class SqlServerBaseDriver extends DriverType("Microsoft SQL Server") { // Microsoft doesn't use "COLUMN" syntax when adding a column to a table override def alterAddColumn = "ADD" + override def brokenLimit_? = true + } /** From 046c337e4685896963926fb3dc49785b73418328 Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Thu, 8 Aug 2013 22:34:40 -0400 Subject: [PATCH 0579/1949] Fixed #1157 - BasicTypesHelpers.toBoolean("on") returns false --- .../scala/net/liftweb/util/BasicTypesHelpers.scala | 14 +++----------- .../net/liftweb/util/BasicTypesHelpersSpec.scala | 5 +++-- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/BasicTypesHelpers.scala b/core/util/src/main/scala/net/liftweb/util/BasicTypesHelpers.scala index 0be6a51069..f775740c96 100644 --- a/core/util/src/main/scala/net/liftweb/util/BasicTypesHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/BasicTypesHelpers.scala @@ -200,15 +200,7 @@ trait BasicTypesHelpers { self: StringHelpers with ControlHelpers => case i: Int => i != 0 case lo: Long => lo != 0 case n : Number => n.intValue != 0 - case s : String => { - val sl = s.toLowerCase - if (sl.length == 0) false - else { - if (sl.charAt(0) == 't') true - else if (sl == "yes") true - else toInt(s) != 0 - } - } + case s : String => asBoolean(s) openOr false case None => false case Empty | Failure(_, _, _) => false case Full(n) => toBoolean(n) @@ -231,8 +223,8 @@ object AsBoolean { def unapply(in: String): Option[Boolean] = if (null eq in) None else in.toLowerCase match { - case "t" | "true" | "yes" | "1" => Full(true) - case "f" | "false" | "no" | "0" => Full(false) + case "t" | "true" | "yes" | "1" | "on" => Full(true) + case "f" | "false" | "no" | "0" | "off" => Full(false) case _ => None } } diff --git a/core/util/src/test/scala/net/liftweb/util/BasicTypesHelpersSpec.scala b/core/util/src/test/scala/net/liftweb/util/BasicTypesHelpersSpec.scala index ed3886583e..2b8aed6bd5 100644 --- a/core/util/src/test/scala/net/liftweb/util/BasicTypesHelpersSpec.scala +++ b/core/util/src/test/scala/net/liftweb/util/BasicTypesHelpersSpec.scala @@ -58,10 +58,11 @@ object BasicTypesHelpersSpec extends Specification with DataTables { "" !!false | "string" !!false | "t" !!true | - "total" !!true | + "total" !!false | "T" !!true | - "This" !!true | + "This" !!false | "0" !!false | + "on" !!true | None !!false | Some("t") !!true | Empty !!false | From c42dc2ab6c680b794b6fe9ec545a4ba1493be782 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 12 Aug 2013 13:54:02 -0400 Subject: [PATCH 0580/1949] Update comments in ListHelpers to refer to Box, not can. --- .../scala/net/liftweb/util/ListHelpers.scala | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/ListHelpers.scala b/core/util/src/main/scala/net/liftweb/util/ListHelpers.scala index 012e1b2359..80ec6b7ff6 100644 --- a/core/util/src/main/scala/net/liftweb/util/ListHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/ListHelpers.scala @@ -107,9 +107,9 @@ trait ListHelpers { } /** - * Returns a Full can with the first element x of the list in + * Returns a Full Box with the first element x of the list in * for which f(x) evaluates to true. If f(x) evaluates to false - * for every x, then an Empty can is returned. + * for every x, then an Empty Box is returned. * * @param in a list of elements to which f can be applied * @param f a function that can be applied to elements of in @@ -121,15 +121,15 @@ trait ListHelpers { /** * Returns the first application of f to an element of in that - * results in a Full can. If f applied to an element of in results - * in an Empty can, then f will be applied to the rest of the - * elements of in until a Full can results. If the list runs out - * then an Empty can is returned. + * results in a Full Box. If f applied to an element of in results + * in an Empty Box, then f will be applied to the rest of the + * elements of in until a Full Box results. If the list runs out + * then an Empty Box is returned. * * @param in a list of elements to which f can be applied * @param f a function that can be applied to elements of in * - * @return a Box containing the first Full can or Empty if f never returns a Full can + * @return a Box containing the first Full Box or Empty if f never returns a Full Box */ def first[B, C](in: Seq[B])(_f: B => Box[C]): Box[C] = { val f: B => Iterable[C] = _f andThen Box.box2Iterable[C] @@ -147,7 +147,7 @@ trait ListHelpers { * * @param key the string to find * - * @return a Full can containing the found value or Empty + * @return a Full Box containing the found value or Empty */ def ciGet(swhat: String): Box[String] = { val what = swhat.toLowerCase From c81f3d2d7191694d604ce2e995ba4e2788741488 Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Tue, 13 Aug 2013 23:27:21 -0400 Subject: [PATCH 0581/1949] Upgraded to sbt 0.12.4 --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index bb1fd56022..a2a2e1da53 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1,2 +1,2 @@ # Deprecate using build.properties, use -Dsbt.version=... in launcher arg instead -sbt.version=0.12.1 +sbt.version=0.12.4 From e97eaf788a980aff729da97886f8251ad0614a49 Mon Sep 17 00:00:00 2001 From: David Pollak Date: Wed, 14 Aug 2013 10:07:41 -0700 Subject: [PATCH 0582/1949] Remove source directive from the scalatest library --- project/Build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index 7d5bb1bf12..35264a4215 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -52,7 +52,7 @@ object BuildDef extends Build { .settings(description := "Markdown Parser", parallelExecution in Test := false, libraryDependencies ++= Seq( - "org.scalatest" %% "scalatest" % "1.9.1" % "test" withSources(), + "org.scalatest" %% "scalatest" % "1.9.1" % "test", "junit" % "junit" % "4.8.2" % "test" )) From c0be185086bec3bb5774309b3cf1f75f33ba0e63 Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Fri, 23 Aug 2013 22:22:23 -0400 Subject: [PATCH 0583/1949] Revert "removed unnescessary structural typing hack" This reverts commit 9167ff0fafd8543239b1d0ffd4e1435ccc1aa905. --- .../src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala b/web/webkit/src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala index 7784697bbb..3ba21e58f8 100644 --- a/web/webkit/src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala +++ b/web/webkit/src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala @@ -23,6 +23,9 @@ import net.liftweb.util.Helpers trait FlexMenuBuilder { + // a hack to use structural typing to get around the private[http] on Loc.buildItem + type StructBuildItem = {def buildItem(kids: List[MenuItem], current: Boolean, path: Boolean): Box[MenuItem]} + /** * Override if you want a link to the current page */ @@ -48,7 +51,7 @@ trait FlexMenuBuilder { } openOr false val kids: List[MenuItem] = if (expandAll) loc.buildKidMenuItems(loc.menu.kids) else Nil - loc.buildItem(kids, currLoc == Full(loc), isInPath).toList + loc.asInstanceOf[StructBuildItem].buildItem(kids, currLoc == Full(loc), isInPath).toList } From 4b3263b3f41f2bbd99cfcefc75e66a51553518a6 Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Fri, 23 Aug 2013 22:45:09 -0400 Subject: [PATCH 0584/1949] Revert "removed unnescessary structural typing hack" This reverts commit 3e5940efb759dbe9c694e7a16d113dad1505cc55. --- .../src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala b/web/webkit/src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala index 7784697bbb..3ba21e58f8 100644 --- a/web/webkit/src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala +++ b/web/webkit/src/main/scala/net/liftweb/sitemap/FlexMenuBuilder.scala @@ -23,6 +23,9 @@ import net.liftweb.util.Helpers trait FlexMenuBuilder { + // a hack to use structural typing to get around the private[http] on Loc.buildItem + type StructBuildItem = {def buildItem(kids: List[MenuItem], current: Boolean, path: Boolean): Box[MenuItem]} + /** * Override if you want a link to the current page */ @@ -48,7 +51,7 @@ trait FlexMenuBuilder { } openOr false val kids: List[MenuItem] = if (expandAll) loc.buildKidMenuItems(loc.menu.kids) else Nil - loc.buildItem(kids, currLoc == Full(loc), isInPath).toList + loc.asInstanceOf[StructBuildItem].buildItem(kids, currLoc == Full(loc), isInPath).toList } From efcd5b93ffff3ea355e59d7155410783e256eaab Mon Sep 17 00:00:00 2001 From: David Pollak Date: Wed, 14 Aug 2013 10:07:41 -0700 Subject: [PATCH 0585/1949] Remove source directive from the scalatest library --- project/Build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index 4bf981c5f1..959d9b2d22 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -52,7 +52,7 @@ object BuildDef extends Build { .settings(description := "Markdown Parser", parallelExecution in Test := false, libraryDependencies ++= Seq( - "org.scalatest" %% "scalatest" % "1.9.1" % "test" withSources(), + "org.scalatest" %% "scalatest" % "1.9.1" % "test", "junit" % "junit" % "4.8.2" % "test" )) From 5d0a297feefee8a34762e17edab65d498f8637a3 Mon Sep 17 00:00:00 2001 From: David Pollak Date: Mon, 26 Aug 2013 11:42:14 -0700 Subject: [PATCH 0586/1949] correctly escape end script tags --- web/webkit/src/main/scala/net/liftweb/http/js/JsCommands.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/js/JsCommands.scala b/web/webkit/src/main/scala/net/liftweb/http/js/JsCommands.scala index 76a120f871..37fc229cac 100755 --- a/web/webkit/src/main/scala/net/liftweb/http/js/JsCommands.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/js/JsCommands.scala @@ -634,8 +634,7 @@ object JsCmds { """)} private def fixEndScriptTag(in: String): String = - if (S.ieMode) """\<\/script\>""".r.replaceAllIn(in, """<\\/script>""") - else in + """\<\/script\>""".r.replaceAllIn(in, """<\\/script>""") } def JsHideId(what: String): JsCmd = LiftRules.jsArtifacts.hide(what).cmd From d1ab0ffe3fa0b77d1269183479fd662448d20d6d Mon Sep 17 00:00:00 2001 From: David Pollak Date: Mon, 26 Aug 2013 11:42:14 -0700 Subject: [PATCH 0587/1949] correctly escape end script tags --- web/webkit/src/main/scala/net/liftweb/http/js/JsCommands.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/js/JsCommands.scala b/web/webkit/src/main/scala/net/liftweb/http/js/JsCommands.scala index 924d26945b..ab80027ea7 100755 --- a/web/webkit/src/main/scala/net/liftweb/http/js/JsCommands.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/js/JsCommands.scala @@ -668,8 +668,7 @@ object JsCmds { """)} private def fixEndScriptTag(in: String): String = - if (S.ieMode) """\<\/script\>""".r.replaceAllIn(in, """<\\/script>""") - else in + """\<\/script\>""".r.replaceAllIn(in, """<\\/script>""") } def JsHideId(what: String): JsCmd = LiftRules.jsArtifacts.hide(what).cmd From 8d42eb8968ed7fa4147f76ab607125c72665f11b Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Mon, 2 Sep 2013 16:39:05 -0400 Subject: [PATCH 0588/1949] Name existing S.ieMode to S.legacyIeCompatibilityMode. We keep S.ieMode as a deprecated value until 3.0. --- .../net/liftweb/util/PCDataMarkupParser.scala | 18 +++++++++--------- .../net/liftweb/http/HtmlProperties.scala | 2 +- .../scala/net/liftweb/http/LiftRules.scala | 6 +++--- .../scala/net/liftweb/http/LiftSession.scala | 7 +++++-- .../src/main/scala/net/liftweb/http/Req.scala | 2 +- .../src/main/scala/net/liftweb/http/S.scala | 8 +++++++- 6 files changed, 26 insertions(+), 17 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/PCDataMarkupParser.scala b/core/util/src/main/scala/net/liftweb/util/PCDataMarkupParser.scala index 56270ab6c9..315e6a88a1 100644 --- a/core/util/src/main/scala/net/liftweb/util/PCDataMarkupParser.scala +++ b/core/util/src/main/scala/net/liftweb/util/PCDataMarkupParser.scala @@ -255,9 +255,9 @@ object AltXML { "param", "img", "area", "input", "col" ) def toXML(n: Node, stripComment: Boolean, convertAmp: Boolean, - ieMode: Boolean): String = { + legacyIeCompatibilityMode: Boolean): String = { val sb = new StringBuilder(50000) - toXML(n, TopScope, sb, stripComment, convertAmp, ieMode) + toXML(n, TopScope, sb, stripComment, convertAmp, legacyIeCompatibilityMode) sb.toString() } @@ -367,7 +367,7 @@ object AltXML { */ def toXML(x: Node, pscope: NamespaceBinding, sb: StringBuilder, stripComment: Boolean, convertAmp: Boolean, - ieMode: Boolean): Unit = + legacyIeCompatibilityMode: Boolean): Unit = x match { case Text(str) => escape(str, sb, !convertAmp) @@ -394,9 +394,9 @@ object AltXML { case g: Group => for (c <- g.nodes) - toXML(c, x.scope, sb, stripComment, convertAmp, ieMode) + toXML(c, x.scope, sb, stripComment, convertAmp, legacyIeCompatibilityMode) - case e: Elem if !ieMode && ((e.child eq null) || e.child.isEmpty) + case e: Elem if !legacyIeCompatibilityMode && ((e.child eq null) || e.child.isEmpty) && inlineTags.contains(e.label) => sb.append('<') e.nameToString(sb) @@ -404,7 +404,7 @@ object AltXML { e.scope.buildString(sb, pscope) sb.append(" />") - case e: Elem if ieMode && ((e.child eq null) || e.child.isEmpty) && + case e: Elem if legacyIeCompatibilityMode && ((e.child eq null) || e.child.isEmpty) && ieBadTags.contains(e.label) => sb.append('<') e.nameToString(sb) @@ -419,7 +419,7 @@ object AltXML { if (e.attributes ne null) e.attributes.buildString(sb) e.scope.buildString(sb, pscope) sb.append('>') - sequenceToXML(e.child, e.scope, sb, stripComment, convertAmp, ieMode) + sequenceToXML(e.child, e.scope, sb, stripComment, convertAmp, legacyIeCompatibilityMode) sb.append("') @@ -436,10 +436,10 @@ object AltXML { */ def sequenceToXML(children: Seq[Node], pscope: NamespaceBinding, sb: StringBuilder, stripComment: Boolean, - convertAmp: Boolean, ieMode: Boolean): Unit = { + convertAmp: Boolean, legacyIeCompatibilityMode: Boolean): Unit = { val it = children.iterator while (it.hasNext) { - toXML(it.next, pscope, sb, stripComment, convertAmp, ieMode) + toXML(it.next, pscope, sb, stripComment, convertAmp, legacyIeCompatibilityMode) } } diff --git a/web/webkit/src/main/scala/net/liftweb/http/HtmlProperties.scala b/web/webkit/src/main/scala/net/liftweb/http/HtmlProperties.scala index 00399b34a3..654b627426 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/HtmlProperties.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/HtmlProperties.scala @@ -290,7 +290,7 @@ final case class OldHtmlProperties(userAgent: Box[String]) extends HtmlPropertie val sb = new StringBuilder(64000) AltXML.toXML(n, scala.xml.TopScope, sb, false, !LiftRules.convertToEntity.vend, - S.ieMode) + S.legacyIeCompatibilityMode) w.append(sb) w.flush() } diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 6aa2da3a57..9bf8dfbe86 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -1314,7 +1314,7 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { val ret = XhtmlResponse(ns, /*LiftRules.docType.vend(req)*/S.htmlProperties.docType, headers, cookies, code, - S.ieMode) + S.legacyIeCompatibilityMode) ret._includeXmlVersion = !S.skipDocType ret }, headers, cookies, req) @@ -1393,11 +1393,11 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { val exceptionHandler = RulesSeq[ExceptionHandlerPF].append { case (Props.RunModes.Development, r, e) => logger.error("Exception being returned to browser when processing " + r.uri.toString, e) - XhtmlResponse(( Exception occured while processing {r.uri}
    {showException(e)}
    ), S.htmlProperties.docType, List("Content-Type" -> "text/html; charset=utf-8"), Nil, 500, S.ieMode) + XhtmlResponse(( Exception occured while processing {r.uri}
    {showException(e)}
    ), S.htmlProperties.docType, List("Content-Type" -> "text/html; charset=utf-8"), Nil, 500, S.legacyIeCompatibilityMode) case (_, r, e) => logger.error("Exception being returned to browser when processing " + r.uri.toString, e) - XhtmlResponse(( Something unexpected happened while serving the page at {r.uri} ), S.htmlProperties.docType, List("Content-Type" -> "text/html; charset=utf-8"), Nil, 500, S.ieMode) + XhtmlResponse(( Something unexpected happened while serving the page at {r.uri} ), S.htmlProperties.docType, List("Content-Type" -> "text/html; charset=utf-8"), Nil, 500, S.legacyIeCompatibilityMode) } /** diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index 58664b9cda..81d8699f93 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -717,10 +717,13 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, private case class RunnerHolder(name: String, func: S.AFuncHolder, owner: Box[String]) - object ieMode extends SessionVar[Boolean](LiftRules.calcIEMode()) { + object legacyIeCompatibilityMode extends SessionVar[Boolean](LiftRules.calcIEMode()) { override private[liftweb] def magicSessionVar_? = true } + @deprecated("Use legacyIeCompatibilityMode for legacy IE detection instead. This will be removed in Lift 3.0.", "2.6") + val ieMode = legacyIeCompatibilityMode + def terminateHint { if (_running_?) { markedForTermination = true; @@ -1181,7 +1184,7 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, private[http] def processRequest(request: Req, continuation: Box[() => Nothing]): Box[LiftResponse] = { - ieMode.is // make sure this is primed + legacyIeCompatibilityMode.is // make sure this is primed S.oldNotices(notices) LiftSession.onBeginServicing.foreach(f => tryo(f(this, request))) val ret = try { diff --git a/web/webkit/src/main/scala/net/liftweb/http/Req.scala b/web/webkit/src/main/scala/net/liftweb/http/Req.scala index 3a96ae7654..1690da0d80 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Req.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Req.scala @@ -589,7 +589,7 @@ object Req { private[liftweb] def defaultCreateNotFound(in: Req) = XhtmlResponse(( The Requested URL {in.contextPath + in.uri} was not found on this server ), - LiftRules.docType.vend(in), List("Content-Type" -> "text/html; charset=utf-8"), Nil, 404, S.ieMode) + LiftRules.docType.vend(in), List("Content-Type" -> "text/html; charset=utf-8"), Nil, 404, S.legacyIeCompatibilityMode) def unapply(in: Req): Option[(List[String], String, RequestType)] = Some((in.path.partPath, in.path.suffix, in.requestType)) } diff --git a/web/webkit/src/main/scala/net/liftweb/http/S.scala b/web/webkit/src/main/scala/net/liftweb/http/S.scala index 7a39e73c60..e6aae10377 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/S.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/S.scala @@ -566,6 +566,9 @@ trait S extends HasParams with Loggable { LiftRules.timeZoneCalculator(containerRequest) /** + * A boolean indicating whether or not the response should be rendered with + * special accomodations for IE 6 / 7 compatibility. + * * @return true if this response should be rendered in * IE6/IE7 compatibility mode. * @@ -576,7 +579,10 @@ trait S extends HasParams with Loggable { * @see Req.isIE8 * @see Req.isIE */ - def ieMode: Boolean = session.map(_.ieMode.is) openOr false // LiftRules.calcIEMode() + def legacyIeCompatibilityMode: Boolean = session.map(_.legacyIeCompatibilityMode.is) openOr false // LiftRules.calcIEMode() + + @deprecated("Use legacyIeCompatibilityMode for legacy IE detection instead, or use S.isIE* for general IE detection. This will be removed in Lift 3.0.", "2.6") + def ieMode = legacyIeCompatibilityMode /** * Get the current instance of HtmlProperties From cc91feedfe164e3a673b264467b2bf8379f87964 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Mon, 2 Sep 2013 16:47:01 -0400 Subject: [PATCH 0589/1949] Include user agent calculation functions in S. --- web/webkit/src/main/scala/net/liftweb/http/S.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/S.scala b/web/webkit/src/main/scala/net/liftweb/http/S.scala index e6aae10377..e2342a2613 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/S.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/S.scala @@ -258,7 +258,7 @@ object S extends S { * @see LiftSession * @see LiftFilter */ -trait S extends HasParams with Loggable { +trait S extends HasParams with Loggable with UserAgentCalculator { import S._ /* @@ -396,6 +396,10 @@ trait S extends HasParams with Loggable { request flatMap { r => CurrentLocation(r.location) } } + /** + * The user agent of the current request, if any. + **/ + def userAgent = request.flatMap(_.userAgent) /** * An exception was thrown during the processing of this request. From de146520efc25c322c429fbe0dc6797209f2dcbb Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Mon, 2 Sep 2013 16:52:06 -0400 Subject: [PATCH 0590/1949] Add helpers for detecting IE10/11. I know we were all hoping that by IE10 they'd be up to speed with the rest of the world, but I still remember some support issues, so I'm adding a detector for it in the event server side detection is needed for some people. Ditto for 11. Here's hoping these will be the last versions of IE that ever need such detection. --- web/webkit/src/main/scala/net/liftweb/http/Req.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/web/webkit/src/main/scala/net/liftweb/http/Req.scala b/web/webkit/src/main/scala/net/liftweb/http/Req.scala index 1690da0d80..fe6065a18f 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Req.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Req.scala @@ -143,6 +143,8 @@ trait UserAgentCalculator { lazy val isIE7: Boolean = ieVersion.map(_ == 7) openOr false lazy val isIE8: Boolean = ieVersion.map(_ == 8) openOr false lazy val isIE9: Boolean = ieVersion.map(_ == 9) openOr false + lazy val ieIE10: Boolean = ieVersion.map(_ == 10) openOr false + lazy val isIE11: Boolean = ieVersion.map(_ == 11) openOr false lazy val isIE = ieVersion.map(_ >= 6) openOr false lazy val safariVersion: Box[Int] = From 3a1254bf62166e1de9fa711999aa53de27c72584 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Mon, 2 Sep 2013 16:57:42 -0400 Subject: [PATCH 0591/1949] IE < 6 still counts as IE. Though we sincerely hope to never see any of these user agents, we should return the correct value for isIE if they hit our apps. --- web/webkit/src/main/scala/net/liftweb/http/Req.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/Req.scala b/web/webkit/src/main/scala/net/liftweb/http/Req.scala index fe6065a18f..704c4ec042 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Req.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Req.scala @@ -145,7 +145,7 @@ trait UserAgentCalculator { lazy val isIE9: Boolean = ieVersion.map(_ == 9) openOr false lazy val ieIE10: Boolean = ieVersion.map(_ == 10) openOr false lazy val isIE11: Boolean = ieVersion.map(_ == 11) openOr false - lazy val isIE = ieVersion.map(_ >= 6) openOr false + lazy val isIE = ieVersion.isDefined lazy val safariVersion: Box[Int] = UserAgentCalculator.safariCalcFunction.vend.apply(userAgent).map(_.toInt) From 20fecab4dba4442917068f5f41b64badaac21e1e Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Mon, 2 Sep 2013 17:04:08 -0400 Subject: [PATCH 0592/1949] Comments should reflect reality: update IE compat comments. We also consider IE 8 a legacy IE. --- web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala | 2 +- web/webkit/src/main/scala/net/liftweb/http/S.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 9bf8dfbe86..a413181cff 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -748,7 +748,7 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { /** * The function that calculates if the response should be rendered in - * IE6/7 compatibility mode + * IE6/7/8 compatibility mode */ @volatile var calcIEMode: () => Boolean = () => (for (r <- S.request) yield r.isIE6 || r.isIE7 || diff --git a/web/webkit/src/main/scala/net/liftweb/http/S.scala b/web/webkit/src/main/scala/net/liftweb/http/S.scala index e2342a2613..08582bc025 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/S.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/S.scala @@ -571,7 +571,7 @@ trait S extends HasParams with Loggable with UserAgentCalculator { /** * A boolean indicating whether or not the response should be rendered with - * special accomodations for IE 6 / 7 compatibility. + * special accomodations for IE 6 / 7 / 8 compatibility. * * @return true if this response should be rendered in * IE6/IE7 compatibility mode. From 244af806071b363ea1512daca2ad789e4577a652 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 14 Sep 2013 13:29:01 -0400 Subject: [PATCH 0593/1949] Simplify Loc.doesMatch_? As I was reading over this code to look at this issue, we were calling Link.isDefinedAt to determine whether or not to call Link.appy. Link.apply calls Link.isDefinedAt to determine whether or not to throw an exception. This seemed redundant in this particular case, so I've reduced this method to a simple boolean expression that should have the same effect. --- .../src/main/scala/net/liftweb/sitemap/Loc.scala | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/sitemap/Loc.scala b/web/webkit/src/main/scala/net/liftweb/sitemap/Loc.scala index f617c0810a..e09e82c94b 100644 --- a/web/webkit/src/main/scala/net/liftweb/sitemap/Loc.scala +++ b/web/webkit/src/main/scala/net/liftweb/sitemap/Loc.scala @@ -383,14 +383,9 @@ trait Loc[T] { } def doesMatch_?(req: Req): Boolean = { - (if (link.isDefinedAt(req)) { - link(req) match { - case Full(x) if testAllParams(allParams, req) => x - case Full(x) => false - case x => x.openOr(false) - } - } else false) && currentValue.isDefined - // the loc only matches if we've got a current value + link.isDefinedAt(req) && + testAllParams(allParams, req) && + currentValue.isDefined } def breadCrumbs: List[Loc[_]] = _menu.breadCrumbs ::: List(this) From 080dfc8b8b2f599f1902899d9502003beca91bae Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 14 Sep 2013 13:30:57 -0400 Subject: [PATCH 0594/1949] Add and implement the MatchWithoutCurrentValue LocParam. The MatchWithoutCurrentValue LocParam overrides Lift's default behavior of considering an Empty currentValue a non-match for a Loc. This default action is desirable in most circumstances, but occasionally we want to allow client code to define some custom behavior in the event currentValue comes up Empty. Using this new LocParam in combination with IfValue would allow them to do so. --- .../main/scala/net/liftweb/sitemap/Loc.scala | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/sitemap/Loc.scala b/web/webkit/src/main/scala/net/liftweb/sitemap/Loc.scala index e09e82c94b..3925ae9a12 100644 --- a/web/webkit/src/main/scala/net/liftweb/sitemap/Loc.scala +++ b/web/webkit/src/main/scala/net/liftweb/sitemap/Loc.scala @@ -385,7 +385,10 @@ trait Loc[T] { def doesMatch_?(req: Req): Boolean = { link.isDefinedAt(req) && testAllParams(allParams, req) && - currentValue.isDefined + ( + currentValue.isDefined || + params.contains(Loc.MatchWithoutCurrentValue) + ) } def breadCrumbs: List[Loc[_]] = _menu.breadCrumbs ::: List(this) @@ -731,6 +734,18 @@ object Loc { */ case object Hidden extends AnyLocParam + /** + * If this parameter is included, the Loc will continue to execute even if + * currentValue is not defined. + * + * By default, Lift will determine that a Loc does not match a given request + * if its currentValue comes up Empty, and as a result will return an HTTP 404. + * For situations where this is not the desired, "Not Found" behavior, you can + * add the MatchWithoutCurrentValue LocParam to a Loc, then use the IfValue + * LocParam to define what should happen when the currentValue is Empty. + */ + case object MatchWithoutCurrentValue extends AnyLocParam + /** * If this is a submenu, use the parent Loc's params */ From 5654bebacae6dbc83972a077ef56c4cc6bac055e Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 14 Sep 2013 14:05:55 -0400 Subject: [PATCH 0595/1949] Add specs to prove MatchWithoutCurrentValue works as expected. --- .../scala/net/liftweb/sitemap/LocSpec.scala | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/web/webkit/src/test/scala/net/liftweb/sitemap/LocSpec.scala b/web/webkit/src/test/scala/net/liftweb/sitemap/LocSpec.scala index d7e35281e5..9f6b3da109 100644 --- a/web/webkit/src/test/scala/net/liftweb/sitemap/LocSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/sitemap/LocSpec.scala @@ -18,6 +18,10 @@ package net.liftweb package sitemap import common._ +import mockweb._ + import MockWeb._ +import mocks._ + import org.specs2.mutable.Specification @@ -40,6 +44,34 @@ object LocSpec extends Specification { val loc = (Menu.param[Param]("Test", "Test", s => Full(Param(s)), p => p.s) / "foo" / "bar" / *).toLoc loc.calcHref(Param("myparam")) mustEqual "/foo/bar/myparam" } + + "should not match a Req matching its Link when currentValue is Empty" in { + val testMenu = Menu.param[Param]("Test", "Test", s => Empty, p => "bacon") / "foo" / "bar" / * + val testSiteMap = SiteMap(testMenu) + + val testLoc = testMenu.toLoc + val mockReq = new MockHttpServletRequest("http://test/foo/bar/123") + + testS(mockReq) { + testReq(mockReq) { req => + testLoc.doesMatch_?(req) mustEqual false + } + } + } + + "should match a Req matching its Link when currentValue is Empty and MatchWithoutCurrentValue is a param" in { + val testMenu = Menu.param[Param]("Test", "Test", s => Empty, p => "bacon") / "foo" / "bar" / * >> Loc.MatchWithoutCurrentValue + val testSiteMap = SiteMap(testMenu) + + val testLoc = testMenu.toLoc + val mockReq = new MockHttpServletRequest("http://test/foo/bar/123") + + testS(mockReq) { + testReq(mockReq) { req => + testLoc.doesMatch_?(req) mustEqual true + } + } + } } } From d18c6efc66f6fad4ce9c7e599e6e8af89b13774a Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 14 Sep 2013 18:21:12 -0400 Subject: [PATCH 0596/1949] Add an example of using MatchWithoutCurrentValue with IfValue. --- web/webkit/src/main/scala/net/liftweb/sitemap/Loc.scala | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/web/webkit/src/main/scala/net/liftweb/sitemap/Loc.scala b/web/webkit/src/main/scala/net/liftweb/sitemap/Loc.scala index 3925ae9a12..48cedace66 100644 --- a/web/webkit/src/main/scala/net/liftweb/sitemap/Loc.scala +++ b/web/webkit/src/main/scala/net/liftweb/sitemap/Loc.scala @@ -743,6 +743,15 @@ object Loc { * For situations where this is not the desired, "Not Found" behavior, you can * add the MatchWithoutCurrentValue LocParam to a Loc, then use the IfValue * LocParam to define what should happen when the currentValue is Empty. + * + * For example, given some class Thing, you could do the following to trigger + * a redirect when a Thing with a particular ID isn't found. + * + * {{{ + * Menu.param[Thing]("Thing", "Thing", Thing.find(_), _.id) >> + * MatchWithoutCurrentValue >> + * IfValue(_.isDefined, () => RedirectResponse("/page/to/redirect/to")) + * }}} */ case object MatchWithoutCurrentValue extends AnyLocParam From a27e3ab3c0d2f2cf34883b92a6c95da3e0878a2e Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Mon, 2 Sep 2013 16:39:05 -0400 Subject: [PATCH 0597/1949] Name existing S.ieMode to S.legacyIeCompatibilityMode. We keep S.ieMode as a deprecated value until 3.0. --- .../net/liftweb/util/PCDataMarkupParser.scala | 18 +++++++++--------- .../net/liftweb/http/HtmlProperties.scala | 2 +- .../scala/net/liftweb/http/LiftRules.scala | 6 +++--- .../scala/net/liftweb/http/LiftSession.scala | 7 +++++-- .../src/main/scala/net/liftweb/http/Req.scala | 2 +- .../src/main/scala/net/liftweb/http/S.scala | 8 +++++++- 6 files changed, 26 insertions(+), 17 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/PCDataMarkupParser.scala b/core/util/src/main/scala/net/liftweb/util/PCDataMarkupParser.scala index b4730f8f35..7f2bdd340e 100644 --- a/core/util/src/main/scala/net/liftweb/util/PCDataMarkupParser.scala +++ b/core/util/src/main/scala/net/liftweb/util/PCDataMarkupParser.scala @@ -255,9 +255,9 @@ object AltXML { "param", "img", "area", "input", "col" ) def toXML(n: Node, stripComment: Boolean, convertAmp: Boolean, - ieMode: Boolean): String = { + legacyIeCompatibilityMode: Boolean): String = { val sb = new StringBuilder(50000) - toXML(n, TopScope, sb, stripComment, convertAmp, ieMode) + toXML(n, TopScope, sb, stripComment, convertAmp, legacyIeCompatibilityMode) sb.toString() } @@ -367,7 +367,7 @@ object AltXML { */ def toXML(x: Node, pscope: NamespaceBinding, sb: StringBuilder, stripComment: Boolean, convertAmp: Boolean, - ieMode: Boolean): Unit = + legacyIeCompatibilityMode: Boolean): Unit = x match { case Text(str) => escape(str, sb, !convertAmp) @@ -394,9 +394,9 @@ object AltXML { case g: Group => for (c <- g.nodes) - toXML(c, x.scope, sb, stripComment, convertAmp, ieMode) + toXML(c, x.scope, sb, stripComment, convertAmp, legacyIeCompatibilityMode) - case e: Elem if !ieMode && ((e.child eq null) || e.child.isEmpty) + case e: Elem if !legacyIeCompatibilityMode && ((e.child eq null) || e.child.isEmpty) && inlineTags.contains(e.label) => sb.append('<') e.nameToString(sb) @@ -404,7 +404,7 @@ object AltXML { e.scope.buildString(sb, pscope) sb.append(" />") - case e: Elem if ieMode && ((e.child eq null) || e.child.isEmpty) && + case e: Elem if legacyIeCompatibilityMode && ((e.child eq null) || e.child.isEmpty) && ieBadTags.contains(e.label) => sb.append('<') e.nameToString(sb) @@ -419,7 +419,7 @@ object AltXML { if (e.attributes ne null) e.attributes.buildString(sb) e.scope.buildString(sb, pscope) sb.append('>') - sequenceToXML(e.child, e.scope, sb, stripComment, convertAmp, ieMode) + sequenceToXML(e.child, e.scope, sb, stripComment, convertAmp, legacyIeCompatibilityMode) sb.append("') @@ -436,10 +436,10 @@ object AltXML { */ def sequenceToXML(children: Seq[Node], pscope: NamespaceBinding, sb: StringBuilder, stripComment: Boolean, - convertAmp: Boolean, ieMode: Boolean): Unit = { + convertAmp: Boolean, legacyIeCompatibilityMode: Boolean): Unit = { val it = children.iterator while (it.hasNext) { - toXML(it.next, pscope, sb, stripComment, convertAmp, ieMode) + toXML(it.next, pscope, sb, stripComment, convertAmp, legacyIeCompatibilityMode) } } diff --git a/web/webkit/src/main/scala/net/liftweb/http/HtmlProperties.scala b/web/webkit/src/main/scala/net/liftweb/http/HtmlProperties.scala index 00399b34a3..654b627426 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/HtmlProperties.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/HtmlProperties.scala @@ -290,7 +290,7 @@ final case class OldHtmlProperties(userAgent: Box[String]) extends HtmlPropertie val sb = new StringBuilder(64000) AltXML.toXML(n, scala.xml.TopScope, sb, false, !LiftRules.convertToEntity.vend, - S.ieMode) + S.legacyIeCompatibilityMode) w.append(sb) w.flush() } diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index e951b9c992..948a22409f 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -1395,7 +1395,7 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { val ret = XhtmlResponse(ns, /*LiftRules.docType.vend(req)*/S.htmlProperties.docType, headers, cookies, code, - S.ieMode) + S.legacyIeCompatibilityMode) ret._includeXmlVersion = !S.skipDocType ret }, headers, cookies, req) @@ -1474,11 +1474,11 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { val exceptionHandler = RulesSeq[ExceptionHandlerPF].append { case (Props.RunModes.Development, r, e) => logger.error("Exception being returned to browser when processing " + r.uri.toString, e) - XhtmlResponse(( Exception occured while processing {r.uri}
    {showException(e)}
    ), S.htmlProperties.docType, List("Content-Type" -> "text/html; charset=utf-8"), Nil, 500, S.ieMode) + XhtmlResponse(( Exception occured while processing {r.uri}
    {showException(e)}
    ), S.htmlProperties.docType, List("Content-Type" -> "text/html; charset=utf-8"), Nil, 500, S.legacyIeCompatibilityMode) case (_, r, e) => logger.error("Exception being returned to browser when processing " + r.uri.toString, e) - XhtmlResponse(( Something unexpected happened while serving the page at {r.uri} ), S.htmlProperties.docType, List("Content-Type" -> "text/html; charset=utf-8"), Nil, 500, S.ieMode) + XhtmlResponse(( Something unexpected happened while serving the page at {r.uri} ), S.htmlProperties.docType, List("Content-Type" -> "text/html; charset=utf-8"), Nil, 500, S.legacyIeCompatibilityMode) } /** diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index fe22915c3a..0059169763 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -751,10 +751,13 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, private case class RunnerHolder(name: String, func: S.AFuncHolder, owner: Box[String]) - object ieMode extends SessionVar[Boolean](LiftRules.calcIEMode()) { + object legacyIeCompatibilityMode extends SessionVar[Boolean](LiftRules.calcIEMode()) { override private[liftweb] def magicSessionVar_? = true } + @deprecated("Use legacyIeCompatibilityMode for legacy IE detection instead. This will be removed in Lift 3.0.", "2.6") + val ieMode = legacyIeCompatibilityMode + def terminateHint { if (_running_?) { markedForTermination = true; @@ -1236,7 +1239,7 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, private[http] def processRequest(request: Req, continuation: Box[() => Nothing]): Box[LiftResponse] = { - ieMode.is // make sure this is primed + legacyIeCompatibilityMode.is // make sure this is primed S.oldNotices(notices) LiftSession.onBeginServicing.foreach(f => tryo(f(this, request))) val ret = try { diff --git a/web/webkit/src/main/scala/net/liftweb/http/Req.scala b/web/webkit/src/main/scala/net/liftweb/http/Req.scala index 3a96ae7654..1690da0d80 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Req.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Req.scala @@ -589,7 +589,7 @@ object Req { private[liftweb] def defaultCreateNotFound(in: Req) = XhtmlResponse(( The Requested URL {in.contextPath + in.uri} was not found on this server ), - LiftRules.docType.vend(in), List("Content-Type" -> "text/html; charset=utf-8"), Nil, 404, S.ieMode) + LiftRules.docType.vend(in), List("Content-Type" -> "text/html; charset=utf-8"), Nil, 404, S.legacyIeCompatibilityMode) def unapply(in: Req): Option[(List[String], String, RequestType)] = Some((in.path.partPath, in.path.suffix, in.requestType)) } diff --git a/web/webkit/src/main/scala/net/liftweb/http/S.scala b/web/webkit/src/main/scala/net/liftweb/http/S.scala index e0ae86b293..bdd1db38fd 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/S.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/S.scala @@ -568,6 +568,9 @@ trait S extends HasParams with Loggable { LiftRules.timeZoneCalculator(containerRequest) /** + * A boolean indicating whether or not the response should be rendered with + * special accomodations for IE 6 / 7 compatibility. + * * @return true if this response should be rendered in * IE6/IE7 compatibility mode. * @@ -578,7 +581,10 @@ trait S extends HasParams with Loggable { * @see Req.isIE8 * @see Req.isIE */ - def ieMode: Boolean = session.map(_.ieMode.is) openOr false // LiftRules.calcIEMode() + def legacyIeCompatibilityMode: Boolean = session.map(_.legacyIeCompatibilityMode.is) openOr false // LiftRules.calcIEMode() + + @deprecated("Use legacyIeCompatibilityMode for legacy IE detection instead, or use S.isIE* for general IE detection. This will be removed in Lift 3.0.", "2.6") + def ieMode = legacyIeCompatibilityMode /** * Get the current instance of HtmlProperties From 62ac1e6d637ad1d4c3964a39cb67c182780205b3 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Mon, 2 Sep 2013 16:47:01 -0400 Subject: [PATCH 0598/1949] Include user agent calculation functions in S. --- web/webkit/src/main/scala/net/liftweb/http/S.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/S.scala b/web/webkit/src/main/scala/net/liftweb/http/S.scala index bdd1db38fd..a8f86425a5 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/S.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/S.scala @@ -258,7 +258,7 @@ object S extends S { * @see LiftSession * @see LiftFilter */ -trait S extends HasParams with Loggable { +trait S extends HasParams with Loggable with UserAgentCalculator { import S._ /* @@ -398,6 +398,10 @@ trait S extends HasParams with Loggable { request flatMap { r => CurrentLocation(r.location) } } + /** + * The user agent of the current request, if any. + **/ + def userAgent = request.flatMap(_.userAgent) /** * An exception was thrown during the processing of this request. From 7ad613ab01287214236d5a545aa17eb177cae9c6 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Mon, 2 Sep 2013 16:52:06 -0400 Subject: [PATCH 0599/1949] Add helpers for detecting IE10/11. I know we were all hoping that by IE10 they'd be up to speed with the rest of the world, but I still remember some support issues, so I'm adding a detector for it in the event server side detection is needed for some people. Ditto for 11. Here's hoping these will be the last versions of IE that ever need such detection. --- web/webkit/src/main/scala/net/liftweb/http/Req.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/web/webkit/src/main/scala/net/liftweb/http/Req.scala b/web/webkit/src/main/scala/net/liftweb/http/Req.scala index 1690da0d80..fe6065a18f 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Req.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Req.scala @@ -143,6 +143,8 @@ trait UserAgentCalculator { lazy val isIE7: Boolean = ieVersion.map(_ == 7) openOr false lazy val isIE8: Boolean = ieVersion.map(_ == 8) openOr false lazy val isIE9: Boolean = ieVersion.map(_ == 9) openOr false + lazy val ieIE10: Boolean = ieVersion.map(_ == 10) openOr false + lazy val isIE11: Boolean = ieVersion.map(_ == 11) openOr false lazy val isIE = ieVersion.map(_ >= 6) openOr false lazy val safariVersion: Box[Int] = From 4d861893e19605b82c78251dda70b456640d4f93 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Mon, 2 Sep 2013 16:57:42 -0400 Subject: [PATCH 0600/1949] IE < 6 still counts as IE. Though we sincerely hope to never see any of these user agents, we should return the correct value for isIE if they hit our apps. --- web/webkit/src/main/scala/net/liftweb/http/Req.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/Req.scala b/web/webkit/src/main/scala/net/liftweb/http/Req.scala index fe6065a18f..704c4ec042 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Req.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Req.scala @@ -145,7 +145,7 @@ trait UserAgentCalculator { lazy val isIE9: Boolean = ieVersion.map(_ == 9) openOr false lazy val ieIE10: Boolean = ieVersion.map(_ == 10) openOr false lazy val isIE11: Boolean = ieVersion.map(_ == 11) openOr false - lazy val isIE = ieVersion.map(_ >= 6) openOr false + lazy val isIE = ieVersion.isDefined lazy val safariVersion: Box[Int] = UserAgentCalculator.safariCalcFunction.vend.apply(userAgent).map(_.toInt) From 8164ed66d8f4b8acb1a0a414e3bc211b5a5155c0 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Mon, 2 Sep 2013 17:04:08 -0400 Subject: [PATCH 0601/1949] Comments should reflect reality: update IE compat comments. We also consider IE 8 a legacy IE. --- web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala | 2 +- web/webkit/src/main/scala/net/liftweb/http/S.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 948a22409f..0ce94c8759 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -805,7 +805,7 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { /** * The function that calculates if the response should be rendered in - * IE6/7 compatibility mode + * IE6/7/8 compatibility mode */ @volatile var calcIEMode: () => Boolean = () => (for (r <- S.request) yield r.isIE6 || r.isIE7 || diff --git a/web/webkit/src/main/scala/net/liftweb/http/S.scala b/web/webkit/src/main/scala/net/liftweb/http/S.scala index a8f86425a5..1bf03e0cbc 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/S.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/S.scala @@ -573,7 +573,7 @@ trait S extends HasParams with Loggable with UserAgentCalculator { /** * A boolean indicating whether or not the response should be rendered with - * special accomodations for IE 6 / 7 compatibility. + * special accomodations for IE 6 / 7 / 8 compatibility. * * @return true if this response should be rendered in * IE6/IE7 compatibility mode. From 6407aa438c8a55371a9ffee8c2532214f31926cf Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 14 Aug 2013 13:36:12 -0400 Subject: [PATCH 0602/1949] Add centralized handling for parameters not mapped to Lift functions. We add a LiftRules entry, handleUnmappedParameter, that is a FactoryMaker that vends a function to handle any parmaeter not mapped to a Lift function. By default, the function checks if the parameter name starts with F, and logs a warning if so. This is to do a quick, high-speed check as to whether or not the parameter looks like a Lift function binding. --- .../main/scala/net/liftweb/http/LiftRules.scala | 15 +++++++++++++++ .../main/scala/net/liftweb/http/LiftSession.scala | 9 +++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index a413181cff..262c04065e 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -492,6 +492,21 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { */ @volatile var localizationLookupFailureNotice: Box[(String, Locale) => Unit] = Empty + /** + * When a parameter is received either via POST or GET and does not have a + * corresponding mapping on the server, the function provided by this + * FactoryMaker will be called with the req and parameter name. + * + * By default, if the parameter looks Lift-like (i.e., it starts with an F), + * then we log a warning with the given parameter name and URI. + */ + val handleUnmappedParameter = new FactoryMaker[(Req,String)=>Unit]( + () => { (req: Req, parameterName: String) => + if (parameterName.startsWith("F")) + logger.warn("Unmapped Lift-like parameter seen in request [%s]: %s".format(req.uri, parameterName)) + } + ) {} + /** * Set to false if you want to have 404's handled the same way in dev and production mode */ diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index 81d8699f93..014314b5bf 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -747,8 +747,13 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, val toRun = { // get all the commands, sorted by owner, (state.uploadedFiles.map(_.name) ::: state.paramNames).distinct. - flatMap { - n => Box.legacyNullTest(nmessageCallback.get(n)).map(mcb => RunnerHolder(n, mcb, mcb.owner)).toList + flatMap { parameterName => + val callback = Box.legacyNullTest(nmessageCallback.get(parameterName)) + + if (callback.isEmpty) + LiftRules.handleUnmappedParameter.vend(state, parameterName) + + callback.map(funcHolder => RunnerHolder(parameterName, funcHolder, funcHolder.owner)).toList }. sortWith { case (RunnerHolder(_, _, Full(a)), RunnerHolder(_, _, Full(b))) if a < b => true From 518b1378e0c8ea8748f5e55e0741281c226a82ac Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 14 Aug 2013 13:54:57 -0400 Subject: [PATCH 0603/1949] Strip a no-longer-relevant warning about synchronization. --- web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala | 4 ---- 1 file changed, 4 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index 014314b5bf..bc8bfda49d 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -574,10 +574,6 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, def ? = this.box openOr false } - /** - * ****IMPORTANT**** when you access messageCallback, it *MUST* - * be in a block that's synchronized on the owner LiftSession - */ private val nmessageCallback: ConcurrentHashMap[String, S.AFuncHolder] = new ConcurrentHashMap private val msgCallbackSync = new Object From 61a5019f2aaf9481b012a5b06f776068e022053b Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 9 Sep 2013 08:39:56 -0400 Subject: [PATCH 0604/1949] Fix some indentation fail. --- web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index bc8bfda49d..ef7b483d38 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -747,7 +747,7 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, val callback = Box.legacyNullTest(nmessageCallback.get(parameterName)) if (callback.isEmpty) - LiftRules.handleUnmappedParameter.vend(state, parameterName) + LiftRules.handleUnmappedParameter.vend(state, parameterName) callback.map(funcHolder => RunnerHolder(parameterName, funcHolder, funcHolder.owner)).toList }. From e43fbf9f005b1af4092464d31c8d0e6fe6d9fd20 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 14 Aug 2013 13:36:12 -0400 Subject: [PATCH 0605/1949] Add centralized handling for parameters not mapped to Lift functions. We add a LiftRules entry, handleUnmappedParameter, that is a FactoryMaker that vends a function to handle any parmaeter not mapped to a Lift function. By default, the function checks if the parameter name starts with F, and logs a warning if so. This is to do a quick, high-speed check as to whether or not the parameter looks like a Lift function binding. --- .../scala/net/liftweb/http/LiftRules.scala | 15 ++++++++ .../scala/net/liftweb/http/LiftSession.scala | 37 +++++++++++-------- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 0ce94c8759..132a49cade 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -533,6 +533,21 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { */ @volatile var localizationLookupFailureNotice: Box[(String, Locale) => Unit] = Empty + /** + * When a parameter is received either via POST or GET and does not have a + * corresponding mapping on the server, the function provided by this + * FactoryMaker will be called with the req and parameter name. + * + * By default, if the parameter looks Lift-like (i.e., it starts with an F), + * then we log a warning with the given parameter name and URI. + */ + val handleUnmappedParameter = new FactoryMaker[(Req,String)=>Unit]( + () => { (req: Req, parameterName: String) => + if (parameterName.startsWith("F")) + logger.warn("Unmapped Lift-like parameter seen in request [%s]: %s".format(req.uri, parameterName)) + } + ) {} + /** * Set to false if you want to have 404's handled the same way in dev and production mode */ diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index 0059169763..8a927d390f 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -778,25 +778,32 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, * Executes the user's functions based on the query parameters */ def runParams(state: Req): List[Any] = { - + + val toRun = { // get all the commands, sorted by owner, - (state.uploadedFiles.map(_.name) ::: state.paramNames).distinct. - flatMap { n => + (state.uploadedFiles.map(_.name) ::: state.paramNames) + .distinct + .flatMap { parameterName => msgCallbackSync.synchronized { - (Box !! nmessageCallback.get(n)) - }.map(mcb => RunnerHolder(n, mcb, mcb.owner)) - }. - sortWith { - case (RunnerHolder(_, _, Full(a)), RunnerHolder(_, _, Full(b))) if a < b => true - case (RunnerHolder(_, _, Full(a)), RunnerHolder(_, _, Full(b))) if a > b => false - case (RunnerHolder(an, _, Full(a)), RunnerHolder(bn, _, Full(b))) if a == b => an < bn - case (RunnerHolder(_, _, Full(_)), _) => false - case (_, RunnerHolder(_, _, Full(_))) => true - case (RunnerHolder(a, _, _), RunnerHolder(b, _, _)) => a < b - case _ => false + val callback = Box.legacyNullTest(nmessageCallback.get(parameterName)) + + if (callback.isEmpty) + LiftRules.handleUnmappedParameter.vend(state, parameterName) + + callback + }.map(funcHolder => RunnerHolder(parameterName, funcHolder, funcHolder.owner)) + } + .sortWith { + case (RunnerHolder(_, _, Full(a)), RunnerHolder(_, _, Full(b))) if a < b => true + case (RunnerHolder(_, _, Full(a)), RunnerHolder(_, _, Full(b))) if a > b => false + case (RunnerHolder(an, _, Full(a)), RunnerHolder(bn, _, Full(b))) if a == b => an < bn + case (RunnerHolder(_, _, Full(_)), _) => false + case (_, RunnerHolder(_, _, Full(_))) => true + case (RunnerHolder(a, _, _), RunnerHolder(b, _, _)) => a < b + case _ => false + } } - } def buildFunc(i: RunnerHolder): () => Any = i.func match { case bfh if bfh.supportsFileParams_? => From d620240035308307ab5ab96c7705d415a4cc5b14 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 14 Aug 2013 13:54:57 -0400 Subject: [PATCH 0606/1949] Strip a no-longer-relevant warning about synchronization. --- web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala | 5 ----- 1 file changed, 5 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index 8a927d390f..a3d9a4728a 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -608,10 +608,6 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, def ? = this.box openOr false } - /** - * ****IMPORTANT**** when you access messageCallback, it *MUST* - * be in a block that's synchronized on the owner LiftSession - */ private var nmessageCallback: ConcurrentHashMap[String, S.AFuncHolder] = new ConcurrentHashMap() @volatile private[http] var notices: Seq[(NoticeType.Value, NodeSeq, Box[String])] = Nil @@ -628,7 +624,6 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, @volatile private var onSessionEnd: List[LiftSession => Unit] = Nil - //private val sessionVarSync = new Object /** * Cache the value of allowing snippet attribute processing From 27a56b1fd9ef66c7d2239affcc75c6a6e8587f8c Mon Sep 17 00:00:00 2001 From: Diego Medina Date: Sat, 14 Sep 2013 23:36:07 -0400 Subject: [PATCH 0607/1949] We use a java ConcurrentHashMap, so we don't need to use a Scala Object and a call to synchronize (this was fixed correctly on 2.6 a while ago but was never ported to lift 3.0) --- .../scala/net/liftweb/http/LiftSession.scala | 112 +++++++----------- 1 file changed, 46 insertions(+), 66 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index a3d9a4728a..9456b7ad50 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -41,7 +41,7 @@ import org.mozilla.javascript.Scriptable import org.mozilla.javascript.UniqueTag import json.JsonAST.{JString, JValue} import xml.Group -import java.util.concurrent.ConcurrentHashMap + object LiftSession { @@ -612,8 +612,6 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, @volatile private[http] var notices: Seq[(NoticeType.Value, NodeSeq, Box[String])] = Nil - private val msgCallbackSync = new Object - private val nasyncComponents: ConcurrentHashMap[(Box[String], Box[String]), LiftCometActor] = new ConcurrentHashMap() private val nasyncById: ConcurrentHashMap[String, LiftCometActor] = new ConcurrentHashMap() @@ -765,9 +763,7 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, * well, you can look them up. */ def findFunc(funcName: String): Option[S.AFuncHolder] = - msgCallbackSync.synchronized { - Box !! nmessageCallback.get(funcName) - } + Box !! nmessageCallback.get(funcName) /** * Executes the user's functions based on the query parameters @@ -780,14 +776,12 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, (state.uploadedFiles.map(_.name) ::: state.paramNames) .distinct .flatMap { parameterName => - msgCallbackSync.synchronized { - val callback = Box.legacyNullTest(nmessageCallback.get(parameterName)) - - if (callback.isEmpty) - LiftRules.handleUnmappedParameter.vend(state, parameterName) - - callback - }.map(funcHolder => RunnerHolder(parameterName, funcHolder, funcHolder.owner)) + val callback = Box.legacyNullTest(nmessageCallback.get(parameterName)) + + if (callback.isEmpty) + LiftRules.handleUnmappedParameter.vend(state, parameterName) + + callback.map(funcHolder => RunnerHolder(parameterName, funcHolder, funcHolder.owner)) } .sortWith { case (RunnerHolder(_, _, Full(a)), RunnerHolder(_, _, Full(b))) if a < b => true @@ -829,19 +823,14 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, * Updates the internal functions mapping */ def updateFunctionMap(funcs: Map[String, S.AFuncHolder], uniqueId: String, when: Long): Unit = - msgCallbackSync.synchronized { - funcs.foreach { - case (name, func) => - nmessageCallback.put(name, - if (func.owner == Full(uniqueId)) func else func.duplicate(uniqueId)) - } + funcs.foreach { + case (name, func) => + nmessageCallback.put(name, + if (func.owner == Full(uniqueId)) func else func.duplicate(uniqueId)) } - def removeFunction(name: String) = { - msgCallbackSync.synchronized{ - nmessageCallback.remove(name) - } - } + def removeFunction(name: String) = + nmessageCallback.remove(name) /** * Set your session-specific progress listener for mime uploads @@ -963,17 +952,15 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, } import scala.collection.JavaConversions._ - msgCallbackSync.synchronized { - nmessageCallback.entrySet(). foreach { - case s => - val k = s.getKey - val f = s.getValue - if (!f.sessionLife && - f.owner.isDefined && - (now - f.lastSeen) > LiftRules.unusedFunctionsLifeTime) { - nmessageCallback.remove(k) - } - } + nmessageCallback.entrySet().foreach { + case s => + val k = s.getKey + val f = s.getValue + if (!f.sessionLife && + f.owner.isDefined && + (now - f.lastSeen) > LiftRules.unusedFunctionsLifeTime) { + nmessageCallback.remove(k) + } } } } @@ -1101,22 +1088,20 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, } import scala.collection.JavaConversions._ - msgCallbackSync.synchronized { - (0 /: nmessageCallback.entrySet())((l, v) => l + (v.getValue.owner match { - case Full(owner) if (owner == ownerName) => - v.getValue.lastSeen = time - 1 - case Empty => v.getValue.lastSeen = time + (0 /: nmessageCallback.entrySet())((l, v) => l + (v.getValue.owner match { + case Full(owner) if (owner == ownerName) => + v.getValue.lastSeen = time 1 - case _ => 0 - })) - } + case Empty => v.getValue.lastSeen = time + 1 + case _ => 0 + })) } /** * Returns true if there are functions bound for this owner */ - private[http] def hasFuncsForOwner(owner: String): Boolean = msgCallbackSync.synchronized { + private[http] def hasFuncsForOwner(owner: String): Boolean = { import scala.collection.JavaConversions._ !nmessageCallback.values().find(_.owner == owner).isEmpty @@ -1379,7 +1364,7 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, private[http] def attachRedirectFunc(uri: String, f: Box[() => Unit]) = { f map { fnc => - val func: String = msgCallbackSync.synchronized { + val func: String = { val funcName = Helpers.nextFuncName nmessageCallback.put(funcName, S.NFuncHolder(() => { fnc() @@ -2610,30 +2595,25 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, val toCmp = Full(act.uniqueId) - msgCallbackSync.synchronized { - import scala.collection.JavaConversions._ - nmessageCallback.foreach {v => v match { - case (k, f) => - if (f.owner == toCmp) nmessageCallback.remove(k) - }} - } - - synchronized { + import scala.collection.JavaConversions._ + nmessageCallback.foreach {v => v match { + case (k, f) => + if (f.owner == toCmp) nmessageCallback.remove(k) + }} + LiftSession.this.synchronized { accessPostPageFuncs { postPageFunctions -= act.uniqueId } } - msgCallbackSync.synchronized { - import scala.collection.JavaConversions._ - val id = Full(act.uniqueId) - nmessageCallback.keys().foreach { - k => - val f = nmessageCallback.get(k) - if (f.owner == id) { - nmessageCallback.remove(k) - } - } + import scala.collection.JavaConversions._ + val id = Full(act.uniqueId) + nmessageCallback.keys().foreach { + k => + val f = nmessageCallback.get(k) + if (f.owner == id) { + nmessageCallback.remove(k) + } } } } From e3050452410b2d8b64d1499188644ab289ef91ea Mon Sep 17 00:00:00 2001 From: Tmr Date: Thu, 22 Aug 2013 02:13:40 -0700 Subject: [PATCH 0608/1949] Small fix in ListenerManager trait's comments --- web/webkit/src/main/scala/net/liftweb/http/CometActor.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala b/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala index a2df7a1a8f..202727bfe9 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala @@ -160,7 +160,7 @@ object ListenerManager { *
      * case object Tick
      *
    - * object Ticker extends ListenerManager {
    + * object Ticker extends ListenerManager with LiftActor {
      *   import net.liftweb.util.ActorPing
      *
      *   // Set up the initial tick
    
    From 91f79b2630127a19878c9cc54966eccdd7a31094 Mon Sep 17 00:00:00 2001
    From: Matt Farmer 
    Date: Sat, 14 Sep 2013 19:17:16 -0400
    Subject: [PATCH 0609/1949] Update License section of readme to reflect
     committer policy.
    
    ---
     README.md | 3 ++-
     1 file changed, 2 insertions(+), 1 deletion(-)
    
    diff --git a/README.md b/README.md
    index 93b0d1bf3d..811ca5957e 100644
    --- a/README.md
    +++ b/README.md
    @@ -136,7 +136,8 @@ You can find up-to-date information on the [Lift Cookbook](http://cookbook.liftw
     
     ## License
     
    -Lift is open source software released under the **Apache 2.0 license**. You must be a committer with signed committer agreement to submit patches. You can learn more about Lift's committer policy on the Lift website.
    +Lift is open source software released under the **Apache 2.0 license**. Generally speaking, you must be a committer with signed committer agreement to submit significant
    +changes to Lift. We do, however, accept some small changes and bugfixes into Lift from non-committers as outlined above in the Pull Requests section.
     
     ## Continuous Integration
     
    
    From 5a275ca1869812166309a343a9e0ad4f5f4f41d8 Mon Sep 17 00:00:00 2001
    From: Tmr 
    Date: Thu, 22 Aug 2013 02:13:40 -0700
    Subject: [PATCH 0610/1949] Small fix in ListenerManager trait's comments
    
    ---
     web/webkit/src/main/scala/net/liftweb/http/CometActor.scala | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala b/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala
    index 2a14f5794c..10200125d6 100644
    --- a/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala
    +++ b/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala
    @@ -150,7 +150,7 @@ object ListenerManager {
      * 
      * case object Tick
      *
    - * object Ticker extends ListenerManager {
    + * object Ticker extends ListenerManager with LiftActor {
      *   import net.liftweb.util.ActorPing
      *
      *   // Set up the initial tick
    
    From 754fd5b06b70edcc7c2326d91f98baf614d071d8 Mon Sep 17 00:00:00 2001
    From: Mikhail Strebkov 
    Date: Tue, 10 Sep 2013 16:56:39 +0400
    Subject: [PATCH 0611/1949] Fixed parsing json double values in scientific
     notation with e+
    
    ---
     core/json/src/main/scala/net/liftweb/json/JsonParser.scala | 2 +-
     core/json/src/test/scala/net/liftweb/json/ParserBugs.scala | 6 ++++++
     2 files changed, 7 insertions(+), 1 deletion(-)
    
    diff --git a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala
    index 5917b337f0..d31aae4682 100644
    --- a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala
    +++ b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala
    @@ -258,7 +258,7 @@ object JsonParser {
               } else if (c == '.' || c == 'e' || c == 'E') {
                 doubleVal = true
                 s.append(c)
    -          } else if (!(Character.isDigit(c) || c == '.' || c == 'e' || c == 'E' || c == '-')) {
    +          } else if (!(Character.isDigit(c) || c == '.' || c == 'e' || c == 'E' || c == '-' || c == '+')) {
                 wasInt = false
                 buf.back
               } else s.append(c)
    diff --git a/core/json/src/test/scala/net/liftweb/json/ParserBugs.scala b/core/json/src/test/scala/net/liftweb/json/ParserBugs.scala
    index 84531227eb..f4b7bebbe9 100644
    --- a/core/json/src/test/scala/net/liftweb/json/ParserBugs.scala
    +++ b/core/json/src/test/scala/net/liftweb/json/ParserBugs.scala
    @@ -52,6 +52,12 @@ object ParserBugs extends Specification {
           (parse(s) mustEqual json)
       }
     
    +  "Double in scientific notation with + can be parsed" in {
    +    val json = JObject(List(JField("t", JDouble(12.3))))
    +    val s = """{"t" : 1.23e+1}"""
    +    parse(s) mustEqual json
    +  }
    +
       private val discardParser = (p : JsonParser.Parser) => {
          var token: JsonParser.Token = null
          do {
    
    From 49e97cd6fdea3bf137d847f58f4b7b518cd4c7d9 Mon Sep 17 00:00:00 2001
    From: Matt Farmer 
    Date: Sat, 14 Sep 2013 19:17:16 -0400
    Subject: [PATCH 0612/1949] Update License section of readme to reflect
     committer policy.
    
    ---
     README.md | 3 ++-
     1 file changed, 2 insertions(+), 1 deletion(-)
    
    diff --git a/README.md b/README.md
    index 200cfc1b68..b5550e35ec 100644
    --- a/README.md
    +++ b/README.md
    @@ -136,7 +136,8 @@ You can find up-to-date information on the [Lift Cookbook](http://cookbook.liftw
     
     ## License
     
    -Lift is open source software released under the **Apache 2.0 license**. You must be a committer with signed committer agreement to submit patches. You can learn more about Lift's committer policy on the Lift website.
    +Lift is open source software released under the **Apache 2.0 license**. Generally speaking, you must be a committer with signed committer agreement to submit significant
    +changes to Lift. We do, however, accept some small changes and bugfixes into Lift from non-committers as outlined above in the Pull Requests section.
     
     ## Continuous Integration
     
    
    From 370cfc7e673780baaafad4e477a313980a440ac0 Mon Sep 17 00:00:00 2001
    From: Mikhail Strebkov 
    Date: Tue, 10 Sep 2013 16:56:39 +0400
    Subject: [PATCH 0613/1949] Fixed parsing json double values in scientific
     notation with e+
    
    ---
     core/json/src/main/scala/net/liftweb/json/JsonParser.scala | 2 +-
     core/json/src/test/scala/net/liftweb/json/ParserBugs.scala | 6 ++++++
     2 files changed, 7 insertions(+), 1 deletion(-)
    
    diff --git a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala
    index 5917b337f0..d31aae4682 100644
    --- a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala
    +++ b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala
    @@ -258,7 +258,7 @@ object JsonParser {
               } else if (c == '.' || c == 'e' || c == 'E') {
                 doubleVal = true
                 s.append(c)
    -          } else if (!(Character.isDigit(c) || c == '.' || c == 'e' || c == 'E' || c == '-')) {
    +          } else if (!(Character.isDigit(c) || c == '.' || c == 'e' || c == 'E' || c == '-' || c == '+')) {
                 wasInt = false
                 buf.back
               } else s.append(c)
    diff --git a/core/json/src/test/scala/net/liftweb/json/ParserBugs.scala b/core/json/src/test/scala/net/liftweb/json/ParserBugs.scala
    index 84531227eb..f4b7bebbe9 100644
    --- a/core/json/src/test/scala/net/liftweb/json/ParserBugs.scala
    +++ b/core/json/src/test/scala/net/liftweb/json/ParserBugs.scala
    @@ -52,6 +52,12 @@ object ParserBugs extends Specification {
           (parse(s) mustEqual json)
       }
     
    +  "Double in scientific notation with + can be parsed" in {
    +    val json = JObject(List(JField("t", JDouble(12.3))))
    +    val s = """{"t" : 1.23e+1}"""
    +    parse(s) mustEqual json
    +  }
    +
       private val discardParser = (p : JsonParser.Parser) => {
          var token: JsonParser.Token = null
          do {
    
    From 9f1a3a6a616c75699e7b960238978820ca98b05c Mon Sep 17 00:00:00 2001
    From: Matt Farmer 
    Date: Sun, 15 Sep 2013 00:21:18 -0400
    Subject: [PATCH 0614/1949] Nuke ieMode with fire.
    
    The ieMode variable was replaced with legacyIeCompatibilityMode in 2.6,
    and marked for deletion for 3.0. This commit carries out the deletion
    half of that.
    ---
     web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala | 3 ---
     web/webkit/src/main/scala/net/liftweb/http/S.scala           | 3 ---
     2 files changed, 6 deletions(-)
    
    diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala
    index 9456b7ad50..a2448aa1e3 100644
    --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala
    +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala
    @@ -748,9 +748,6 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String,
         override private[liftweb] def magicSessionVar_? = true
       }
     
    -  @deprecated("Use legacyIeCompatibilityMode for legacy IE detection instead. This will be removed in Lift 3.0.", "2.6")
    -  val ieMode = legacyIeCompatibilityMode
    -
       def terminateHint {
         if (_running_?) {
           markedForTermination = true;
    diff --git a/web/webkit/src/main/scala/net/liftweb/http/S.scala b/web/webkit/src/main/scala/net/liftweb/http/S.scala
    index 1bf03e0cbc..29738f3e17 100644
    --- a/web/webkit/src/main/scala/net/liftweb/http/S.scala
    +++ b/web/webkit/src/main/scala/net/liftweb/http/S.scala
    @@ -587,9 +587,6 @@ trait S extends HasParams with Loggable with UserAgentCalculator {
        */
       def legacyIeCompatibilityMode: Boolean = session.map(_.legacyIeCompatibilityMode.is) openOr false // LiftRules.calcIEMode()
     
    -  @deprecated("Use legacyIeCompatibilityMode for legacy IE detection instead, or use S.isIE* for general IE detection. This will be removed in Lift 3.0.", "2.6")
    -  def ieMode = legacyIeCompatibilityMode
    -
       /**
        * Get the current instance of HtmlProperties
        */
    
    From 935ee8b5d3350c378560e419867f41006f064aa3 Mon Sep 17 00:00:00 2001
    From: Matt Farmer 
    Date: Sun, 15 Sep 2013 00:22:04 -0400
    Subject: [PATCH 0615/1949] Correct omissions in scaladoc for
     legacyIeCompatibilityMode.
    
    ---
     web/webkit/src/main/scala/net/liftweb/http/S.scala | 4 ++--
     1 file changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/web/webkit/src/main/scala/net/liftweb/http/S.scala b/web/webkit/src/main/scala/net/liftweb/http/S.scala
    index 29738f3e17..be6edf6188 100644
    --- a/web/webkit/src/main/scala/net/liftweb/http/S.scala
    +++ b/web/webkit/src/main/scala/net/liftweb/http/S.scala
    @@ -576,9 +576,9 @@ trait S extends HasParams with Loggable with UserAgentCalculator {
        * special accomodations for IE 6 / 7 / 8 compatibility.
        *
        * @return true if this response should be rendered in
    -   * IE6/IE7 compatibility mode.
    +   * IE 6/7/8 compatibility mode.
        *
    -   * @see LiftSession.ieMode
    +   * @see LiftSession.legacyIeCompatibilityMode
        * @see LiftRules.calcIEMode
        * @see Req.isIE6
        * @see Req.isIE7
    
    From d1413ea000ff9da0b05d89eb671bb13f1324d667 Mon Sep 17 00:00:00 2001
    From: Diego Medina 
    Date: Sat, 5 Oct 2013 00:48:28 -0400
    Subject: [PATCH 0616/1949] added a target jvm of java 1.6, as I'm building
     with java 1.7
    
    ---
     build.sbt | 2 ++
     1 file changed, 2 insertions(+)
    
    diff --git a/build.sbt b/build.sbt
    index d753e6d813..2e1eebe2ef 100644
    --- a/build.sbt
    +++ b/build.sbt
    @@ -14,6 +14,8 @@ organizationName in ThisBuild      := "WorldWide Conferencing, LLC"
     
     crossScalaVersions in ThisBuild    := Seq("2.10.0", "2.9.2", "2.9.1-1", "2.9.1")
     
    +javacOptions ++= Seq("-source", "1.5")
    +
     libraryDependencies in ThisBuild <++= scalaVersion {sv => Seq(specs2(sv), scalacheck) }
     
     // Settings for Sonatype compliance
    
    From c7a02391eda995b17c05bde94bceae49a1780fab Mon Sep 17 00:00:00 2001
    From: xuwei-k <6b656e6a69@gmail.com>
    Date: Wed, 16 Oct 2013 02:13:14 +0900
    Subject: [PATCH 0617/1949] override JObject#hashCode
    
    ---
     core/json/src/main/scala/net/liftweb/json/JsonAST.scala | 4 +++-
     1 file changed, 3 insertions(+), 1 deletion(-)
    
    diff --git a/core/json/src/main/scala/net/liftweb/json/JsonAST.scala b/core/json/src/main/scala/net/liftweb/json/JsonAST.scala
    index bb385655ad..9a8d402544 100644
    --- a/core/json/src/main/scala/net/liftweb/json/JsonAST.scala
    +++ b/core/json/src/main/scala/net/liftweb/json/JsonAST.scala
    @@ -387,9 +387,11 @@ object JsonAST {
         def values = Map() ++ obj.map(_.values : (String, Any))
     
         override def equals(that: Any): Boolean = that match {
    -      case o: JObject => Set(obj.toArray: _*) == Set(o.obj.toArray: _*)
    +      case o: JObject => obj.toSet == o.obj.toSet
           case _ => false
         }
    +
    +    override def hashCode = obj.toSet[JField].hashCode
       }
       case class JArray(arr: List[JValue]) extends JValue {
         type Values = List[Any]
    
    From 8f753cf3a32936baec1ea9f772b6a1834911a647 Mon Sep 17 00:00:00 2001
    From: xuwei-k <6b656e6a69@gmail.com>
    Date: Wed, 16 Oct 2013 02:13:39 +0900
    Subject: [PATCH 0618/1949] JObject equals hashCode test
    
    ---
     .../json/src/test/scala/net/liftweb/json/JsonAstSpec.scala | 7 +++++++
     1 file changed, 7 insertions(+)
    
    diff --git a/core/json/src/test/scala/net/liftweb/json/JsonAstSpec.scala b/core/json/src/test/scala/net/liftweb/json/JsonAstSpec.scala
    index 044ddef7f7..6ef44acfc1 100644
    --- a/core/json/src/test/scala/net/liftweb/json/JsonAstSpec.scala
    +++ b/core/json/src/test/scala/net/liftweb/json/JsonAstSpec.scala
    @@ -149,6 +149,13 @@ object JsonAstSpec extends Specification with JValueGen with ScalaCheck {
         check(forAll(anyReplacement))
       }
     
    +  "equals hashCode" in check{ x: JObject =>
    +    val y = JObject(scala.util.Random.shuffle(x.obj))
    +
    +    x must_== y
    +    x.## must_== y.##
    +  }
    +
       private def reorderFields(json: JValue) = json map {
         case JObject(xs) => JObject(xs.reverse)
         case x => x
    
    From 735930986039deb02996b386f03ea370e8f46099 Mon Sep 17 00:00:00 2001
    From: xuwei-k <6b656e6a69@gmail.com>
    Date: Wed, 16 Oct 2013 08:07:25 +0900
    Subject: [PATCH 0619/1949] Update contributors
    
    ---
     contributors.md | 6 ++++++
     1 file changed, 6 insertions(+)
    
    diff --git a/contributors.md b/contributors.md
    index 37e75e67da..7e1a2c1ccd 100644
    --- a/contributors.md
    +++ b/contributors.md
    @@ -180,3 +180,9 @@ Viktor Hedefalk
     ### Email: ###
     hedefalk @@ gmail .. com
     
    +### Name: ###
    +Kenji Yoshida
    +
    +### Email: ###
    +6b656e6a69 at gmail dot com
    +
    
    From a2c2939a4edd1bb82a40d2dbcd22367056a1b299 Mon Sep 17 00:00:00 2001
    From: websterc 
    Date: Sun, 3 Nov 2013 07:20:35 -0800
    Subject: [PATCH 0620/1949] Improve performance of decomposing case classes to
     Json
    
    ---
     contributors.md                                            | 7 +++++++
     core/json/src/main/scala/net/liftweb/json/Extraction.scala | 6 +++---
     2 files changed, 10 insertions(+), 3 deletions(-)
    
    diff --git a/contributors.md b/contributors.md
    index 7e1a2c1ccd..8ea731487d 100644
    --- a/contributors.md
    +++ b/contributors.md
    @@ -186,3 +186,10 @@ Kenji Yoshida
     ### Email: ###
     6b656e6a69 at gmail dot com
     
    +### Name: ###
    +Christopher Webster
    +
    +### Email: ###
    +cwebster93 at gmail .. com
    +
    +
    diff --git a/core/json/src/main/scala/net/liftweb/json/Extraction.scala b/core/json/src/main/scala/net/liftweb/json/Extraction.scala
    index e354675ad7..c912e4f012 100644
    --- a/core/json/src/main/scala/net/liftweb/json/Extraction.scala
    +++ b/core/json/src/main/scala/net/liftweb/json/Extraction.scala
    @@ -84,9 +84,9 @@ object Extraction {
             case x if (x.getClass.isArray) => JArray(x.asInstanceOf[Array[_]].toList map decompose)
             case x: Option[_] => x.flatMap[JValue] { y => Some(decompose(y)) }.getOrElse(JNothing)
             case x => 
    -          val constructorArgs = primaryConstructorArgs(x.getClass)
    -          constructorArgs.collect { case (name, _) if Reflection.hasDeclaredField(x.getClass, name) =>
    -            val f = x.getClass.getDeclaredField(name)
    +          val fields = x.getClass.getDeclaredFields.map(field => (field.getName, field)).toMap
    +          val constructorArgs = primaryConstructorArgs(x.getClass).map{ case (name, _) => (name,fields.get(name)) }
    +          constructorArgs.collect { case (name, Some(f)) =>
                 f.setAccessible(true)
                 JField(unmangleName(name), decompose(f get x))
               } match {
    
    From 1ccf205f16e24927a51e485898577131df0e4ba5 Mon Sep 17 00:00:00 2001
    From: xuwei-k <6b656e6a69@gmail.com>
    Date: Wed, 16 Oct 2013 02:13:14 +0900
    Subject: [PATCH 0621/1949] override JObject#hashCode
    
    ---
     core/json/src/main/scala/net/liftweb/json/JsonAST.scala | 4 +++-
     1 file changed, 3 insertions(+), 1 deletion(-)
    
    diff --git a/core/json/src/main/scala/net/liftweb/json/JsonAST.scala b/core/json/src/main/scala/net/liftweb/json/JsonAST.scala
    index bb385655ad..9a8d402544 100644
    --- a/core/json/src/main/scala/net/liftweb/json/JsonAST.scala
    +++ b/core/json/src/main/scala/net/liftweb/json/JsonAST.scala
    @@ -387,9 +387,11 @@ object JsonAST {
         def values = Map() ++ obj.map(_.values : (String, Any))
     
         override def equals(that: Any): Boolean = that match {
    -      case o: JObject => Set(obj.toArray: _*) == Set(o.obj.toArray: _*)
    +      case o: JObject => obj.toSet == o.obj.toSet
           case _ => false
         }
    +
    +    override def hashCode = obj.toSet[JField].hashCode
       }
       case class JArray(arr: List[JValue]) extends JValue {
         type Values = List[Any]
    
    From 5ae3e31214b714586644282768d6bbd7a9956091 Mon Sep 17 00:00:00 2001
    From: xuwei-k <6b656e6a69@gmail.com>
    Date: Wed, 16 Oct 2013 02:13:39 +0900
    Subject: [PATCH 0622/1949] JObject equals hashCode test
    
    ---
     .../json/src/test/scala/net/liftweb/json/JsonAstSpec.scala | 7 +++++++
     1 file changed, 7 insertions(+)
    
    diff --git a/core/json/src/test/scala/net/liftweb/json/JsonAstSpec.scala b/core/json/src/test/scala/net/liftweb/json/JsonAstSpec.scala
    index 044ddef7f7..6ef44acfc1 100644
    --- a/core/json/src/test/scala/net/liftweb/json/JsonAstSpec.scala
    +++ b/core/json/src/test/scala/net/liftweb/json/JsonAstSpec.scala
    @@ -149,6 +149,13 @@ object JsonAstSpec extends Specification with JValueGen with ScalaCheck {
         check(forAll(anyReplacement))
       }
     
    +  "equals hashCode" in check{ x: JObject =>
    +    val y = JObject(scala.util.Random.shuffle(x.obj))
    +
    +    x must_== y
    +    x.## must_== y.##
    +  }
    +
       private def reorderFields(json: JValue) = json map {
         case JObject(xs) => JObject(xs.reverse)
         case x => x
    
    From 92d4698729a867d13ab70650964d54f7a871ba98 Mon Sep 17 00:00:00 2001
    From: xuwei-k <6b656e6a69@gmail.com>
    Date: Wed, 16 Oct 2013 08:07:25 +0900
    Subject: [PATCH 0623/1949] Update contributors
    
    ---
     contributors.md | 6 ++++++
     1 file changed, 6 insertions(+)
    
    diff --git a/contributors.md b/contributors.md
    index 37e75e67da..7e1a2c1ccd 100644
    --- a/contributors.md
    +++ b/contributors.md
    @@ -180,3 +180,9 @@ Viktor Hedefalk
     ### Email: ###
     hedefalk @@ gmail .. com
     
    +### Name: ###
    +Kenji Yoshida
    +
    +### Email: ###
    +6b656e6a69 at gmail dot com
    +
    
    From c46656f811bc5dddbdbce977f8e12bd269a7a1b0 Mon Sep 17 00:00:00 2001
    From: Diego Medina 
    Date: Wed, 6 Nov 2013 23:51:27 -0500
    Subject: [PATCH 0624/1949] Revert "Removed SimpleController and moved
     MVCHelper to package"
    
    This reverts commit 89822dcdee387cdd2cf5f51989d218b8a41740d3.
    ---
     .../scala/net/liftweb/http/MVCHelper.scala    | 230 ++++++++++++++++++
     .../net/liftweb/http/SimpleController.scala   |  61 +++++
     2 files changed, 291 insertions(+)
     create mode 100644 web/webkit/src/main/scala/net/liftweb/http/MVCHelper.scala
     create mode 100644 web/webkit/src/main/scala/net/liftweb/http/SimpleController.scala
    
    diff --git a/web/webkit/src/main/scala/net/liftweb/http/MVCHelper.scala b/web/webkit/src/main/scala/net/liftweb/http/MVCHelper.scala
    new file mode 100644
    index 0000000000..d010deb165
    --- /dev/null
    +++ b/web/webkit/src/main/scala/net/liftweb/http/MVCHelper.scala
    @@ -0,0 +1,230 @@
    +/*
    + * Copyright 2010-2011 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 net.liftweb.common._
    +import net.liftweb.util._
    +import scala.xml.{NodeSeq, Node}
    +
    +/**
    + * Mix this trait into a class to provide support for
    + * MVC style coding.  Each controller line is defined as:
    + * 
    + * serve {
    + *   case "user" :: User(user) :: _ => "#name" #> user.firstName
    + * }
    + * 
    + * + * The above code matches /user/4, loads the user with primary + * key 4 from the database, then applies the transform to the /user.html + * template replacing the node with the id "name" with the firstName of + * the user + */ +trait MVCHelper extends LiftRules.DispatchPF { + /** + * The partial function to match a request to a response + */ + protected type MVCMatch = PartialFunction[List[String], MVCResponse] + + /** + * Serve an MVC page based on matching the path + */ + protected def serve(pf: MVCMatch) {_dispatch ::= pf} + + @volatile private var _dispatch: List[MVCMatch] = Nil + + private lazy val nonDevDispatch = _dispatch.reverse + + private object curRequest extends RequestVar[Req](null) { + override def __nameSalt = Helpers.nextFuncName + } + + private object curSession extends + RequestVar[LiftSession](S.session openOr + LiftRules.statelessSession. + vend.apply(curRequest.is)) { + override def __nameSalt = Helpers.nextFuncName + } + + private def dispatch: List[MVCMatch] = + if (Props.devMode) _dispatch.reverse else nonDevDispatch + + /** + * Is the Rest helper defined for a given request + */ + def isDefinedAt(in: Req) = { + S.session match { + case Full(_) => dispatch.find(_.isDefinedAt(in.path.partPath)).isDefined + + case _ => + curRequest.set(in) + S.init(in, curSession.is) { + dispatch.find(_.isDefinedAt(in.path.partPath)).isDefined + } + } + } + + /** + * Apply the Rest helper + */ + def apply(in: Req): () => Box[LiftResponse] = { + val path = in.path.partPath + S.session match { + case Full(_) => { + val resp = dispatch.find(_.isDefinedAt(path)).get. + apply(path).toResponse + + () => resp + } + + case _ => + S.init(in, curSession.is) { + val resp = dispatch.find(_.isDefinedAt(path)).get. + apply(path).toResponse + + () => resp + } + } + } + + /** + * A trait that holds a response for the MVCHelper. Conversions + * exist from Unit (just serve the template), + * CssBindFunc (do the substitution on the template), + * NodeSeq (run the template), + * LiftResponse (send the response back), + * or Box or Option of any of the above. + * + */ + protected sealed trait MVCResponse { + def toResponse: Box[LiftResponse] + } + + private def templateForPath(req: Req): Box[NodeSeq] = { + + def tryIt(path: List[String]): Box[NodeSeq] = path match { + case Nil => Empty + case xs => Templates(path) match { + case ret@ Full(_) => ret + case _ => tryIt(path.dropRight(1)) + } + } + + tryIt(req.path.partPath) + } + + object MVCResponse { + implicit def unitToResponse(unit: Unit): MVCResponse = + new MVCResponse { + val toResponse: Box[LiftResponse] = + for { + session <- S.session + req <- S.request + template <- templateForPath(req) + resp <- session.processTemplate(Full(template), + req, req.path, 200) + } yield resp + } + + implicit def bindToResponse(bind: CssBindFunc): MVCResponse = + new MVCResponse { + val toResponse: Box[LiftResponse] = + for { + session <- S.session + req <- S.request + template <- templateForPath(req) + resp <- session.processTemplate(Full(bind(template)), + req, req.path, 200) + } yield resp + } + + implicit def nsToResponse(nodes: Seq[Node]): MVCResponse = { + new MVCResponse { + val toResponse: Box[LiftResponse] = + for { + session <- S.session + req <- S.request + resp <- session.processTemplate(Full(nodes), + req, req.path, 200) + } yield resp + } + } + + implicit def respToResponse(resp: LiftResponse): MVCResponse = + new MVCResponse { + val toResponse: Box[LiftResponse] = Full(resp) + } + + + implicit def boxThinginy[T](box: Box[T])(implicit f: T => MVCResponse): MVCResponse = new MVCResponse { + val toResponse: Box[LiftResponse] = boxToResp(box)(f) + } + + implicit def optionThinginy[T](box: Option[T])(implicit f: T => MVCResponse): MVCResponse = new MVCResponse { + val toResponse: Box[LiftResponse] = boxToResp(box)(f) + } + } + + /** + * Turn a Box[T] into the return type expected by + * DispatchPF. Note that this method will return + * messages from Failure() and return codes and messages + * from ParamFailure[Int[(msg, _, _, code) + */ + protected implicit def boxToResp[T](in: Box[T]) + (implicit c: T => MVCResponse): Box[LiftResponse] = + in match { + case Full(v) => c(v).toResponse + case e: EmptyBox => emptyToResp(e) + } + + /** + * Convert an Empty into an appropriate LiftResponse. In the + * case of Failure, you may want to display a particular error + * message to the user. + */ + protected def emptyToResp(eb: EmptyBox): Box[LiftResponse] = + eb match { + case ParamFailure(msg, _, _, code: Int) => + Full(InMemoryResponse(msg.getBytes("UTF-8"), + ("Content-Type" -> + "text/plain; charset=utf-8") :: + Nil, Nil, code)) + + case Failure(msg, _, _) => + Full(NotFoundResponse(msg)) + + case _ => Empty + } + + /** + * Validate what, if it passes validation, then + * redirect to the new URL, else display the messages + * using S.error and redisplay the current page. + */ + protected def saveRedir(what: + {def validate: List[FieldError]; + def save(): Boolean}, + where: String) = () => { + what.validate match { + case Nil => what.save(); S.redirectTo(where) + case xs => S.error(xs) + } + } +} + diff --git a/web/webkit/src/main/scala/net/liftweb/http/SimpleController.scala b/web/webkit/src/main/scala/net/liftweb/http/SimpleController.scala new file mode 100644 index 0000000000..5552d5b03f --- /dev/null +++ b/web/webkit/src/main/scala/net/liftweb/http/SimpleController.scala @@ -0,0 +1,61 @@ +/* + * Copyright 2007-2011 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 scala.collection.immutable.TreeMap +import net.liftweb.common._ +import net.liftweb.util._ +import provider._ + +/** + * The base trait of Controllers that handle pre-view requests + */ +trait SimpleController + { + def request: Req + + def httpRequest: HTTPRequest + + def param(name: String): Box[String] = { + request.params.get(name) match { + case None => Empty + case Some(nl) => nl.take(1) match { + case Nil => Empty + case l => Full(l.head) + } + } + } + + def post_? : Boolean = request.post_? + + def get(name: String): Box[String] = + httpRequest.session.attribute(name) match { + case null => Empty + case n: String => Full(n) + case _ => Empty + } + + def set(name: String, value: String) { + httpRequest.session.setAttribute(name, value) + } + + def unset(name: String) { + httpRequest.session.removeAttribute(name) + } +} + From e13030efcfbb41ff3ca17a93241882f5ac3893a9 Mon Sep 17 00:00:00 2001 From: Diego Medina Date: Wed, 6 Nov 2013 23:55:27 -0500 Subject: [PATCH 0625/1949] Revert "Added MVCHelper to package" This reverts commit bc7155b182e4b11bab0518b38b2ce9fe8abaf81b. --- .../net/liftweb/http/js/jquery/JqJsCmds.scala | 15 ++ .../net/liftweb/http/mvc/MVCHelper.scala | 236 ------------------ 2 files changed, 15 insertions(+), 236 deletions(-) delete mode 100644 web/webkit/src/main/scala/net/liftweb/http/mvc/MVCHelper.scala diff --git a/web/webkit/src/main/scala/net/liftweb/http/js/jquery/JqJsCmds.scala b/web/webkit/src/main/scala/net/liftweb/http/js/jquery/JqJsCmds.scala index 50ff77f2fb..3c488f0dd8 100755 --- a/web/webkit/src/main/scala/net/liftweb/http/js/jquery/JqJsCmds.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/js/jquery/JqJsCmds.scala @@ -619,6 +619,21 @@ object JqJsCmds { * Requires the jQuery blockUI plugin */ class ModalDialog(html: NodeSeq, css: Box[JsObj]) extends JsCmd { + /* + private def contentAsJsStr = { + val w = new java.io.StringWriter + + S.htmlProperties. + htmlWriter(Group(S.session. + map(s => + s.fixHtml(s.processSurroundAndInclude("Modal Dialog", + html))). + openOr(html)), + w) + w.toString.encJs + } +*/ + val toJsCmd = fixHtmlCmdFunc("inline", html){str => "jQuery.blockUI({ message: " + str + (css.map(", css: " + _.toJsCmd + " ").openOr("")) + "});"} diff --git a/web/webkit/src/main/scala/net/liftweb/http/mvc/MVCHelper.scala b/web/webkit/src/main/scala/net/liftweb/http/mvc/MVCHelper.scala deleted file mode 100644 index aa4631ac4d..0000000000 --- a/web/webkit/src/main/scala/net/liftweb/http/mvc/MVCHelper.scala +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright 2010-2011 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.http.mvc - -import net.liftweb.common._ -import net.liftweb.util._ -import scala.xml.{NodeSeq, Node} -import net.liftweb.http._ -import net.liftweb.common.Full -import net.liftweb.common.Full -import net.liftweb.common.Full -import net.liftweb.common.Full -import net.liftweb.http.InMemoryResponse -import net.liftweb.common.Full - -/** - * Mix this trait into a class to provide support for - * MVC style coding. Each controller line is defined as: - *
    - * serve {
    - *   case "user" :: User(user) :: _ => "#name" #> user.firstName
    - * }
    - * 
    - * - * The above code matches /user/4, loads the user with primary - * key 4 from the database, then applies the transform to the /user.html - * template replacing the node with the id "name" with the firstName of - * the user - */ -trait MVCHelper extends LiftRules.DispatchPF { - /** - * The partial function to match a request to a response - */ - protected type MVCMatch = PartialFunction[List[String], MVCResponse] - - /** - * Serve an MVC page based on matching the path - */ - protected def serve(pf: MVCMatch) {_dispatch ::= pf} - - @volatile private var _dispatch: List[MVCMatch] = Nil - - private lazy val nonDevDispatch = _dispatch.reverse - - private object curRequest extends RequestVar[Req](null) { - override def __nameSalt = Helpers.nextFuncName - } - - private object curSession extends - RequestVar[LiftSession](S.session openOr - LiftRules.statelessSession. - vend.apply(curRequest.is)) { - override def __nameSalt = Helpers.nextFuncName - } - - private def dispatch: List[MVCMatch] = - if (Props.devMode) _dispatch.reverse else nonDevDispatch - - /** - * Is the Rest helper defined for a given request - */ - def isDefinedAt(in: Req) = { - S.session match { - case Full(_) => dispatch.find(_.isDefinedAt(in.path.partPath)).isDefined - - case _ => - curRequest.set(in) - S.init(in, curSession.is) { - dispatch.find(_.isDefinedAt(in.path.partPath)).isDefined - } - } - } - - /** - * Apply the Rest helper - */ - def apply(in: Req): () => Box[LiftResponse] = { - val path = in.path.partPath - S.session match { - case Full(_) => { - val resp = dispatch.find(_.isDefinedAt(path)).get. - apply(path).toResponse - - () => resp - } - - case _ => - S.init(in, curSession.is) { - val resp = dispatch.find(_.isDefinedAt(path)).get. - apply(path).toResponse - - () => resp - } - } - } - - /** - * A trait that holds a response for the MVCHelper. Conversions - * exist from Unit (just serve the template), - * CssBindFunc (do the substitution on the template), - * NodeSeq (run the template), - * LiftResponse (send the response back), - * or Box or Option of any of the above. - * - */ - protected sealed trait MVCResponse { - def toResponse: Box[LiftResponse] - } - - private def templateForPath(req: Req): Box[NodeSeq] = { - - def tryIt(path: List[String]): Box[NodeSeq] = path match { - case Nil => Empty - case xs => Templates(path) match { - case ret@ Full(_) => ret - case _ => tryIt(path.dropRight(1)) - } - } - - tryIt(req.path.partPath) - } - - object MVCResponse { - implicit def unitToResponse(unit: Unit): MVCResponse = - new MVCResponse { - val toResponse: Box[LiftResponse] = - for { - session <- S.session - req <- S.request - template <- templateForPath(req) - resp <- session.processTemplate(Full(template), - req, req.path, 200) - } yield resp - } - - implicit def bindToResponse(bind: CssBindFunc): MVCResponse = - new MVCResponse { - val toResponse: Box[LiftResponse] = - for { - session <- S.session - req <- S.request - template <- templateForPath(req) - resp <- session.processTemplate(Full(bind(template)), - req, req.path, 200) - } yield resp - } - - implicit def nsToResponse(nodes: Seq[Node]): MVCResponse = { - new MVCResponse { - val toResponse: Box[LiftResponse] = - for { - session <- S.session - req <- S.request - resp <- session.processTemplate(Full(nodes), - req, req.path, 200) - } yield resp - } - } - - implicit def respToResponse(resp: LiftResponse): MVCResponse = - new MVCResponse { - val toResponse: Box[LiftResponse] = Full(resp) - } - - - implicit def boxThinginy[T](box: Box[T])(implicit f: T => MVCResponse): MVCResponse = new MVCResponse { - val toResponse: Box[LiftResponse] = boxToResp(box)(f) - } - - implicit def optionThinginy[T](box: Option[T])(implicit f: T => MVCResponse): MVCResponse = new MVCResponse { - val toResponse: Box[LiftResponse] = boxToResp(box)(f) - } - } - - /** - * Turn a Box[T] into the return type expected by - * DispatchPF. Note that this method will return - * messages from Failure() and return codes and messages - * from ParamFailure[Int[(msg, _, _, code) - */ - protected implicit def boxToResp[T](in: Box[T]) - (implicit c: T => MVCResponse): Box[LiftResponse] = - in match { - case Full(v) => c(v).toResponse - case e: EmptyBox => emptyToResp(e) - } - - /** - * Convert an Empty into an appropriate LiftResponse. In the - * case of Failure, you may want to display a particular error - * message to the user. - */ - protected def emptyToResp(eb: EmptyBox): Box[LiftResponse] = - eb match { - case ParamFailure(msg, _, _, code: Int) => - Full(InMemoryResponse(msg.getBytes("UTF-8"), - ("Content-Type" -> - "text/plain; charset=utf-8") :: - Nil, Nil, code)) - - case Failure(msg, _, _) => - Full(NotFoundResponse(msg)) - - case _ => Empty - } - - /** - * Validate what, if it passes validation, then - * redirect to the new URL, else display the messages - * using S.error and redisplay the current page. - */ - protected def saveRedir(what: - {def validate: List[FieldError]; - def save(): Boolean}, - where: String) = () => { - what.validate match { - case Nil => what.save(); S.redirectTo(where) - case xs => S.error(xs) - } - } -} - From 021c43db4c7585a54de59f280c3837c8b6538786 Mon Sep 17 00:00:00 2001 From: Diego Medina Date: Wed, 6 Nov 2013 23:56:48 -0500 Subject: [PATCH 0626/1949] Revert "Deleted JX" This reverts commit 849a376b156eb878711bbd422e0619b8ef79c742. Conflicts: web/webkit/src/main/scala/net/liftweb/http/js/Jx.scala --- .../scala/net/liftweb/mapper/MappedInt.scala | 1 + .../net/liftweb/http/jquery/JqSHtml.scala | 68 +++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100755 web/webkit/src/main/scala/net/liftweb/http/jquery/JqSHtml.scala diff --git a/persistence/mapper/src/main/scala/net/liftweb/mapper/MappedInt.scala b/persistence/mapper/src/main/scala/net/liftweb/mapper/MappedInt.scala index 91a06ddcf4..872b19f641 100644 --- a/persistence/mapper/src/main/scala/net/liftweb/mapper/MappedInt.scala +++ b/persistence/mapper/src/main/scala/net/liftweb/mapper/MappedInt.scala @@ -26,6 +26,7 @@ import java.util.Date import net.liftweb.http._ import reflect.runtime.universe._ import net.liftweb.json._ +import net.liftweb.http.jquery.{JqSHtml} import xml.{Text, NodeSeq} import js._ diff --git a/web/webkit/src/main/scala/net/liftweb/http/jquery/JqSHtml.scala b/web/webkit/src/main/scala/net/liftweb/http/jquery/JqSHtml.scala new file mode 100755 index 0000000000..09cafd5101 --- /dev/null +++ b/web/webkit/src/main/scala/net/liftweb/http/jquery/JqSHtml.scala @@ -0,0 +1,68 @@ +/* + * Copyright 2007-2011 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 +package jquery + +import net.liftweb.util.Helpers._ +import net.liftweb.http.js._ +import net.liftweb.http.js.jquery._ +import JqJsCmds._ + +/** + * This contains Html artifacts that are heavily relying on JQuery + */ +@deprecated("This contains Html artifacts that are heavily relying on JQuery", "2.3") +object JqSHtml { + def fadeOutErrors(duration: TimeSpan, fadeTime: TimeSpan): JsCmd = { + FadeOut(LiftRules.noticesContainerId + "_error", duration, fadeTime) + } + + def fadeOutWarnings(duration: TimeSpan, fadeTime: TimeSpan): JsCmd = { + FadeOut(LiftRules.noticesContainerId + "_warn", duration, fadeTime) + } + + def fadeOutNotices(duration: TimeSpan, fadeTime: TimeSpan): JsCmd = { + FadeOut(LiftRules.noticesContainerId + "_notice", duration, fadeTime) + } + + /** + * Generate a JavaScript FadeOut command for the Errors screen real estate using + * the default prefade duration and fade time from JsRules + */ + def fadeOutErrors(): JsCmd = { + FadeOut(LiftRules.noticesContainerId + "_error", JsRules.prefadeDuration, JsRules.fadeTime) + } + + /** + * Generate a JavaScript FadeOut command for the Warnings screen real estate using + * the default prefade duration and fade time from JsRules + */ + def fadeOutWarnings(): JsCmd = { + FadeOut(LiftRules.noticesContainerId + "_warn", JsRules.prefadeDuration, JsRules.fadeTime) + } + + /** + * Generate a JavaScript FadeOut command for the Notices screen real estate using + * the default prefade duration and fade time from JsRules + */ + def fadeOutNotices(): JsCmd = { + FadeOut(LiftRules.noticesContainerId + "_notice", JsRules.prefadeDuration, JsRules.fadeTime) + } + +} + From 0c18c58acaad1cbf568114692cc3b4f5346eb854 Mon Sep 17 00:00:00 2001 From: Diego Medina Date: Thu, 7 Nov 2013 00:04:15 -0500 Subject: [PATCH 0627/1949] Revert "Refactored main lift processing" This reverts commit f0731f24d60d92d57128e5fd33cb9c2946ef5216. Conflicts: core/common/src/main/scala/net/liftweb/common/Box.scala --- .../main/scala/net/liftweb/common/Box.scala | 11 + .../db/src/main/scala/net/liftweb/db/DB.scala | 6 +- .../net/liftweb/http/DefaultRoutines.scala | 9 +- .../scala/net/liftweb/http/LiftServlet.scala | 933 ++++++++++++++++++ .../main/scala/net/liftweb/http/js/Jx.scala | 2 + .../liftweb/http/provider/HTTPProvider.scala | 1 - .../main/scala/net/liftweb/sitemap/Loc.scala | 3 + 7 files changed, 957 insertions(+), 8 deletions(-) create mode 100644 web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index ff562b35cf..25a7283ad9 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -98,6 +98,17 @@ sealed trait BoxTrait { case _ => Empty } + /** + * This method allows one to encapsulate any object in a Box in a null-safe manner, + * treating null values to Empty. This is a parallel method to + * the Scala Option's apply method. Note that the apply method is overloaded + * and it's much, much better to use legacyNullTest in this case. + * + * @return Full(in) if in is not null; Empty otherwise + */ + @deprecated("Use legacyNullTest", "2.5") + def apply[T](in: T): Box[T] = legacyNullTest(in) + /** * Apply the specified PartialFunction to the specified value and return the result * in a Full Box; if the pf is undefined at that point return Empty. diff --git a/persistence/db/src/main/scala/net/liftweb/db/DB.scala b/persistence/db/src/main/scala/net/liftweb/db/DB.scala index 2be07f0799..bd3b4423e4 100644 --- a/persistence/db/src/main/scala/net/liftweb/db/DB.scala +++ b/persistence/db/src/main/scala/net/liftweb/db/DB.scala @@ -147,12 +147,10 @@ trait DB extends Loggable { * perform this function after transaction has ended. This is helpful for sending messages to Actors after we know * a transaction has been either committed or rolled back */ - - /* @deprecated("Use appendPostTransaction {committed => ...}", "2.4") + @deprecated("Use appendPostTransaction {committed => ...}", "2.4") def performPostCommit(f: => Unit) { postCommit = (() => f) :: postCommit } - */ // remove thread-local association private def clearThread(success: Boolean): Unit = { @@ -330,12 +328,10 @@ trait DB extends Loggable { /** * Append a function to be invoked after the transaction has ended for the given connection identifier */ - /* @deprecated("Use appendPostTransaction (name, {committed => ...})", "2.4") def appendPostFunc(name: ConnectionIdentifier, func: () => Unit) { appendPostTransaction(name, dontUse => func()) } - */ /** * Append a function to be invoked after the transaction on the specified connection identifier has ended. diff --git a/web/webkit/src/main/scala/net/liftweb/http/DefaultRoutines.scala b/web/webkit/src/main/scala/net/liftweb/http/DefaultRoutines.scala index e22393ee85..2b9c2bc342 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/DefaultRoutines.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/DefaultRoutines.scala @@ -53,12 +53,16 @@ object DefaultRoutines { private def resBundleFor(loc: Locale, path: List[String]): Box[ResourceBundle] = resourceMap.synchronized { val key = loc.toString -> path - resourceMap.get(key).getOrElse { + resourceMap.get(key) match { + case Full(x) => x + case _ => { val res = rawResBundle(loc, path) if (!Props.devMode) resourceMap(key) = res res } + } + } /** * Returns the resources for the current request. In development @@ -95,7 +99,8 @@ object DefaultRoutines { val cb = for { req <- S.originalRequest - path = req.path.partPath.dropRight(1) ::: req.path.partPath.takeRight(1).map(s => "_resources_"+s) + path = req.path.partPath.dropRight(1) ::: + req.path.partPath.takeRight(1).map(s => "_resources_"+s) bundle <- resBundleFor(loc, path) } yield bundle diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala new file mode 100644 index 0000000000..58d1f96625 --- /dev/null +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala @@ -0,0 +1,933 @@ +/* + * Copyright 2007-2011 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 xml.{Node, NodeSeq, Group} + +import common._ +import actor._ +import util._ +import util.Helpers._ +import js._ +import auth._ +import provider._ +import json.JsonAST.JValue + +/** + * Wrap a LiftResponse and cache the result to avoid computing the actual response + * more than once + */ +private [http] case class CachedResponse(wrapped: LiftResponse) extends LiftResponse { + private val _cachedResponse = wrapped.toResponse + + def toResponse = _cachedResponse + + // Should we retry request processing + def failed_? = _cachedResponse.code >= 500 && _cachedResponse.code < 600 +} + +class LiftServlet extends Loggable { + private var servletContext: HTTPContext = null + + def this(ctx: HTTPContext) = { + this () + this.servletContext = ctx + } + + def getContext: HTTPContext = servletContext + + def destroy = { + try { + LiftRules.ending = true + + tryo { + SessionMaster.shutDownAllSessions() + } + + val cur = millis + + // wait 10 seconds or until the request count is zero + while (LiftRules.reqCnt.get > 0 && (millis - cur) < 10000L) { + Thread.sleep(20) + } + + tryo { + Schedule.shutdown + } + tryo { + LAScheduler.shutdown() + } + + tryo { + LAPinger.shutdown + } + + LiftRules.runUnloadHooks() + logger.debug("Destroyed Lift handler.") + // super.destroy + } catch { + case e: Exception => logger.error("Destruction failure", e) + } + } + + def init = { + LiftRules.ending = false + } + + def getLiftSession(request: Req): LiftSession = LiftRules.getLiftSession(request) + + private def wrapState[T](req: Req, session: Box[LiftSession])(f: => T): T = { + session match { + case Full(ses) => S.init(req, ses)(f) + case _ => CurrentReq.doWith(req)(f) + } + } + + private def handleGenericContinuation(reqOrg: Req, resp: HTTPResponse, session: Box[LiftSession], func: ((=> LiftResponse) => Unit) => Unit): Boolean = { + + val req = if (null eq reqOrg) reqOrg else reqOrg.snapshot + + def runFunction(doAnswer: LiftResponse => Unit) { + Schedule.schedule(() => { + val answerFunc: (=> LiftResponse) => Unit = response => + doAnswer(wrapState(req, session)(response)) + + func(answerFunc) + + }, 5 millis) + } + + if (reqOrg.request.suspendResumeSupport_?) { + runFunction(liftResponse => { + // do the actual write on a separate thread + Schedule.schedule(() => { + reqOrg.request.resume(reqOrg, liftResponse) + }, 0 seconds) + }) + + reqOrg.request.suspend(cometTimeout) + false + } else { + val future = new LAFuture[LiftResponse] + + runFunction(answer => future.satisfy(answer)) + + future.get(cometTimeout) match { + case Full(answer) => sendResponse(answer, resp, req); true + case _ => false + } + } + } + + /** + * Processes the HTTP requests + */ + def service(req: Req, resp: HTTPResponse): Boolean = { + try { + def doIt: Boolean = { + if (LiftRules.logServiceRequestTiming) { + logTime { + val ret = doService(req, resp) + val msg = "Service request (" + req.request.method + ") " + req.request.uri + " returned " + resp.getStatus + "," + (msg, ret) + } + } else { + doService(req, resp) + } + } + + req.request.resumeInfo match { + case None => doIt + case r if r eq null => doIt + case Some((or: Req, r: LiftResponse)) if (req.path == or.path) => sendResponse(r, resp, req); true + case _ => doIt + } + } catch { + case rest.ContinuationException(theReq, sesBox, func) => + handleGenericContinuation(theReq, resp, sesBox, func); true // we have to return true to hold onto the request + + case e if e.getClass.getName.endsWith("RetryRequest") => throw e + case e: Throwable => logger.info("Request for " + req.request.uri + " failed " + e.getMessage, e); throw e + } + } + + private def flatten(in: List[Any]): List[Any] = in match { + case Nil => Nil + case Some(x: AnyRef) :: xs => x :: flatten(xs) + case Full(x: AnyRef) :: xs => x :: flatten(xs) + case (lst: Iterable[_]) :: xs => lst.toList ::: flatten(xs) + case (x: AnyRef) :: xs => x :: flatten(xs) + case x :: xs => flatten(xs) + } + + private def authPassed_?(req: Req): Boolean = { + + val checkRoles: (Role, List[Role]) => Boolean = { + case (resRole, roles) => (false /: roles)((l, r) => l || resRole.isChildOf(r.name)) + } + + val role = NamedPF.applyBox(req, LiftRules.httpAuthProtectedResource.toList) + role.map(_ match { + case Full(r) => + LiftRules.authentication.verified_?(req) match { + case true => checkRoles(r, userRoles.get) + case _ => false + } + case _ => LiftRules.authentication.verified_?(req) + }) openOr true + } + + private val recent: LRUMap[String, Int] = new LRUMap(2000) + + private def registerRecentlyChecked(id: String): Unit = + synchronized { + val next = recent.get(id) match { + case Full(x) => x + 1 + case _ => 1 + } + + recent(id) = next + } + + private def recentlyChecked(id: Box[String]): Int = synchronized { + id.flatMap(recent.get).openOr(0) + } + + /** + * Service the HTTP request + */ + def doService(req: Req, response: HTTPResponse): Boolean = { + var tmpStatelessHolder: Box[Box[LiftResponse]] = Empty + + tryo { + LiftRules.onBeginServicing.toList.foreach(_(req)) + } + + def hasSession(idb: Box[String]): Boolean = { + idb.flatMap { + id => + registerRecentlyChecked(id) + SessionMaster.getSession(id, Empty) + }.isDefined + } + + val wp = req.path.wholePath + val pathLen = wp.length + def isComet: Boolean = { + if (pathLen < 2) false + else { + val kindaComet = wp.head == LiftRules.cometPath + val cometScript = (pathLen >= 3 && kindaComet && + wp(2) == LiftRules.cometScriptName()) + + (kindaComet && !cometScript) && req.acceptsJavaScript_? + } + } + def isAjax: Boolean = { + if (pathLen < 2) false + else { + val kindaAjax = wp.head == LiftRules.ajaxPath + val ajaxScript = kindaAjax && + wp(1) == LiftRules.ajaxScriptName() + + (kindaAjax && !ajaxScript) && req.acceptsJavaScript_? + } + } + + val sessionIdCalc = new SessionIdCalc(req) + + val resp: Box[LiftResponse] = try { + // if the application is shutting down, return a 404 + if (LiftRules.ending) { + LiftRules.notFoundOrIgnore(req, Empty) + } else if (!authPassed_?(req)) { + Full(LiftRules.authentication.unauthorizedResponse) + } else if (LiftRules.redirectAsyncOnSessionLoss && !hasSession(sessionIdCalc.id) && (isComet || isAjax)) { + val theId = sessionIdCalc.id + + // okay after 2 attempts to redirect, just ignore calls to the + // async URL + if (recentlyChecked(theId) > 1) { + Empty + } else { + val cmd = + if (isComet) + js.JE.JsRaw(LiftRules.noCometSessionCmd.vend.toJsCmd + ";lift_toWatch = {};").cmd + else + js.JE.JsRaw(LiftRules.noAjaxSessionCmd.vend.toJsCmd).cmd + + Full(new JsCommands(cmd :: Nil).toResponse) + } + } else if (S.statelessInit(req) { + // if the request is matched is defined in the stateless table, dispatch + tmpStatelessHolder = NamedPF.applyBox(req, + LiftRules.statelessDispatch.toList).map(_.apply() match { + case Full(a) => Full(LiftRules.convertResponse((a, Nil, S.responseCookies, req))) + case r => r + }) + tmpStatelessHolder.isDefined + }) { + val f = tmpStatelessHolder.openOrThrowException("This is a full box here, checked on previous line") + f match { + case Full(v) => Full(v) + case Empty => LiftRules.notFoundOrIgnore(req, Empty) + case f: Failure => Full(req.createNotFound(f)) + } + } else { + // otherwise do a stateful response + val liftSession = getLiftSession(req) + + def doSession(r2: Req, s2: LiftSession, continue: Box[() => Nothing]): () => Box[LiftResponse] = { + try { + S.init(r2, s2) { + dispatchStatefulRequest(S.request.openOrThrowException("I'm pretty sure this is a full box here"), liftSession, r2, continue) + } + } catch { + case cre: ContinueResponseException => + r2.destroyServletSession() + doSession(r2, getLiftSession(r2), Full(cre.continue)) + } + } + + val lzy: () => Box[LiftResponse] = doSession(req, liftSession, Empty) + + lzy() + } + } catch { + case foc: LiftFlowOfControlException => throw foc + case e: Exception if !e.getClass.getName.endsWith("RetryRequest") => S.runExceptionHandlers(req, e) + } + + tryo { + LiftRules.onEndServicing.toList.foreach(_(req, resp)) + } + + resp match { + case Full(EmptyResponse) => + true + + case Full(cresp) => + sendResponse(cresp, response, req) + true + + case _ => { + false + } + } + } + + private def dispatchStatefulRequest(req: Req, + liftSession: LiftSession, + originalRequest: Req, + continuation: Box[() => Nothing]): () => Box[LiftResponse] = { + val toMatch = req + + val dispatch: (Boolean, Box[LiftResponse]) = + NamedPF.find(toMatch, LiftRules.dispatchTable(req.request)) match { + case Full(pf) => + LiftSession.onBeginServicing.foreach(_(liftSession, req)) + val ret: (Boolean, Box[LiftResponse]) = + try { + try { + // run the continuation in the new session + // if there is a continuation + continuation match { + case Full(func) => { + func() + S.redirectTo("/") + } + case _ => // do nothing + } + + liftSession.runParams(req) + S.functionLifespan(true) { + pf(toMatch)() match { + case Full(v) => + (true, Full(LiftRules.convertResponse((liftSession.checkRedirect(v), Nil, + S.responseCookies, req)))) + + case Empty => + (true, LiftRules.notFoundOrIgnore(req, Full(liftSession))) + + case f: Failure => + (true, Full(liftSession.checkRedirect(req.createNotFound(f)))) + } + } + } catch { + case ite: java.lang.reflect.InvocationTargetException if (ite.getCause.isInstanceOf[ResponseShortcutException]) => + (true, Full(liftSession.handleRedirect(ite.getCause.asInstanceOf[ResponseShortcutException], req))) + + case rd: net.liftweb.http.ResponseShortcutException => (true, Full(liftSession.handleRedirect(rd, req))) + } + } finally { + if (S.functionMap.size > 0) { + liftSession.updateFunctionMap(S.functionMap, S.renderVersion, millis) + S.clearFunctionMap + } + liftSession.notices = S.getNotices + } + + LiftSession.onEndServicing.foreach(_(liftSession, req, + ret._2)) + ret + + case _ => (false, Empty) + } + + val wp = req.path.wholePath + + if (LiftRules.enableContainerSessions && !req.stateless_?) { + req.request.session + } + + def respToFunc(in: Box[LiftResponse]): () => Box[LiftResponse] = { + val ret = in.map(LiftRules.performTransform) + () => ret + } + + val toReturn: () => Box[LiftResponse] = + if (dispatch._1) { + respToFunc(dispatch._2) + } else if (wp.length == 3 && wp.head == LiftRules.cometPath && + wp(2) == LiftRules.cometScriptName()) { + respToFunc(LiftRules.serveCometScript(liftSession, req)) + } else if ((wp.length >= 1) && wp.head == LiftRules.cometPath) { + handleComet(req, liftSession, originalRequest) match { + case Left(x) => respToFunc(x) + case Right(x) => x + } + } else if (wp.length == 2 && wp.head == LiftRules.ajaxPath && + wp(1) == LiftRules.ajaxScriptName()) { + respToFunc(LiftRules.serveAjaxScript(liftSession, req)) + } else if (wp.length >= 1 && wp.head == LiftRules.ajaxPath) { + respToFunc(handleAjax(liftSession, req)) + } else { + respToFunc(liftSession.processRequest(req, continuation)) + } + + toReturn + } + + /** + * Tracks the two aspects of an AJAX version: the sequence number, + * whose sole purpose is to identify requests that are retries for the + * same resource, and pending requests, which indicates how many + * requests are still queued for this particular page version on the + * client. The latter is used to expire result data for sequence + * numbers that are no longer needed. + */ + private case class AjaxVersionInfo(renderVersion:String, sequenceNumber:Long, pendingRequests:Int) + private object AjaxVersions { + def unapply(ajaxPathPart: String) : Option[AjaxVersionInfo] = { + val separator = ajaxPathPart.indexOf("-") + if (separator > -1 && ajaxPathPart.length > separator + 2) + Some( + AjaxVersionInfo(ajaxPathPart.substring(0, separator), + java.lang.Long.parseLong(ajaxPathPart.substring(separator + 1, ajaxPathPart.length - 1), 36), + Integer.parseInt(ajaxPathPart.substring(ajaxPathPart.length - 1), 36)) + ) + else + None + } + } + /** + * Extracts two versions from a given AJAX path: + * - The RenderVersion, which is used for GC purposes. + * - The requestVersions, which let us determine if this is + * a request we've already dealt with or are currently dealing + * with (so we don't rerun the associated handler). See + * handleVersionedAjax for more. + * + * The requestVersion is passed to the function that is passed in. + */ + private def extractVersions[T](path: List[String])(f: (Box[AjaxVersionInfo]) => T): T = { + path match { + case ajaxPath :: AjaxVersions(versionInfo @ AjaxVersionInfo(renderVersion, _, _)) :: _ => + RenderVersion.doWith(renderVersion)(f(Full(versionInfo))) + case ajaxPath :: renderVersion :: _ => + RenderVersion.doWith(renderVersion)(f(Empty)) + case _ => f(Empty) + } + } + + /** + * Runs the actual AJAX processing. This includes handling __lift__GC, + * or running the parameters in the session. onComplete is run when the + * AJAX request has completed with a response that is meant for the + * user. In cases where the request is taking too long to respond, + * an LAFuture may be used to delay the real response (and thus the + * invocation of onComplete) while this function returns an empty + * response. + */ + private def runAjax(liftSession: LiftSession, + requestState: Req): Box[LiftResponse] = { + try { + requestState.param("__lift__GC") match { + case Full(_) => + liftSession.updateFuncByOwner(RenderVersion.get, millis) + Full(JavaScriptResponse(js.JsCmds.Noop)) + + case _ => + try { + val what = flatten(try { + liftSession.runParams(requestState) + } catch { + case ResponseShortcutException(_, Full(to), _) => + import js.JsCmds._ + List(RedirectTo(to)) + }) + + val what2 = what.flatMap { + case js: JsCmd => List(js) + case jv: JValue => List(jv) + case n: NodeSeq => List(n) + case js: JsCommands => List(js) + case r: LiftResponse => List(r) + case s => Nil + } + + val ret: LiftResponse = what2 match { + case (json: JsObj) :: Nil => JsonResponse(json) + case (jv: JValue) :: Nil => JsonResponse(jv) + case (js: JsCmd) :: xs => { + (JsCommands(S.noticesToJsCmd :: Nil) & + ((js :: xs).flatMap { + case js: JsCmd => List(js) + case _ => Nil + }.reverse) & + S.jsToAppend).toResponse + } + + case (n: Node) :: _ => XmlResponse(n) + case (ns: NodeSeq) :: _ => XmlResponse(Group(ns)) + case (r: LiftResponse) :: _ => r + case _ => JsCommands(S.noticesToJsCmd :: JsCmds.Noop :: S.jsToAppend).toResponse + } + + LiftRules.cometLogger.debug("AJAX Response: " + liftSession.uniqueId + " " + ret) + + Full(ret) + } finally { + if (S.functionMap.size > 0) { + liftSession.updateFunctionMap(S.functionMap, RenderVersion.get, millis) + S.clearFunctionMap + } + } + } + } catch { + case foc: LiftFlowOfControlException => throw foc + case e: Exception => S.runExceptionHandlers(requestState, e) + } + } + + // Retry requests will stop trying to wait for the original request to + // complete 500ms after the client's timeout. This is because, while + // we want the original thread to complete so that it can provide an + // answer for future retries, we don't want retries tying up resources + // when the client won't receive the response anyway. + private lazy val ajaxPostTimeout: Long = LiftRules.ajaxPostTimeout * 1000L + 500L + /** + * Kick off AJAX handling. Extracts relevant versions and handles the + * begin/end servicing requests. Then checks whether to wait on an + * existing request for this same version to complete or whether to + * do the actual processing. + */ + private def handleAjax(liftSession: LiftSession, + requestState: Req): Box[LiftResponse] = { + extractVersions(requestState.path.partPath) { versionInfo => + LiftRules.cometLogger.debug("AJAX Request: " + liftSession.uniqueId + " " + requestState.params) + tryo { + LiftSession.onBeginServicing.foreach(_(liftSession, requestState)) + } + + // Here, a Left[LAFuture] indicates a future that needs to be + // *satisfied*, meaning we will run the request processing. + // A Right[LAFuture] indicates a future we need to *wait* on, + // meaning we will return the result of whatever satisfies the + // future. + val nextAction:Either[LAFuture[Box[LiftResponse]], LAFuture[Box[LiftResponse]]] = + versionInfo match { + case Full(AjaxVersionInfo(_, handlerVersion, pendingRequests)) => + val renderVersion = RenderVersion.get + + liftSession.withAjaxRequests { currentAjaxRequests => + // Create a new future, put it in the request list, and return + // the associated info with the future that needs to be + // satisfied by the current request handler. + def newRequestInfo = { + val info = AjaxRequestInfo(handlerVersion, new LAFuture[Box[LiftResponse]], millis) + + val existing = currentAjaxRequests.getOrElseUpdate(renderVersion, Nil) + currentAjaxRequests += (renderVersion -> (info :: existing)) + + info + } + + val infoList = currentAjaxRequests.get(renderVersion) + val (requestInfo, result) = + infoList + .flatMap { entries => + entries + .find(_.requestVersion == handlerVersion) + .map { entry => + (entry, Right(entry.responseFuture)) + } + } + .getOrElse { + val entry = newRequestInfo + + (entry, Left(entry.responseFuture)) + } + + // If there are no other pending requests, we can + // invalidate all the render version's AJAX entries except + // for the current one, as the client is no longer looking + // to retry any of them. + if (pendingRequests == 0) { + // Satisfy anyone waiting on futures for invalid + // requests with a failure. + for { + list <- infoList + entry <- list if entry.requestVersion != handlerVersion + } { + entry.responseFuture.satisfy(Failure("Request no longer pending.")) + } + + currentAjaxRequests += (renderVersion -> List(requestInfo)) + } + + result + } + + case _ => + // Create a future that processes the ajax response + // immediately. This runs if we don't have a handler + // version, which happens in cases like AJAX requests for + // Lift GC that don't go through the de-duping pipeline. + // Because we always return a Left here, the ajax processing + // always runs for this type of request. + Left(new LAFuture[Box[LiftResponse]]) + } + + val ret:Box[LiftResponse] = + nextAction match { + case Left(future) => + val result = runAjax(liftSession, requestState) map CachedResponse + + if (result.exists(_.failed_?)) { + // The request failed. The client will retry it, so + // remove it from the list of current Ajax requests that + // needs to be satisfied so we re-process the next request + // from scratch + liftSession.withAjaxRequests { currentAjaxRequests => + currentAjaxRequests.remove(RenderVersion.get) + } + } + + future.satisfy(result) + result + + case Right(future) => + val ret = future.get(ajaxPostTimeout) openOr Failure("AJAX retry timeout.") + + ret + } + + tryo { + LiftSession.onEndServicing.foreach(_(liftSession, requestState, ret)) + } + + ret + } + } + +/** + * An actor that manages continuations from container (Jetty style) + */ + class ContinuationActor(request: Req, session: LiftSession, + actors: List[(LiftCometActor, Long)], + onBreakout: List[AnswerRender] => Unit) extends LiftActor { + private var answers: List[AnswerRender] = Nil + private var done = false + val seqId = Helpers.nextNum + + def messageHandler = { + case BeginContinuation => + val sendItToMe: AnswerRender => Unit = ah => this ! ah + + actors.foreach { + case (act, when) => act ! Listen(when, ListenerId(seqId), sendItToMe) + } + + case ar: AnswerRender => + answers = ar :: answers + LAPinger.schedule(this, BreakOut(), 5 millis) + + case BreakOut() if !done => + done = true + session.exitComet(this) + actors.foreach { + case (act, _) => tryo(act ! Unlisten(ListenerId(seqId))) + } + onBreakout(answers) + + case _ => + } + + override def toString = "Actor dude " + seqId + } + + private object BeginContinuation + + private lazy val cometTimeout: Long = (LiftRules.cometRequestTimeout openOr 120) * 1000L + + private def setupContinuation(request: Req, session: LiftSession, actors: List[(LiftCometActor, Long)]): Any = { + val cont = new ContinuationActor(request, session, actors, + answers => request.request.resume( + (request, S.init(request, session) + (LiftRules.performTransform( + convertAnswersToCometResponse(session, + answers.toList, actors)))))) + + + try { + session.enterComet(cont -> request) + + LAPinger.schedule(cont, BreakOut(), TimeSpan(cometTimeout)) + + request.request.suspend(cometTimeout + 2000L) + } finally { + cont ! BeginContinuation + } + } + + private def handleComet(requestState: Req, sessionActor: LiftSession, originalRequest: Req): Either[Box[LiftResponse], () => Box[LiftResponse]] = { + val actors: List[(LiftCometActor, Long)] = + requestState.params.toList.flatMap { + case (name, when) => + sessionActor.getAsyncComponent(name).toList.map(c => (c, toLong(when))) + } + + if (actors.isEmpty) Left(Full(new JsCommands(LiftRules.noCometSessionCmd.vend :: js.JE.JsRaw("lift_toWatch = {};").cmd :: Nil).toResponse)) + else requestState.request.suspendResumeSupport_? match { + case true => { + setupContinuation(requestState, sessionActor, actors) + Left(Full(EmptyResponse)) + } + + case _ => { + Right(handleNonContinuationComet(requestState, sessionActor, actors, originalRequest)) + } + } + } + + private def convertAnswersToCometResponse(session: LiftSession, ret: Seq[AnswerRender], actors: List[(LiftCometActor, Long)]): LiftResponse = { + val ret2: List[AnswerRender] = ret.toList + val jsUpdateTime = ret2.map(ar => "if (lift_toWatch['" + ar.who.uniqueId + "'] !== undefined) lift_toWatch['" + ar.who.uniqueId + "'] = '" + ar.when + "';").mkString("\n") + val jsUpdateStuff = ret2.map { + ar => { + val ret = ar.response.toJavaScript(session, ar.displayAll) + + if (!S.functionMap.isEmpty) { + session.updateFunctionMap(S.functionMap, + ar.who.uniqueId, ar.when) + S.clearFunctionMap + } + + ret + } + } + + actors foreach (_._1 ! ClearNotices) + + val addl: List[JsCmd] = + (for { + req <- S.request + rendVer <- extractRenderVersion(req.path.partPath) + } yield RenderVersion.doWith(rendVer) { + S.jsToAppend + }) openOr Nil + + (new JsCommands(JsCmds.Run(jsUpdateTime) :: jsUpdateStuff ::: addl)).toResponse + } + + private def extractRenderVersion(in: List[String]): Box[String] = in match { + case _ :: _ :: _ :: rv :: _ => Full(rv) + case _ => Empty + } + + private def handleNonContinuationComet(request: Req, session: LiftSession, actors: List[(LiftCometActor, Long)], + originalRequest: Req): () => Box[LiftResponse] = () => { + val f = new LAFuture[List[AnswerRender]] + val cont = new ContinuationActor(request, session, actors, + answers => f.satisfy(answers)) + + try { + cont ! BeginContinuation + + session.enterComet(cont -> request) + + LAPinger.schedule(cont, BreakOut(), TimeSpan(cometTimeout)) + + val ret2 = f.get(cometTimeout) openOr Nil + + Full(S.init(originalRequest, session) { + convertAnswersToCometResponse(session, ret2, actors) + }) + } finally { + session.exitComet(cont) + } + } + + val dumpRequestResponse = Props.getBool("dump.request.response", false) + + private def logIfDump(request: Req, response: BasicResponse) { + if (dumpRequestResponse) { + val toDump = request.uri + "\n" + + request.params + "\n" + + response.headers + "\n" + + ( + response match { + case InMemoryResponse(data, _, _, _) => new String(data, "UTF-8") + case _ => "data" + } + ) + + logger.trace(toDump) + } + } + + /** + * Sends the { @code HTTPResponse } to the browser using data from the + * { @link Response } and { @link Req }. + */ + private[http] def sendResponse(liftResp: LiftResponse, response: HTTPResponse, request: Req) { + def fixHeaders(headers: List[(String, String)]) = headers map ((v) => v match { + case ("Location", uri) => + val u = request + (v._1, ( + (for ( + updated <- Full((if (!LiftRules.excludePathFromContextPathRewriting.vend(uri)) u.contextPath else "") + uri).filter(ignore => uri.startsWith("/")); + rwf <- URLRewriter.rewriteFunc) yield rwf(updated)) openOr uri + )) + case _ => v + }) + + def pairFromRequest(req: Req): (Box[Req], Box[String]) = { + val acceptHeader = for (innerReq <- Box.legacyNullTest(req.request); + accept <- innerReq.header("Accept")) yield accept + + (Full(req), acceptHeader) + } + + val resp = liftResp.toResponse + + logIfDump(request, resp) + + def insureField(headers: List[(String, String)], toInsure: List[(String, String)]): List[(String, String)] = { + val org = Map(headers: _*) + + toInsure.foldLeft(org) { + case (map, (key, value)) => + if (map.contains(key)) map + else map + (key -> value) + }.toList + + } + + + val len = resp.size + // insure that certain header fields are set + val header = if (resp.code == 304 || resp.code == 303) + fixHeaders(resp.headers) + else + insureField(fixHeaders(resp.headers), + LiftRules.defaultHeaders(NodeSeq.Empty -> request) ::: + /* List(("Content-Type", + LiftRules.determineContentType(pairFromRequest(request)))) ::: */ + (if (len >= 0) List(("Content-Length", len.toString)) else Nil)) + + LiftRules.beforeSend.toList.foreach(f => tryo(f(resp, response, header, Full(request)))) + // set the cookies + response.addCookies(resp.cookies) + + // send the response + response.addHeaders(header.map { + case (name, value) => HTTPParam(name, value) + }) + LiftRules.supplimentalHeaders(response) + + liftResp match { + case ResponseWithReason(_, reason) => response setStatusWithReason (resp.code, reason) + case _ => response setStatus resp.code + } + + try { + resp match { + case EmptyResponse => + case InMemoryResponse(bytes, _, _, _) => + response.outputStream.write(bytes) + response.outputStream.flush() + + case StreamingResponse(stream, endFunc, _, _, _, _) => + try { + var len = 0 + val ba = new Array[Byte](8192) + val os = response.outputStream + stream match { + case jio: java.io.InputStream => len = jio.read(ba) + case stream => len = stream.read(ba) + } + while (len >= 0) { + if (len > 0) os.write(ba, 0, len) + stream match { + case jio: java.io.InputStream => len = jio.read(ba) + case stream => len = stream.read(ba) + } + } + response.outputStream.flush() + } finally { + endFunc() + } + + case OutputStreamResponse(out, _, _, _, _) => + out(response.outputStream) + response.outputStream.flush() + } + } catch { + case e: java.io.IOException => // ignore IO exceptions... they happen + } + + LiftRules.afterSend.toList.foreach(f => tryo(f(resp, response, header, Full(request)))) + } +} + +import provider.servlet._ + +class LiftFilter extends ServletFilterProvider + +private class SessionIdCalc(req: Req) { + private val CometPath = LiftRules.cometPath + lazy val id: Box[String] = req.request.sessionId match { + case Full(id) => Full(id) + case _ => req.path.wholePath match { + case CometPath :: _ :: id :: _ if id != LiftRules.cometScriptName() => Full(id) + case _ => Empty + } + } +} diff --git a/web/webkit/src/main/scala/net/liftweb/http/js/Jx.scala b/web/webkit/src/main/scala/net/liftweb/http/js/Jx.scala index ef1263e660..c4e4554358 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/js/Jx.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/js/Jx.scala @@ -180,6 +180,8 @@ case class JxIfElse(toTest: JsExp, ifTrue: NodeSeq, ifFalse: NodeSeq) extends No } + + case class Jx(child: NodeSeq) extends Node with JxBase with JxYieldFunc { def appendToParent(parentName: String): JsCmd = addToDocFrag(parentName, child.toList) diff --git a/web/webkit/src/main/scala/net/liftweb/http/provider/HTTPProvider.scala b/web/webkit/src/main/scala/net/liftweb/http/provider/HTTPProvider.scala index 85d1b9fc0f..abf75f7289 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/provider/HTTPProvider.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/provider/HTTPProvider.scala @@ -22,7 +22,6 @@ import net.liftweb.common._ import net.liftweb.util._ import java.util.{Locale, ResourceBundle} import Helpers._ -import processing.LiftServlet /** * Implement this trait in order to integrate Lift with other underlaying web containers. Not necessarily JEE containers. diff --git a/web/webkit/src/main/scala/net/liftweb/sitemap/Loc.scala b/web/webkit/src/main/scala/net/liftweb/sitemap/Loc.scala index 2254c9e630..d4aa26a470 100644 --- a/web/webkit/src/main/scala/net/liftweb/sitemap/Loc.scala +++ b/web/webkit/src/main/scala/net/liftweb/sitemap/Loc.scala @@ -878,6 +878,9 @@ object Loc { } } + // @deprecated def alwaysTrue(a: Req) = true + // @deprecated def retString(toRet: String)(other: Seq[(String, String)]) = Full(toRet) + implicit def strToFailMsg(in: => String): FailMsg = () => { RedirectWithState( LiftRules.siteMapFailRedirectLocation.mkString("/", "/", ""), From 48051bd79d43f802e7c6b628729513e0545d66f9 Mon Sep 17 00:00:00 2001 From: Diego Medina Date: Thu, 7 Nov 2013 00:05:36 -0500 Subject: [PATCH 0628/1949] Revert "Removed deprecated methods" This reverts commit 3fbcd7d009daf5d4e3eb41cac38cf0f118010c2f. Conflicts: web/webkit/src/main/scala/net/liftweb/http/package.scala web/webkit/src/main/scala/net/liftweb/http/processing/LiftServlet.scala --- .../main/scala/net/liftweb/common/Box.scala | 34 + .../scala/net/liftweb/util/ClassHelpers.scala | 6 + .../net/liftweb/util/StringHelpers.scala | 3 + .../scala/net/liftweb/util/TimeHelpers.scala | 21 + .../main/scala/net/liftweb/util/package.scala | 5 + .../scala/net/liftweb/http/Bindings.scala | 129 +++ .../scala/net/liftweb/http/CometActor.scala | 16 + .../scala/net/liftweb/http/LiftFilter.scala | 5 - .../scala/net/liftweb/http/LiftRules.scala | 46 + .../src/main/scala/net/liftweb/http/S.scala | 42 +- .../scala/net/liftweb/http/Templates.scala | 23 + .../net/liftweb/http/js/JsCommands.scala | 2 + .../liftweb/http/processing/LiftServlet.scala | 953 ------------------ .../scala/net/liftweb/sitemap/XmlMenu.scala | 45 + 14 files changed, 371 insertions(+), 959 deletions(-) create mode 100644 web/webkit/src/main/scala/net/liftweb/http/Bindings.scala delete mode 100644 web/webkit/src/main/scala/net/liftweb/http/LiftFilter.scala delete mode 100644 web/webkit/src/main/scala/net/liftweb/http/processing/LiftServlet.scala create mode 100644 web/webkit/src/main/scala/net/liftweb/sitemap/XmlMenu.scala diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index 25a7283ad9..bfcb0cfb26 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -234,6 +234,40 @@ sealed abstract class Box[+A] extends Product with Serializable{ */ def openOrThrowException(justification: String): A + /** + * Return the value contained in this Box if it is Full; + * throw an exception otherwise. + * + * Using open_! in an example posted to the Lift mailing list + * may disqualify you for a helpful response. + * + * The method has a '!' in its name. This means "don't use it unless + * you are 100% sure that the Box is Full and you should probably + * comment your code with the explanation of the guaranty." + * The better case for extracting the value out of a Box can + * be found at http://lift.la/scala-option-lift-box-and-how-to-make-your-co + * + * @return the value contained in this Box if it is full; throw an exception otherwise + */ + /* + @deprecated("use openOrThrowException, or better yet, do the right thing with your code and use map, flatMap or foreach", "2.4") + final def open_! : A = openOrThrowException("Legacy method implementation") + */ + /** + * Return the value contained in this Box if it is Full; + * throw an exception otherwise. + * This means "don't use it unless + * you are 100% sure that the Box is Full and you should probably + * comment your code with the explanation of the guaranty. + * The better case for extracting the value out of a Box can + * be found at http://lift.la/scala-option-lift-box-and-how-to-make-your-co + * + * @return the value contained in this Box if it is full; throw an exception otherwise + */ + /* + @deprecated("use openOrThrowException, or better yet, do the right thing with your code and use map, flatMap or foreach", "2.4") + final def openTheBox: A = openOrThrowException("Legacy method implementation") + */ /** * Return the value contained in this Box if it is full; otherwise return the specified default * @return the value contained in this Box if it is full; otherwise return the specified default diff --git a/core/util/src/main/scala/net/liftweb/util/ClassHelpers.scala b/core/util/src/main/scala/net/liftweb/util/ClassHelpers.scala index 5a737b226d..538d9d333c 100644 --- a/core/util/src/main/scala/net/liftweb/util/ClassHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/ClassHelpers.scala @@ -151,6 +151,12 @@ trait ClassHelpers { self: ControlHelpers => def findClass(where: List[(String, List[String])]): Box[Class[AnyRef]] = findType[AnyRef](where) + @deprecated("Use StringHelpers.camelify", "2.3") + def camelCase(name : String): String = StringHelpers.camelify(name) + @deprecated("Use StringHelpers.camelifyMethod", "2.3") + def camelCaseMethod(name: String): String = StringHelpers.camelifyMethod(name) + @deprecated("Use StringHelpers.snakify", "2.3") + def unCamelCase(name : String) = StringHelpers.snakify(name) /** * @return true if the method is public and has no parameters diff --git a/core/util/src/main/scala/net/liftweb/util/StringHelpers.scala b/core/util/src/main/scala/net/liftweb/util/StringHelpers.scala index bf6ef1faac..1b3715976a 100644 --- a/core/util/src/main/scala/net/liftweb/util/StringHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/StringHelpers.scala @@ -364,6 +364,9 @@ trait StringHelpers { /** @return a SuperString with more available methods such as roboSplit or commafy */ implicit def listStringToSuper(in: List[String]): SuperListString = new SuperListString(in) + @deprecated("Use blankForNull instead", "2.3") + def emptyForNull(s: String) = blankForNull(s) + /** * Test for null and return either the given String if not null or the blank String. */ diff --git a/core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala b/core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala index 9f622b1c79..39fbfdd8ed 100644 --- a/core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala @@ -70,6 +70,16 @@ trait TimeHelpers { self: ControlHelpers => def year = years } + /* + /** + * transforms a TimeSpan to a date by converting the TimeSpan expressed as millis and creating + * a Date lasting that number of millis from the Epoch time (see the documentation for java.util.Date) + */ + implicit def timeSpanToDate(in: TimeSpan): Date = in.date + + /** transforms a TimeSpan to its long value as millis */ + implicit def timeSpanToLong(in: TimeSpan): Long = in.millis + */ /** * The TimeSpan class represents an amount of time. @@ -284,6 +294,17 @@ trait TimeHelpers { self: ControlHelpers => /** @return the current year */ def currentYear: Int = Calendar.getInstance.get(Calendar.YEAR) + /** + * @return the current time as a Date object + */ + @deprecated("use now instead", "2.4") + def timeNow = new Date + + /** + * @deprecated use today instead + * @return the current Day as a Date object + */ + def dayNow: Date = 0.seconds.later.noTime /** alias for new Date(millis) */ def time(when: Long) = new Date(when) diff --git a/core/util/src/main/scala/net/liftweb/util/package.scala b/core/util/src/main/scala/net/liftweb/util/package.scala index 2f3311ef83..48d4d85e2b 100644 --- a/core/util/src/main/scala/net/liftweb/util/package.scala +++ b/core/util/src/main/scala/net/liftweb/util/package.scala @@ -26,6 +26,11 @@ import xml.NodeSeq package object util { type CssBindFunc = CssSel + /** + * Changed the name ActorPing to Schedule + */ + @deprecated("Use Schedule", "2.3") + val ActorPing = Schedule /** * Wrap a function and make sure it's a NodeSeq => NodeSeq. Much easier diff --git a/web/webkit/src/main/scala/net/liftweb/http/Bindings.scala b/web/webkit/src/main/scala/net/liftweb/http/Bindings.scala new file mode 100644 index 0000000000..93ed7ad735 --- /dev/null +++ b/web/webkit/src/main/scala/net/liftweb/http/Bindings.scala @@ -0,0 +1,129 @@ +/* + * Copyright 2006-2009 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,Empty,Failure} +import util.Props +import scala.xml.{NodeSeq, Text} + +/** + * A collection of types and implicit transformations used to allow composition + * of page elements based upon the types of rendered objects. + * + * In Lift, a "snippet" is a function from NodeSeq => NodeSeq, where the argument + * to the function is a template, and the result is a fragment of a page to be + * rendered. Of course, this is a bit of an abbreviation; the snippet function + * also has an argument which is the application state made available from S. + * A DataBinding[T] is very similar in this respect; it is a function from some + * piece of information of type T to a function from NodeSeq => NodeSeq. Since + * DataBinding is strongly typed with respect to the type of information being + * rendered, DataBinding instances are ideal for the rendering of objects that + * is used to build up snippets. For example: + * + *
    + * import net.liftweb.http.Bindings._
    +
    + * case class MyClass(str: String, i: Int, other: MyOtherClass)
    + * case class MyOtherClass(foo: String)
    + *
    + * trait MyClassBinding extends DataBinding[MyClass] {
    + *   implicit val otherBinding: DataBinding[MyOtherClass]
    + *
    + *   override def apply(entity: MyClass) = (xhtml: NodeSeq) => {
    + *     val otherTemplate = chooseTemplate("myclass", "other", xhtml)
    + *     bind(
    + *       "myclass", xhtml, 
    + *       "str" -> Text("#" + entity.str + "#"),
    + *       "i" -> Text(entity.i.toString),
    + *       "other" -> entity.other.bind(otherTemplate)
    + *     )
    + *   }
    + *
    + * }
    + * 
    + * object myOtherClassBinding extends DataBinding[MyOtherClass] {
    + *   override def apply(other: MyOtherClass) = (xhtml: NodeSeq) => {
    + *     bind("other", xhtml, "foo" -> Text("%" + other.foo + "%"))
    + *   }
    + * }
    + *
    + * object MyClassConcreteBinding extends MyClassBinding {
    + *   override val otherBinding = myOtherClassBinding
    + * }
    + * 
    + * + * In this example, two classes and their associated bindings are constructed; + * the first binding for MyClass is abstract, needing a specific instance of + * DataBinding[MyOtherClass] to enable the implicit conversion needed to render + * the contained MyOtherClass instance. A subtemplate is selected, and the + * call to other.bind both necessitates the implicit conversion to a Bindings.Binder + * instance and applies the appropriate formatting. You can see how this + * usage keeps the concerns of the view and the model nicely separated, while + * allowing composition over object graphs. + * + * Please see the tests, as well as
    this blog post for additional details. + */ +object Bindings { + type Binding = NodeSeq => NodeSeq + + type DataBinding[T] = T => NodeSeq => NodeSeq + + /** + * Implicitly convert the specified object to a binder for that object if a DataBinding for + * that object's type is available in implicit scope. This essentially adds a bind() method + * to an object if an appropriate implicit DataBinding is available. + */ + implicit def binder[T](t: T)(implicit binding: DataBinding[T]): Binder = Binder(binding(t)) + + /** + * Wrap the specified Binding (a function from NodeSeq => NodeSeq) in a Binder so that + * it can be applied using Binder's bind methods. + */ + implicit def binder(binding: Binding): Binder = Binder(binding) + + /** + * A decorator for a binding function that allows it to be called as bind() rather than apply(). + * This class also provides facilities for binding to a specific template + */ + case class Binder(val binding: Binding) { + /** + * Apply this binder's binding function to the specified NodeSeq. + */ + def bind(xhtml: NodeSeq): NodeSeq = binding.apply(xhtml) + + /** + * Apply this binder's binding function to the specified templated + * looked up using Templates.apply + */ + def bind(templatePath: List[String]): NodeSeq = { + Templates(templatePath) map binding match { + case Full(xhtml) => xhtml + case Failure(msg, ex, _) if Props.mode == Props.RunModes.Development => Text(ex.map(_.getMessage).openOr(msg)) + case Empty if Props.mode == Props.RunModes.Development => Text("Unable to find template with path " + templatePath.mkString("/", "/", "")) + case _ => NodeSeq.Empty + } + } + } + + /** + * Bind any input value to the empty NodeSeq. + */ + object EmptyBinding extends Binding { + override def apply(xhtml : NodeSeq) : NodeSeq = NodeSeq.Empty + } +} diff --git a/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala b/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala index 202727bfe9..a5e0544e36 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala @@ -285,6 +285,19 @@ trait ListenerManager { protected def lowPriority: PartialFunction[Any, Unit] = Map.empty } +/** + * This is a legacy trait, left over from Lift's + * Scala 2.7 support. You should use or migrate to + * CometListener instead. + * + * @see CometListener + */ +@deprecated("Use the CometListener trait instead.", "2.4") +trait CometListenee extends CometListener { + self: CometActor => +} + + /** * A LiftActorJ with ListenerManager. Subclass this class * to get a Java-usable LiftActorJ with ListenerManager @@ -549,6 +562,9 @@ trait CometActor extends LiftActor with LiftCometActor with BindHelpers { @volatile private var _defaultHtml: NodeSeq = _ + @deprecated("Use defaultHtml", "2.3") + def defaultXml = _defaultHtml + /** * The template that was passed to this component during comet * initializations diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftFilter.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftFilter.scala deleted file mode 100644 index 40d822277d..0000000000 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftFilter.scala +++ /dev/null @@ -1,5 +0,0 @@ -package net.liftweb.http - -import provider.servlet.ServletFilterProvider - -class LiftFilter extends ServletFilterProvider diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 132a49cade..4665150c98 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -411,6 +411,18 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { */ val convertToEntity: FactoryMaker[Boolean] = new FactoryMaker(false) {} + /** + * Certain paths within your application can be marked as stateless + * and if there is access to Lift's stateful facilities (setting + * SessionVars, updating function tables, etc.) the developer will + * receive a notice and the operation will not complete. This test + * has been deprecated in favor of statelessReqTest which also passes + * the HTTPRequest instance for testing of the user agent and other stuff. + */ + @deprecated("Use statelessReqTest", "2.4") + val statelessTest = RulesSeq[StatelessTestPF] + + /** * Certain paths and requests within your application can be marked as stateless * and if there is access to Lift's stateful facilities (setting @@ -1138,6 +1150,14 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { _doneBoot = true } + /** + * Holds user's DispatchPF functions that will be executed in a stateless context. This means that + * no session will be created and no JSESSIONID cookie will be presented to the user (unless + * the user has presented a JSESSIONID cookie). + */ + @deprecated("Use statelessDispatch", "2.4") + def statelessDispatchTable = statelessDispatch + /** * Holds user's DispatchPF functions that will be executed in a stateless context. This means that * no session will be created and no JSESSIONID cookie will be presented to the user (unless @@ -1329,6 +1349,12 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { */ val statelessRewrite = RulesSeq[RewritePF] + /** + * Use statelessRewrite or statefuleRewrite + */ + @deprecated("Use statelessRewrite or statefuleRewrite", "2.3") + val rewrite = statelessRewrite + /** * Holds the user's rewrite functions that can alter the URI parts and query parameters. * This rewrite takes place within the scope of the S state so SessionVars and other session-related @@ -1838,6 +1864,26 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { @volatile var templateCache: Box[TemplateCache[(Locale, List[String]), NodeSeq]] = Empty + /** + * A function to format a Date... can be replaced by a function that is user-specific + Replaced by dateTimeConverter + */ + @deprecated("Replaced by dateTimeConverter", "2.3") + @volatile var formatDate: Date => String = date => date match { + case null => LiftRules.dateTimeConverter.vend.formatDate(new Date(0L)) + case s => toInternetDate(s) + } + + /** + * A function that parses a String into a Date... can be replaced by something that's user-specific + Replaced by dateTimeConverter + */ + @deprecated("Replaced by dateTimeConverter", "2.3") + @volatile var parseDate: String => Box[Date] = str => str match { + case null => Empty + case s => Helpers.toDate(s) + } + val dateTimeConverter: FactoryMaker[DateTimeConverter] = new FactoryMaker[DateTimeConverter]( () => DefaultDateTimeConverter ) {} /** diff --git a/web/webkit/src/main/scala/net/liftweb/http/S.scala b/web/webkit/src/main/scala/net/liftweb/http/S.scala index be6edf6188..32466df3ac 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/S.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/S.scala @@ -1527,7 +1527,10 @@ trait S extends HasParams with Loggable with UserAgentCalculator { ).openOr(Nil) } - + @deprecated("Use S.getResponseHeader instead for clarity.", "2.5") + def getHeader(name: String): Box[String] = { + getResponseHeader(name) + } /** * Returns the current set value of the given HTTP response header as a Box. If * you want a request header, use Req.getHeader or S.getRequestHeader. @@ -2079,6 +2082,14 @@ trait S extends HasParams with Loggable with UserAgentCalculator { _attrs.doWith((Null, Nil))(f) } + /** + * Temporarily adds the given attributes to the current set, then executes the given function. + * + * @param attr The attributes to set temporarily + */ + @deprecated("Use the S.withAttrs method instead", "2.4") + def setVars[T](attr: MetaData)(f: => T): T = withAttrs(attr)(f) + /** * A function that will eagerly evaluate a template. */ @@ -2757,6 +2768,12 @@ trait S extends HasParams with Loggable with UserAgentCalculator { */ private[http] def noticesToJsCmd: JsCmd = LiftRules.noticesToJsCmd() + @deprecated("Use AFuncHolder.listStrToAF", "2.4") + def toLFunc(in: List[String] => Any): AFuncHolder = LFuncHolder(in, Empty) + + @deprecated("Use AFuncHolder.unitToAF", "2.4") + def toNFunc(in: () => Any): AFuncHolder = NFuncHolder(in, Empty) + implicit def stuff2ToUnpref(in: (Symbol, Any)): UnprefixedAttribute = new UnprefixedAttribute(in._1.name, Text(in._2.toString), Null) /** @@ -2844,6 +2861,29 @@ trait S extends HasParams with Loggable with UserAgentCalculator { f(name) } + + /** + * Similar with addFunctionMap but also returns the name. + * + * Use fmapFunc(AFuncHolder)(String => T) + */ + @deprecated("Use fmapFunc(AFuncHolder)(String => T)", "2.4") + def mapFunc(in: AFuncHolder): String = { + mapFunc(formFuncName, in) + } + + /** + * Similar with addFunctionMap but also returns the name. + * + * Use fmapFunc(AFuncHolder)(String => T) + */ + @deprecated("Use fmapFunc(AFuncHolder)(String => T)", "2.4") + def mapFunc(name: String, inf: AFuncHolder): String = { + addFunctionMap(name, inf) + name + } + + /** * Returns all the HTTP parameters having 'n' name */ diff --git a/web/webkit/src/main/scala/net/liftweb/http/Templates.scala b/web/webkit/src/main/scala/net/liftweb/http/Templates.scala index 3f46f870b5..cd150a2da7 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Templates.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Templates.scala @@ -84,6 +84,29 @@ object Templates { def apply(places: List[String], locale: Locale): Box[NodeSeq] = findRawTemplate(places, locale).map(checkForContentId) + /** + * Given a list of paths (e.g. List("foo", "index")), + * find the template. + * @param places - the path to look in + * + * @return the template if it can be found + */ + @deprecated("use apply", "2.4") + def findAnyTemplate(places: List[String]): Box[NodeSeq] = + findRawTemplate(places, S.locale) + + /** + * Given a list of paths (e.g. List("foo", "index")), + * find the template. + * @param places - the path to look in + * @param locale - the locale of the template to search for + * + * @return the template if it can be found + */ + @deprecated("use apply", "2.4") + def findAnyTemplate(places: List[String], locale: Locale): Box[NodeSeq] = findRawTemplate(places, locale) + + /** * Check to see if the template is marked designer friendly * and lop off the stuff before the first surround diff --git a/web/webkit/src/main/scala/net/liftweb/http/js/JsCommands.scala b/web/webkit/src/main/scala/net/liftweb/http/js/JsCommands.scala index 37fc229cac..c21427ef0d 100755 --- a/web/webkit/src/main/scala/net/liftweb/http/js/JsCommands.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/js/JsCommands.scala @@ -937,12 +937,14 @@ object JsRules { * The default duration for displaying FadeOut and FadeIn * messages. */ + //@deprecated @volatile var prefadeDuration: Helpers.TimeSpan = 5 seconds /** * The default fade time for fading FadeOut and FadeIn * messages. */ + //@deprecated @volatile var fadeTime: Helpers.TimeSpan = 1 second } diff --git a/web/webkit/src/main/scala/net/liftweb/http/processing/LiftServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/processing/LiftServlet.scala deleted file mode 100644 index fc17a153bc..0000000000 --- a/web/webkit/src/main/scala/net/liftweb/http/processing/LiftServlet.scala +++ /dev/null @@ -1,953 +0,0 @@ -/* - * Copyright 2007-2011 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 -package processing - -import xml.{Node, NodeSeq, Group} - -import common._ -import actor._ -import util._ -import util.Helpers._ -import js._ -import auth._ -import provider._ -import net.liftweb.json._ - - -/** - * Wrap a LiftResponse and cache the result to avoid computing the actual response - * more than once - */ -private [http] case class CachedResponse(wrapped: LiftResponse) extends LiftResponse { - private val _cachedResponse = wrapped.toResponse - - def toResponse = _cachedResponse - - // Should we retry request processing - def failed_? = _cachedResponse.code >= 500 && _cachedResponse.code < 600 -} - -class LiftServlet extends Loggable { - private var servletContext: HTTPContext = null - - def this(ctx: HTTPContext) = { - this () - this.servletContext = ctx - } - - def getContext: HTTPContext = servletContext - - def destroy = { - try { - LiftRules.ending = true - - tryo { - SessionMaster.shutDownAllSessions() - } - - val cur = millis - - // wait 10 seconds or until the request count is zero - while (LiftRules.reqCnt.get > 0 && (millis - cur) < 10000L) { - Thread.sleep(20) - } - - tryo { - Schedule.shutdown - } - tryo { - LAScheduler.shutdown() - } - - tryo { - LAPinger.shutdown - } - - LiftRules.runUnloadHooks() - logger.debug("Destroyed Lift handler.") - // super.destroy - } catch { - case e: Exception => logger.error("Destruction failure", e) - } - } - - def init = { - LiftRules.ending = false - } - - def getLiftSession(request: Req): LiftSession = LiftRules.getLiftSession(request) - - private def wrapState[T](req: Req, session: Box[LiftSession])(f: => T): T = { - session match { - case Full(ses) => S.init(req, ses)(f) - case _ => CurrentReq.doWith(req)(f) - } - } - - private def handleGenericContinuation(reqOrg: Req, resp: HTTPResponse, session: Box[LiftSession], func: ((=> LiftResponse) => Unit) => Unit): Boolean = { - - val req = if (null eq reqOrg) reqOrg else reqOrg.snapshot - - def runFunction(doAnswer: LiftResponse => Unit) { - Schedule.schedule(() => { - val answerFunc: (=> LiftResponse) => Unit = response => - doAnswer(wrapState(req, session)(response)) - - func(answerFunc) - - }, 5 millis) - } - - if (reqOrg.request.suspendResumeSupport_?) { - runFunction(liftResponse => { - // do the actual write on a separate thread - Schedule.schedule(() => { - reqOrg.request.resume(reqOrg, liftResponse) - }, 0 seconds) - }) - - reqOrg.request.suspend(cometTimeout) - false - } else { - val future = new LAFuture[LiftResponse] - - runFunction(answer => future.satisfy(answer)) - - future.get(cometTimeout) match { - case Full(answer) => sendResponse(answer, resp, req); true - case _ => false - } - } - } - - /** - * Processes the HTTP requests - */ - def service(req: Req, resp: HTTPResponse): Boolean = { - try { - def doIt: Boolean = { - if (LiftRules.logServiceRequestTiming) { - logTime { - val ret = doService(req, resp) - val msg = "Service request (" + req.request.method + ") " + req.request.uri + " returned " + resp.getStatus + "," - (msg, ret) - } - } else { - doService(req, resp) - } - } - - req.request.resumeInfo match { - case None => doIt - case r if r eq null => doIt - case Some((or: Req, r: LiftResponse)) if (req.path == or.path) => sendResponse(r, resp, req); true - case _ => doIt - } - } catch { - case rest.ContinuationException(theReq, sesBox, func) => - handleGenericContinuation(theReq, resp, sesBox, func); true // we have to return true to hold onto the request - - case e if e.getClass.getName.endsWith("RetryRequest") => throw e - case e: Throwable => logger.info("Request for " + req.request.uri + " failed " + e.getMessage, e); throw e - } - } - - private def flatten(in: List[Any]): List[Any] = in match { - case Nil => Nil - case Some(x: AnyRef) :: xs => x :: flatten(xs) - case Full(x: AnyRef) :: xs => x :: flatten(xs) - case (lst: Iterable[_]) :: xs => lst.toList ::: flatten(xs) - case (x: AnyRef) :: xs => x :: flatten(xs) - case x :: xs => flatten(xs) - } - - private def authPassed_?(req: Req): Boolean = { - - val checkRoles: (Role, List[Role]) => Boolean = { - case (resRole, roles) => (false /: roles)((l, r) => l || resRole.isChildOf(r.name)) - } - - val role = NamedPF.applyBox(req, LiftRules.httpAuthProtectedResource.toList) - role.map(_ match { - case Full(r) => - LiftRules.authentication.verified_?(req) match { - case true => checkRoles(r, userRoles.get) - case _ => false - } - case _ => LiftRules.authentication.verified_?(req) - }) openOr true - } - - private val recent: LRUMap[String, Int] = new LRUMap(2000) - - private def registerRecentlyChecked(id: String): Unit = - synchronized { - val next = recent.get(id) match { - case Full(x) => x + 1 - case _ => 1 - } - - recent(id) = next - } - - private def recentlyChecked(id: Box[String]): Int = synchronized { - id.flatMap(recent.get).openOr(0) - } - - /** - * Service the HTTP request - */ - def doService(req: Req, response: HTTPResponse): Boolean = { - var tmpStatelessHolder: Box[Box[LiftResponse]] = Empty - - tryo { - LiftRules.onBeginServicing.toList.foreach(_(req)) - } - - def hasSession(idb: Box[String]): Boolean = { - idb.flatMap { - id => - registerRecentlyChecked(id) - SessionMaster.getSession(id, Empty) - }.isDefined - } - - val wp = req.path.wholePath - val pathLen = wp.length - def isComet: Boolean = { - if (pathLen < 2) false - else { - val kindaComet = wp.head == LiftRules.cometPath - val cometScript = (pathLen >= 3 && kindaComet && - wp(2) == LiftRules.cometScriptName()) - - (kindaComet && !cometScript) && req.acceptsJavaScript_? - } - } - def isAjax: Boolean = { - if (pathLen < 2) false - else { - val kindaAjax = wp.head == LiftRules.ajaxPath - val ajaxScript = kindaAjax && - wp(1) == LiftRules.ajaxScriptName() - - (kindaAjax && !ajaxScript) && req.acceptsJavaScript_? - } - } - - val sessionIdCalc = new SessionIdCalc(req) - - val resp: Box[LiftResponse] = try { - // if the application is shutting down, return a 404 - if (LiftRules.ending) { - LiftRules.notFoundOrIgnore(req, Empty) - } else if (!authPassed_?(req)) { - Full(LiftRules.authentication.unauthorizedResponse) - } else if (LiftRules.redirectAsyncOnSessionLoss && !hasSession(sessionIdCalc.id) && (isComet || isAjax)) { - val theId = sessionIdCalc.id - - // okay after 250 attempts to redirect, just ignore calls to the - // async URL. Why? Because there might be more than 1 - // async window open and we want to make sure they all close - if (recentlyChecked(theId) > 2500) { - Empty - } else { - val cmd = { - // back off more, the higher the count - Thread.sleep(5 * recentlyChecked(theId)) - if (isComet) - js.JE.JsRaw(LiftRules.noCometSessionCmd.vend.toJsCmd + ";lift_toWatch = {};").cmd - else - js.JE.JsRaw(LiftRules.noAjaxSessionCmd.vend.toJsCmd).cmd - } - - Full(new JsCommands(cmd :: Nil).toResponse) - } - } else if (S.statelessInit(req) { - // if the request is matched is defined in the stateless table, dispatch - tmpStatelessHolder = NamedPF.applyBox(req, - LiftRules.statelessDispatch.toList).map(_.apply() match { - case Full(a) => Full(LiftRules.convertResponse((a, Nil, S.responseCookies, req))) - case r => r - }) - tmpStatelessHolder.isDefined - }) { - val f = tmpStatelessHolder.openOrThrowException("This is a full box here, checked on previous line") - f match { - case Full(v) => Full(v) - case Empty => LiftRules.notFoundOrIgnore(req, Empty) - case f: Failure => Full(req.createNotFound(f)) - } - } else { - // otherwise do a stateful response - val liftSession = getLiftSession(req) - - def doSession(r2: Req, s2: LiftSession, continue: Box[() => Nothing]): () => Box[LiftResponse] = { - try { - S.init(r2, s2) { - dispatchStatefulRequest(S.request.openOrThrowException("I'm pretty sure this is a full box here"), liftSession, r2, continue) - } - } catch { - case cre: ContinueResponseException => - r2.destroyServletSession() - doSession(r2, getLiftSession(r2), Full(cre.continue)) - } - } - - val lzy: () => Box[LiftResponse] = doSession(req, liftSession, Empty) - - lzy() - } - } catch { - case foc: LiftFlowOfControlException => throw foc - case e: Exception if !e.getClass.getName.endsWith("RetryRequest") => S.runExceptionHandlers(req, e) - } - - tryo { - LiftRules.onEndServicing.toList.foreach(_(req, resp)) - } - - resp match { - case Full(EmptyResponse) => - true - - case Full(cresp) => - sendResponse(cresp, response, req) - true - - case _ => { - false - } - } - } - - private def dispatchStatefulRequest(req: Req, - liftSession: LiftSession, - originalRequest: Req, - continuation: Box[() => Nothing]): () => Box[LiftResponse] = { - val toMatch = req - - val dispatch: (Boolean, Box[LiftResponse]) = - NamedPF.find(toMatch, LiftRules.dispatchTable(req.request)) match { - case Full(pf) => - LiftSession.onBeginServicing.foreach(_(liftSession, req)) - val ret: (Boolean, Box[LiftResponse]) = - try { - try { - // run the continuation in the new session - // if there is a continuation - continuation match { - case Full(func) => { - func() - S.redirectTo("/") - } - case _ => // do nothing - } - - liftSession.runParams(req) - S.functionLifespan(true) { - pf(toMatch)() match { - case Full(v) => - (true, Full(LiftRules.convertResponse((liftSession.checkRedirect(v), Nil, - S.responseCookies, req)))) - - case Empty => - (true, LiftRules.notFoundOrIgnore(req, Full(liftSession))) - - case f: Failure => - (true, Full(liftSession.checkRedirect(req.createNotFound(f)))) - } - } - } catch { - case ite: java.lang.reflect.InvocationTargetException if (ite.getCause.isInstanceOf[ResponseShortcutException]) => - (true, Full(liftSession.handleRedirect(ite.getCause.asInstanceOf[ResponseShortcutException], req))) - - case rd: net.liftweb.http.ResponseShortcutException => (true, Full(liftSession.handleRedirect(rd, req))) - } - } finally { - if (S.functionMap.size > 0) { - liftSession.updateFunctionMap(S.functionMap, S.renderVersion, millis) - S.clearFunctionMap - } - liftSession.notices = S.getNotices - } - - LiftSession.onEndServicing.foreach(_(liftSession, req, - ret._2)) - ret - - case _ => (false, Empty) - } - - val wp = req.path.wholePath - - if (LiftRules.enableContainerSessions && !req.stateless_?) { - req.request.session - } - - def respToFunc(in: Box[LiftResponse]): () => Box[LiftResponse] = { - val ret = in.map(LiftRules.performTransform) - () => ret - } - - val toReturn: () => Box[LiftResponse] = - if (dispatch._1) { - respToFunc(dispatch._2) - } else if (wp.length == 3 && wp.head == LiftRules.cometPath && - wp(2) == LiftRules.cometScriptName()) { - respToFunc(LiftRules.serveCometScript(liftSession, req)) - } else if ((wp.length >= 1) && wp.head == LiftRules.cometPath) { - handleComet(req, liftSession, originalRequest) match { - case Left(x) => respToFunc(x) - case Right(x) => x - } - } else if (wp.length == 2 && wp.head == LiftRules.ajaxPath && - wp(1) == LiftRules.ajaxScriptName()) { - respToFunc(LiftRules.serveAjaxScript(liftSession, req)) - } else if (wp.length >= 1 && wp.head == LiftRules.ajaxPath) { - respToFunc(handleAjax(liftSession, req)) - } else { - respToFunc(liftSession.processRequest(req, continuation)) - } - - toReturn - } - - /** - * Tracks the two aspects of an AJAX version: the sequence number, - * whose sole purpose is to identify requests that are retries for the - * same resource, and pending requests, which indicates how many - * requests are still queued for this particular page version on the - * client. The latter is used to expire result data for sequence - * numbers that are no longer needed. - */ - private case class AjaxVersionInfo(renderVersion:String, sequenceNumber:Long, pendingRequests:Int) - private object AjaxVersions { - def unapply(ajaxPathPart: String) : Option[AjaxVersionInfo] = { - val separator = ajaxPathPart.indexOf("-") - if (separator > -1 && ajaxPathPart.length > separator + 2) - Some( - AjaxVersionInfo(ajaxPathPart.substring(0, separator), - java.lang.Long.parseLong(ajaxPathPart.substring(separator + 1, ajaxPathPart.length - 1), 36), - Integer.parseInt(ajaxPathPart.substring(ajaxPathPart.length - 1), 36)) - ) - else - None - } - } - /** - * Extracts two versions from a given AJAX path: - * - The RenderVersion, which is used for GC purposes. - * - The requestVersions, which let us determine if this is - * a request we've already dealt with or are currently dealing - * with (so we don't rerun the associated handler). See - * handleVersionedAjax for more. - * - * The requestVersion is passed to the function that is passed in. - */ - private def extractVersions[T](path: List[String])(f: (Box[AjaxVersionInfo]) => T): T = { - path match { - case ajaxPath :: AjaxVersions(versionInfo @ AjaxVersionInfo(renderVersion, _, _)) :: _ => - RenderVersion.doWith(renderVersion)(f(Full(versionInfo))) - case ajaxPath :: renderVersion :: _ => - RenderVersion.doWith(renderVersion)(f(Empty)) - case _ => f(Empty) - } - } - - /** - * Runs the actual AJAX processing. This includes handling __lift__GC, - * or running the parameters in the session. onComplete is run when the - * AJAX request has completed with a response that is meant for the - * user. In cases where the request is taking too long to respond, - * an LAFuture may be used to delay the real response (and thus the - * invocation of onComplete) while this function returns an empty - * response. - */ - private def runAjax(liftSession: LiftSession, - requestState: Req): Box[LiftResponse] = { - try { - requestState.param("__lift__GC") match { - case Full(_) => - liftSession.updateFuncByOwner(RenderVersion.get, millis) - Full(JavaScriptResponse(js.JsCmds.Noop)) - - case _ => - try { - val what = flatten(try { - liftSession.runParams(requestState) - } catch { - case ResponseShortcutException(_, Full(to), _) => - import js.JsCmds._ - List(RedirectTo(to)) - }) - - val what2 = what.flatMap { - case js: JsCmd => List(js) - case jv: JValue => List(jv) - case n: NodeSeq => List(n) - case js: JsCommands => List(js) - case r: LiftResponse => List(r) - case s => Nil - } - - val ret: LiftResponse = what2 match { - case (json: JsObj) :: Nil => JsonResponse(json) - case (jv: JValue) :: Nil => JsonResponse(jv) - case (js: JsCmd) :: xs => { - (JsCommands(S.noticesToJsCmd :: Nil) & - ((js :: xs).flatMap { - case js: JsCmd => List(js) - case _ => Nil - }.reverse) & - S.jsToAppend).toResponse - } - - case (n: Node) :: _ => XmlResponse(n) - case (ns: NodeSeq) :: _ => XmlResponse(Group(ns)) - case (r: LiftResponse) :: _ => r - case _ => JsCommands(S.noticesToJsCmd :: JsCmds.Noop :: S.jsToAppend).toResponse - } - - LiftRules.cometLogger.debug("AJAX Response: " + liftSession.uniqueId + " " + - LiftRules.cometLoggerStringSecurer(ret.toString)) - - Full(ret) - } finally { - if (S.functionMap.size > 0) { - liftSession.updateFunctionMap(S.functionMap, RenderVersion.get, millis) - S.clearFunctionMap - } - } - } - } catch { - case foc: LiftFlowOfControlException => throw foc - case e: Exception => S.runExceptionHandlers(requestState, e) - } - } - - // Retry requests will stop trying to wait for the original request to - // complete 500ms after the client's timeout. This is because, while - // we want the original thread to complete so that it can provide an - // answer for future retries, we don't want retries tying up resources - // when the client won't receive the response anyway. - private lazy val ajaxPostTimeout: Long = LiftRules.ajaxPostTimeout * 1000L + 500L - /** - * Kick off AJAX handling. Extracts relevant versions and handles the - * begin/end servicing requests. Then checks whether to wait on an - * existing request for this same version to complete or whether to - * do the actual processing. - */ - private def handleAjax(liftSession: LiftSession, - requestState: Req): Box[LiftResponse] = { - extractVersions(requestState.path.partPath) { versionInfo => - LiftRules.cometLogger.debug("AJAX Request: " + liftSession.uniqueId + " " + - LiftRules.cometLoggerStringSecurer(requestState.params.toString)) - tryo { - LiftSession.onBeginServicing.foreach(_(liftSession, requestState)) - } - - LiftRules.makeCometBreakoutDecision(liftSession, requestState) - - // Here, a Left[LAFuture] indicates a future that needs to be - // *satisfied*, meaning we will run the request processing. - // A Right[LAFuture] indicates a future we need to *wait* on, - // meaning we will return the result of whatever satisfies the - // future. - val nextAction:Either[LAFuture[Box[LiftResponse]], LAFuture[Box[LiftResponse]]] = - versionInfo match { - case Full(AjaxVersionInfo(_, handlerVersion, pendingRequests)) => - val renderVersion = RenderVersion.get - - liftSession.withAjaxRequests { currentAjaxRequests => - // Create a new future, put it in the request list, and return - // the associated info with the future that needs to be - // satisfied by the current request handler. - def newRequestInfo = { - val info = AjaxRequestInfo(handlerVersion, new LAFuture[Box[LiftResponse]], millis) - - val existing = currentAjaxRequests.getOrElseUpdate(renderVersion, Nil) - currentAjaxRequests += (renderVersion -> (info :: existing)) - - info - } - - val infoList = currentAjaxRequests.get(renderVersion) - val (requestInfo, result) = - infoList - .flatMap { entries => - entries - .find(_.requestVersion == handlerVersion) - .map { entry => - (entry, Right(entry.responseFuture)) - } - } - .getOrElse { - val entry = newRequestInfo - - (entry, Left(entry.responseFuture)) - } - - // If there are no other pending requests, we can - // invalidate all the render version's AJAX entries except - // for the current one, as the client is no longer looking - // to retry any of them. - if (pendingRequests == 0) { - // Satisfy anyone waiting on futures for invalid - // requests with a failure. - for { - list <- infoList - entry <- list if entry.requestVersion != handlerVersion - } { - entry.responseFuture.satisfy(Failure("Request no longer pending.")) - } - - currentAjaxRequests += (renderVersion -> List(requestInfo)) - } - - result - } - - case _ => - // Create a future that processes the ajax response - // immediately. This runs if we don't have a handler - // version, which happens in cases like AJAX requests for - // Lift GC that don't go through the de-duping pipeline. - // Because we always return a Left here, the ajax processing - // always runs for this type of request. - Left(new LAFuture[Box[LiftResponse]]) - } - - val ret:Box[LiftResponse] = - nextAction match { - case Left(future) => - val result = runAjax(liftSession, requestState) map CachedResponse - - if (result.exists(_.failed_?)) { - // The request failed. The client will retry it, so - // remove it from the list of current Ajax requests that - // needs to be satisfied so we re-process the next request - // from scratch - liftSession.withAjaxRequests { currentAjaxRequests => - currentAjaxRequests.remove(RenderVersion.get) - } - } - - future.satisfy(result) - result - - case Right(future) => - val ret = future.get(ajaxPostTimeout) openOr Failure("AJAX retry timeout.") - - ret - } - - tryo { - LiftSession.onEndServicing.foreach(_(liftSession, requestState, ret)) - } - - ret - } - } - - /** - * An actor that manages continuations from container (Jetty style) - */ - class ContinuationActor(request: Req, session: LiftSession, - actors: List[(LiftCometActor, Long)], - onBreakout: List[AnswerRender] => Unit) extends LiftActor { - private var answers: List[AnswerRender] = Nil - private var done = false - val seqId = Helpers.nextNum - - def messageHandler = { - case BeginContinuation if !done => - - val sendItToMe: AnswerRender => Unit = ah => this ! ah - - actors.foreach { - case (act, when) => act ! Listen(when, ListenerId(seqId), sendItToMe) - } - - case ar: AnswerRender => - answers ::= ar - LAPinger.schedule(this, BreakOut(), 5 millis) - - case BreakOut() if !done => - done = true - session.exitComet(this) - actors.foreach { - case (act, _) => tryo(act ! Unlisten(ListenerId(seqId))) - } - onBreakout(answers) - - case _ => - } - - // override def toString = "Actor dude " + seqId - } - - private object BeginContinuation - - private lazy val cometTimeout: Long = (LiftRules.cometRequestTimeout openOr 120) * 1000L - - private def setupContinuation(request: Req, session: LiftSession, actors: List[(LiftCometActor, Long)]): Any = { - val cont = new ContinuationActor(request, session, actors, - answers => { - request.request.resume( - (request, S.init(request, session) - (LiftRules.performTransform( - convertAnswersToCometResponse(session, - answers.toList, actors)))))}) - - - try { - session.enterComet(cont -> request) - - LAPinger.schedule(cont, BreakOut(), TimeSpan(cometTimeout)) - - request.request.suspend(cometTimeout + 2000L) - } finally { - cont ! BeginContinuation - } - } - - private def handleComet(requestState: Req, sessionActor: LiftSession, originalRequest: Req): Either[Box[LiftResponse], () => Box[LiftResponse]] = { - val actors: List[(LiftCometActor, Long)] = - requestState.params.toList.flatMap { - case (name, when) => - sessionActor.getAsyncComponent(name).toList.map(c => (c, toLong(when))) - } - - if (SessionMaster.isDead(sessionActor.uniqueId) || !sessionActor.stateful_? || actors.isEmpty) { - Left(Full(JsCommands(List(LiftRules.noCometSessionCmd.vend, js.JE.JsRaw("lift_toWatch = {};").cmd)).toResponse)) - } else requestState.request.suspendResumeSupport_? match { - case true => { - setupContinuation(requestState, sessionActor, actors) - Left(Full(EmptyResponse)) - } - - case _ => { - Right(handleNonContinuationComet(requestState, sessionActor, actors, originalRequest)) - } - } - } - - private def convertAnswersToCometResponse(session: LiftSession, ret: Seq[AnswerRender], actors: List[(LiftCometActor, Long)]): LiftResponse = { - val ret2: List[AnswerRender] = ret.toList - val jsUpdateTime = ret2.map(ar => "if (lift_toWatch['" + ar.who.uniqueId + "'] !== undefined) lift_toWatch['" + ar.who.uniqueId + "'] = '" + ar.when + "';").mkString("\n") - val jsUpdateStuff = ret2.map { - ar => { - val ret = ar.response.toJavaScript(session, ar.displayAll) - - if (!S.functionMap.isEmpty) { - session.updateFunctionMap(S.functionMap, - ar.who.uniqueId, ar.when) - S.clearFunctionMap - } - - ret - } - } - - actors foreach (_._1 ! ClearNotices) - - val addl: List[JsCmd] = - (for { - req <- S.request - rendVer <- extractRenderVersion(req.path.partPath) - } yield RenderVersion.doWith(rendVer) { - S.jsToAppend - }) openOr Nil - - (new JsCommands(JsCmds.Run(jsUpdateTime) :: jsUpdateStuff ::: addl)).toResponse - } - - private def extractRenderVersion(in: List[String]): Box[String] = in match { - case _ :: _ :: _ :: rv :: _ => Full(rv) - case _ => Empty - } - - private def handleNonContinuationComet(request: Req, session: LiftSession, actors: List[(LiftCometActor, Long)], - originalRequest: Req): () => Box[LiftResponse] = () => { - val f = new LAFuture[List[AnswerRender]] - val cont = new ContinuationActor(request, session, actors, - answers => f.satisfy(answers)) - - try { - cont ! BeginContinuation - - session.enterComet(cont -> request) - - LAPinger.schedule(cont, BreakOut(), TimeSpan(cometTimeout)) - - val ret2 = f.get(cometTimeout + 50L) openOr Nil - - Full(S.init(originalRequest, session) { - convertAnswersToCometResponse(session, ret2, actors) - }) - } finally { - session.exitComet(cont) - } - } - - val dumpRequestResponse = Props.getBool("dump.request.response", false) - - private def logIfDump(request: Req, response: BasicResponse) { - if (dumpRequestResponse) { - val toDump = request.uri + "\n" + - LiftRules.cometLoggerStringSecurer(request.params.toString) + "\n" + - response.headers + "\n" + - ( - response match { - case InMemoryResponse(data, _, _, _) => - LiftRules.cometLoggerStringSecurer(new String(data, "UTF-8")) - case _ => "data" - } - ) - - logger.trace(toDump) - } - } - - /** - * Sends the { @code HTTPResponse } to the browser using data from the - * { @link Response } and { @link Req }. - */ - private[http] def sendResponse(liftResp: LiftResponse, response: HTTPResponse, request: Req) { - def fixHeaders(headers: List[(String, String)]) = headers map ((v) => v match { - case ("Location", uri) => - val u = request - (v._1, ( - (for ( - updated <- Full((if (!LiftRules.excludePathFromContextPathRewriting.vend(uri)) u.contextPath else "") + uri).filter(ignore => uri.startsWith("/")); - rwf <- URLRewriter.rewriteFunc) yield rwf(updated)) openOr uri - )) - case _ => v - }) - - def pairFromRequest(req: Req): (Box[Req], Box[String]) = { - val acceptHeader = for (innerReq <- Box.legacyNullTest(req.request); - accept <- innerReq.header("Accept")) yield accept - - (Full(req), acceptHeader) - } - - val resp = liftResp.toResponse - - logIfDump(request, resp) - - def insureField(headers: List[(String, String)], toInsure: List[(String, String)]): List[(String, String)] = { - val org = Map(headers: _*) - - toInsure.foldLeft(org) { - case (map, (key, value)) => - if (map.contains(key)) map - else map + (key -> value) - }.toList - - } - - - val len = resp.size - // insure that certain header fields are set - val header = if (resp.code == 304 || resp.code == 303) - fixHeaders(resp.headers) - else - insureField(fixHeaders(resp.headers), - LiftRules.defaultHeaders(NodeSeq.Empty -> request) ::: - /* List(("Content-Type", - LiftRules.determineContentType(pairFromRequest(request)))) ::: */ - (if (len >= 0) List(("Content-Length", len.toString)) else Nil)) - - LiftRules.beforeSend.toList.foreach(f => tryo(f(resp, response, header, Full(request)))) - // set the cookies - response.addCookies(resp.cookies) - - // send the response - response.addHeaders(header.map { - case (name, value) => HTTPParam(name, value) - }) - LiftRules.supplimentalHeaders(response) - - liftResp match { - case ResponseWithReason(_, reason) => response setStatusWithReason (resp.code, reason) - case _ => response setStatus resp.code - } - - try { - resp match { - case EmptyResponse => - case InMemoryResponse(bytes, _, _, _) => - response.outputStream.write(bytes) - response.outputStream.flush() - - case StreamingResponse(stream, endFunc, _, _, _, _) => - try { - var len = 0 - val ba = new Array[Byte](8192) - val os = response.outputStream - stream match { - case jio: java.io.InputStream => len = jio.read(ba) - case stream => len = stream.read(ba) - } - while (len >= 0) { - if (len > 0) os.write(ba, 0, len) - stream match { - case jio: java.io.InputStream => len = jio.read(ba) - case stream => len = stream.read(ba) - } - } - response.outputStream.flush() - } finally { - endFunc() - } - - case OutputStreamResponse(out, _, _, _, _) => - out(response.outputStream) - response.outputStream.flush() - } - } catch { - case e: java.io.IOException => // ignore IO exceptions... they happen - } - - LiftRules.afterSend.toList.foreach(f => tryo(f(resp, response, header, Full(request)))) - } -} - -import provider.servlet._ - -class LiftFilter extends ServletFilterProvider - -private class SessionIdCalc(req: Req) { - private val CometPath = LiftRules.cometPath - lazy val id: Box[String] = { - val cometId = req.path.wholePath match { - case CometPath :: _ :: id :: _ if id != LiftRules.cometScriptName() => - req.request.sessionId match { - case Full(id2) if id2 == id => Full(id) - case eb: EmptyBox => Full(id) - case _ => Empty - } - - case _ => req.request.sessionId - } - cometId - } -} diff --git a/web/webkit/src/main/scala/net/liftweb/sitemap/XmlMenu.scala b/web/webkit/src/main/scala/net/liftweb/sitemap/XmlMenu.scala new file mode 100644 index 0000000000..9ebad69485 --- /dev/null +++ b/web/webkit/src/main/scala/net/liftweb/sitemap/XmlMenu.scala @@ -0,0 +1,45 @@ +/* + * Copyright 2009-2011 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 sitemap + +import scala.xml.NodeSeq + + +/** + * The beginning of an experiment to provide a capability to define + * the sitemap menu in xml. Currently pretty limited. + * menu elements have a name attribute, and contain text and link + * elements, and optionally multiple menu elemnts. + * The contents of the text element is the menu display x(ht)ml, + * and the contents of the link element is an array of + * path components in JSON array syntax. + * + * @author nafg + */ + +/* +object XmlMenu { + def apply(xml: NodeSeq): Seq[Menu] = for(node<-xml) yield node match { + case m @ { children @ _* } => + val name = m \ "@name" text + val text = NodeSeq.fromSeq((m \ "text" iterator) flatMap {_.child.iterator} toSeq) + val link = util.JSONParser.parse(m \ "link" text).get.asInstanceOf[List[Any]].map(_.asInstanceOf[String]) + Menu(Loc(name, link, text), apply(m \ "menu") : _*) + } +}*/ + From 273650496dae7ddc5b4bfec61fcfa1ab7a855e62 Mon Sep 17 00:00:00 2001 From: Diego Medina Date: Thu, 7 Nov 2013 00:14:56 -0500 Subject: [PATCH 0629/1949] Revert "Added WiringUI to a package" This reverts commit e771cf81e183867dfaf8e4d565fb79b858cde4a8. Conflicts: web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala --- .../scala/net/liftweb/http/LiftRules.scala | 22 +- .../scala/net/liftweb/http/LiftServlet.scala | 240 ++++++++---- .../scala/net/liftweb/http/WiringUI.scala | 362 ++++++++++++++++++ .../main/scala/net/liftweb/http/package.scala | 12 +- .../javascript/JavaScriptContext.scala | 1 - 5 files changed, 559 insertions(+), 78 deletions(-) create mode 100644 web/webkit/src/main/scala/net/liftweb/http/WiringUI.scala diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 4665150c98..5852c5d199 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -122,7 +122,7 @@ object LiftRules extends LiftRulesMocker { LiftRulesMocker.calcLiftRulesInstance() } else prodInstance - type DispatchPF = PartialFunction[Req, () => Box[LiftResponse]] + type DispatchPF = PartialFunction[Req, () => Box[LiftResponse]]; /** * A partial function that allows processing of any attribute on an Elem @@ -141,6 +141,7 @@ object LiftRules extends LiftRulesMocker { */ type StatelessTestPF = PartialFunction[List[String], Boolean] + /** * The test between the path of a request, the HTTP request, and whether that path * should result in stateless servicing of that path @@ -148,27 +149,16 @@ object LiftRules extends LiftRulesMocker { type StatelessReqTestPF = PartialFunction[StatelessReqTest, Boolean] type RewritePF = PartialFunction[RewriteRequest, RewriteResponse] - type SnippetPF = PartialFunction[List[String], NodeSeq => NodeSeq] - type LiftTagPF = PartialFunction[(String, Elem, MetaData, NodeSeq, String), NodeSeq] - type URINotFoundPF = PartialFunction[(Req, Box[Failure]), NotFound] - type URLDecoratorPF = PartialFunction[String, String] - type SnippetDispatchPF = PartialFunction[String, DispatchSnippet] - type ViewDispatchPF = PartialFunction[List[String], Either[() => Box[NodeSeq], LiftView]] - type HttpAuthProtectedResourcePF = PartialFunction[Req, Box[Role]] - type ExceptionHandlerPF = PartialFunction[(Props.RunModes.Value, Req, Throwable), LiftResponse] - type ResourceBundleFactoryPF = PartialFunction[(String, Locale), ResourceBundle] - type SplitSuffixPF = PartialFunction[List[String], (List[String], String)] - type CometCreationPF = PartialFunction[CometCreationInfo, LiftCometActor] /** * A partial function that allows the application to define requests that should be @@ -176,6 +166,14 @@ object LiftRules extends LiftRulesMocker { */ type LiftRequestPF = PartialFunction[Req, Boolean] + /* + private[this] var _doneBoot = false + private[http] def doneBoot = _doneBoot + + private[http] def doneBoot_=(in: Boolean) {_doneBoot = in} +*/ + + /** * Holds the failure information when a snippet can not be executed. */ diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala index 58d1f96625..8117614f60 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala @@ -208,62 +208,76 @@ class LiftServlet extends Loggable { id.flatMap(recent.get).openOr(0) } - /** - * Service the HTTP request - */ - def doService(req: Req, response: HTTPResponse): Boolean = { - var tmpStatelessHolder: Box[Box[LiftResponse]] = Empty + trait ProcessingStep { + def process(req: Req): Box[LiftResponse] - tryo { - LiftRules.onBeginServicing.toList.foreach(_(req)) - } + def processFunc: (Req) => Box[LiftResponse] = process _ + } - def hasSession(idb: Box[String]): Boolean = { - idb.flatMap { - id => - registerRecentlyChecked(id) - SessionMaster.getSession(id, Empty) - }.isDefined - } + /** To save memory these are only created once and should just be holders for functions **/ - val wp = req.path.wholePath - val pathLen = wp.length - def isComet: Boolean = { - if (pathLen < 2) false - else { - val kindaComet = wp.head == LiftRules.cometPath - val cometScript = (pathLen >= 3 && kindaComet && - wp(2) == LiftRules.cometScriptName()) - - (kindaComet && !cometScript) && req.acceptsJavaScript_? + object ShuttingDown extends ProcessingStep { + + def notFoundOrIgnore(req: Req, session: Box[LiftSession]): Box[LiftResponse] = { + if (LiftRules.passNotFoundToChain) { + net.liftweb.common.Failure("Not found") + } else { + Full( + session.map(_.checkRedirect(req.createNotFound)) + .getOrElse(req.createNotFound) + ) } } - def isAjax: Boolean = { - if (pathLen < 2) false - else { - val kindaAjax = wp.head == LiftRules.ajaxPath - val ajaxScript = kindaAjax && - wp(1) == LiftRules.ajaxScriptName() - (kindaAjax && !ajaxScript) && req.acceptsJavaScript_? - } + def process(req: Req) = { + if(LiftRules.ending) + notFoundOrIgnore(req,Empty) + else + Empty } - val sessionIdCalc = new SessionIdCalc(req) + } - val resp: Box[LiftResponse] = try { - // if the application is shutting down, return a 404 - if (LiftRules.ending) { - LiftRules.notFoundOrIgnore(req, Empty) - } else if (!authPassed_?(req)) { + object CheckAuth extends ProcessingStep { + + def authPassed_?(req: Req): Boolean = { + + val checkRoles: (Role, List[Role]) => Boolean = { + case (resRole, roles) => (false /: roles)((l, r) => l || resRole.isChildOf(r.name)) + } + + val role = NamedPF.applyBox(req, LiftRules.httpAuthProtectedResource.toList) + role.map(_ match { + case Full(r) => + LiftRules.authentication.verified_?(req) match { + case true => checkRoles(r, userRoles.get) + case _ => false + } + case _ => LiftRules.authentication.verified_?(req) + }) openOr true + } + + def process(req: Req) = + if(!authPassed_?(req)) Full(LiftRules.authentication.unauthorizedResponse) - } else if (LiftRules.redirectAsyncOnSessionLoss && !hasSession(sessionIdCalc.id) && (isComet || isAjax)) { + else + Empty + + } + + object SessionLossCheck extends ProcessingStep { + + def process(req: Req): Box[LiftResponse] = { + val (isComet, isAjax) = cometOrAjax_?(req) + val sessionIdCalc = new SessionIdCalc(req) + + if (LiftRules.redirectAsyncOnSessionLoss && !sessionExists_?(sessionIdCalc.id) && (isComet || isAjax)) { val theId = sessionIdCalc.id // okay after 2 attempts to redirect, just ignore calls to the // async URL if (recentlyChecked(theId) > 1) { - Empty + net.liftweb.common.Failure("Too many attempts") } else { val cmd = if (isComet) @@ -273,7 +287,60 @@ class LiftServlet extends Loggable { Full(new JsCommands(cmd :: Nil).toResponse) } - } else if (S.statelessInit(req) { + } else { + Empty + } + } + + def reqHasSession(req: Req): Boolean = { + val sessionIdCalc = new SessionIdCalc(req) + !sessionExists_?(sessionIdCalc.id) + } + + def sessionExists_?(idb: Box[String]): Boolean = { + idb.flatMap { + id => + registerRecentlyChecked(id) + SessionMaster.getSession(id, Empty) + }.isDefined + } + + def cometOrAjax_?(req: Req): (Boolean, Boolean) = { + + val wp = req.path.wholePath + val pathLen = wp.length + + def isComet: Boolean = { + if (pathLen < 2) false + else { + val kindaComet = wp.head == LiftRules.cometPath + val cometScript = (pathLen >= 3 && kindaComet && + wp(2) == LiftRules.cometScriptName()) + + (kindaComet && !cometScript) && req.acceptsJavaScript_? + } + } + def isAjax: Boolean = { + if (pathLen < 2) false + else { + val kindaAjax = wp.head == LiftRules.ajaxPath + val ajaxScript = kindaAjax && + wp(1) == LiftRules.ajaxScriptName() + + (kindaAjax && !ajaxScript) && req.acceptsJavaScript_? + } + } + (isComet, isAjax) + } + + } + + object StatelessResponse extends ProcessingStep { + + def process(req: Req): Box[LiftResponse] = { + var tmpStatelessHolder: Box[Box[LiftResponse]] = Empty + + if(S.statelessInit(req) { // if the request is matched is defined in the stateless table, dispatch tmpStatelessHolder = NamedPF.applyBox(req, LiftRules.statelessDispatch.toList).map(_.apply() match { @@ -286,28 +353,75 @@ class LiftServlet extends Loggable { f match { case Full(v) => Full(v) case Empty => LiftRules.notFoundOrIgnore(req, Empty) - case f: Failure => Full(req.createNotFound(f)) + case f: net.liftweb.common.Failure => Full(req.createNotFound(f)) } } else { - // otherwise do a stateful response - val liftSession = getLiftSession(req) + Empty + } + } + } - def doSession(r2: Req, s2: LiftSession, continue: Box[() => Nothing]): () => Box[LiftResponse] = { - try { - S.init(r2, s2) { - dispatchStatefulRequest(S.request.openOrThrowException("I'm pretty sure this is a full box here"), liftSession, r2, continue) - } - } catch { - case cre: ContinueResponseException => - r2.destroyServletSession() - doSession(r2, getLiftSession(r2), Full(cre.continue)) + object StatefulResponse extends ProcessingStep { + + def process(req: Req) = { + // otherwise do a stateful response + val liftSession = getLiftSession(req) + + def doSession(r2: Req, s2: LiftSession, continue: Box[() => Nothing]): () => Box[LiftResponse] = { + try { + S.init(r2, s2) { + dispatchStatefulRequest(S.request.openOrThrowException("I'm pretty sure this is a full box here"), liftSession, r2, continue) } + } catch { + case cre: ContinueResponseException => + r2.destroyServletSession() + doSession(r2, getLiftSession(r2), Full(cre.continue)) } + } - val lzy: () => Box[LiftResponse] = doSession(req, liftSession, Empty) + val lzy: () => Box[LiftResponse] = doSession(req, liftSession, Empty) + + lzy() + } + } - lzy() + /** + * This is the processing pipeline for all lift requests. + * Basically each of these takes a Req and returns either a + * Full(Response) - in which case return + * Empty - Go to the next handler + * Failure - short circuit and return + * + */ + val processingPipeline: Seq[ProcessingStep] = + Seq( + ShuttingDown, + CheckAuth, + SessionLossCheck, + StatelessResponse, + StatefulResponse + ) + + /** + * Service the HTTP request + */ + def doService(req: Req, response: HTTPResponse): Boolean = { + + tryo { + LiftRules.onBeginServicing.toList.foreach(_(req)) + } + + def stepThroughPipeline(steps: Seq[ProcessingStep]): Box[LiftResponse] = { + //Seems broken but last step always hits + steps.head.process(req) match { + case Empty => stepThroughPipeline(steps.tail) + case a@_ => a } + } + + /* Go through the pipeline and send response if full **/ + val resp: Box[LiftResponse] = try { + stepThroughPipeline(processingPipeline) } catch { case foc: LiftFlowOfControlException => throw foc case e: Exception if !e.getClass.getName.endsWith("RetryRequest") => S.runExceptionHandlers(req, e) @@ -364,8 +478,8 @@ class LiftServlet extends Loggable { case Empty => (true, LiftRules.notFoundOrIgnore(req, Full(liftSession))) - case f: Failure => - (true, Full(liftSession.checkRedirect(req.createNotFound(f)))) + case f: net.liftweb.common.Failure => + (true, net.liftweb.common.Full(liftSession.checkRedirect(req.createNotFound(f)))) } } } catch { @@ -488,7 +602,7 @@ class LiftServlet extends Loggable { liftSession.runParams(requestState) } catch { case ResponseShortcutException(_, Full(to), _) => - import js.JsCmds._ + import net.liftweb.http.js.JsCmds._ List(RedirectTo(to)) }) @@ -605,7 +719,7 @@ class LiftServlet extends Loggable { list <- infoList entry <- list if entry.requestVersion != handlerVersion } { - entry.responseFuture.satisfy(Failure("Request no longer pending.")) + entry.responseFuture.satisfy(net.liftweb.common.Failure("Request no longer pending.")) } currentAjaxRequests += (renderVersion -> List(requestInfo)) @@ -643,7 +757,7 @@ class LiftServlet extends Loggable { result case Right(future) => - val ret = future.get(ajaxPostTimeout) openOr Failure("AJAX retry timeout.") + val ret = future.get(ajaxPostTimeout) openOr net.liftweb.common.Failure("AJAX retry timeout.") ret } @@ -917,9 +1031,7 @@ class LiftServlet extends Loggable { } } -import provider.servlet._ - -class LiftFilter extends ServletFilterProvider +import net.liftweb.http.provider.servlet._ private class SessionIdCalc(req: Req) { private val CometPath = LiftRules.cometPath diff --git a/web/webkit/src/main/scala/net/liftweb/http/WiringUI.scala b/web/webkit/src/main/scala/net/liftweb/http/WiringUI.scala new file mode 100644 index 0000000000..fdbb1b06c3 --- /dev/null +++ b/web/webkit/src/main/scala/net/liftweb/http/WiringUI.scala @@ -0,0 +1,362 @@ +/* + * Copyright 2010-2011 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._ +import util._ + +import js._ +import JsCmds._ +import scala.xml.{NodeSeq, Elem, Text} + +/** + * Surface a user interface on top of Wiring + */ +object WiringUI { + /** + * Given a NodeSeq, a Cell and a function that can generate + * a NodeSeq => NodeSeq from the cell's value, register the + * postPageJavaScript that will update the element with + * a new value. + * + * @param in the NodeSeq that contains the view markup + * @param cell the cell to associate with + * @param f the function that performs the drawing + * + * @return the mutated NodeSeq (an id attribute may be added if + * there's none already defined) + */ + def apply[T](in: NodeSeq, cell: Cell[T])(f: T => NodeSeq => NodeSeq): NodeSeq = toNode(in, cell)((t, ns) => f(t)(ns)) + + /** + * Given a Cell and a function that can generate + * a NodeSeq => NodeSeq from the cell's value, return a function that + * takes a NodeSeq and registers the + * postPageJavaScript that will update the element with + * a new value. + * + * @param cell the cell to associate with + * @param f the function that performs the drawing + * + * @return a function that mutates NodeSeq (an id attribute may be added if + * there's none already defined) + */ + def apply[T](cell: Cell[T])(f: T => NodeSeq => NodeSeq): NodeSeq => NodeSeq = (in: NodeSeq) => toNode(in, cell)((t, ns) => f(t)(ns)) + + /** + * Given a NodeSeq, a Cell and a function that can generate + * a NodeSeq => NodeSeq from the cell's value, register the + * postPageJavaScript that will update the element with + * a new value. + * + * @param in the NodeSeq that contains the view markup + * @param cell the cell to associate with + * + * @param jsEffect a function that wraps the SetHtml JsCmd so + * you can, for example, fade out the old value, set the new value and + * fade it in. The first parameter is the id of the element, the + * second is a flag that's true if this is the first time the element is + * being rendered (you might want to skip effects for the inital page load), + * and the third parameter is the SetHtml JavaScript code. + * + * @param f the function that performs the drawing + * + * @return the mutated NodeSeq (an id attribute may be added if + * there's none already defined) + */ + def apply[T](in: NodeSeq, cell: Cell[T], jsEffect: (String, Boolean, JsCmd) => JsCmd)(f: T => NodeSeq => NodeSeq): NodeSeq = toNode(in, cell, jsEffect)((t, ns) => f(t)(ns)) + + /** + * Given a Cell and a function that can generate + * a NodeSeq => NodeSeq from the cell's value, return a function that with a NodeSeq + * will register the + * postPageJavaScript that will update the element with + * a new value. + * + * @param cell the cell to associate with + * + * @param jsEffect a function that wraps the SetHtml JsCmd so + * you can, for example, fade out the old value, set the new value and + * fade it in. The first parameter is the id of the element, the + * second is a flag that's true if this is the first time the element is + * being rendered (you might want to skip effects for the inital page load), + * and the third parameter is the SetHtml JavaScript code. + * + * @param f the function that performs the drawing + * + * @return the mutated NodeSeq (an id attribute may be added if + * there's none already defined) + */ + def apply[T](cell: Cell[T], jsEffect: (String, Boolean, JsCmd) => JsCmd)(f: T => NodeSeq => NodeSeq): NodeSeq => NodeSeq = + in => toNode(in, cell, jsEffect)((t, ns) => f(t)(ns)) + + /** + * Given a NodeSeq, a Cell and a function that can generate + * a NodeSeq from the cell's value and the template value, register the + * postPageJavaScript that will update the element with + * a new value. + * + * @param in the NodeSeq that contains the view markup + * @param cell the cell to associate with + * @param f the function that performs the drawing + * + * @return the mutated NodeSeq (an id attribute may be added if + * there's none already defined) + */ + def toNode[T](in: NodeSeq, cell: Cell[T])(f: (T, NodeSeq) => NodeSeq): NodeSeq = toNode(in, cell, (id, first, js) => js)(f) + + def history[T](cell: Cell[T])(f: (Box[T], T, NodeSeq) => JsCmd): NodeSeq => NodeSeq = + in => { + val myElem: Elem = in.find { + case e: Elem => true + case _ => false + }.map(_.asInstanceOf[Elem]).getOrElse({in}) + + + addHistJsFunc(cell, (old: Box[T], nw: T) => f(old, nw, in)) + + new Elem(myElem.prefix, + myElem.label, + myElem.attributes, + myElem.scope) + } + + + /** + * Given a NodeSeq, a Cell and a function that can generate + * a NodeSeq from the cell's value and the template value, register the + * postPageJavaScript that will update the element with + * a new value. + * + * @param in the NodeSeq that contains the view markup + * @param cell the cell to associate with + * @param f the function that performs the drawing + * + * @return the mutated NodeSeq (an id attribute may be added if + * there's none already defined) + */ + def toNode[T](cell: Cell[T])(f: (T, NodeSeq) => NodeSeq): NodeSeq => NodeSeq = in => toNode(in, cell, (id, first, js) => js)(f) + + /** + * Given a NodeSeq, a Cell register the + * postPageJavaScript that will update the element with + * a new value. + * + * @param in the NodeSeq that contains the view markup + * @param cell the cell to associate with + * + * @return the mutated NodeSeq (an id attribute may be added if + * there's none already defined) + */ + def asText[T](in: NodeSeq, cell: Cell[T]): NodeSeq = + toNode(in, cell, (id, first, js) => js)((t, ns) => Text(t.toString)) + + /** + * Given a Cell register the + * postPageJavaScript that will update the element with + * a new value. + * + * @param cell the cell to associate with + * + * @return a function that will mutate the NodeSeq (an id attribute may be added if + * there's none already defined) + */ + def asText[T](cell: Cell[T]): NodeSeq => NodeSeq = + in => toNode(in, cell, (id, first, js) => js)((t, ns) => Text(t.toString)) + + /** + * Given a NodeSeq, a Cell register the + * postPageJavaScript that will update the element with + * a new value. + * + * @param in the NodeSeq that contains the view markup + * @param cell the cell to associate with + * + * @param jsEffect a function that wraps the SetHtml JsCmd so + * you can, for example, fade out the old value, set the new value and + * fade it in. The first parameter is the id of the element, the + * second is a flag that's true if this is the first time the element is + * being rendered (you might want to skip effects for the inital page load), + * and the third parameter is the SetHtml JavaScript code. + * + * @return the mutated NodeSeq (an id attribute may be added if + * there's none already defined) + */ + def asText[T](in: NodeSeq, cell: Cell[T], jsEffect: (String, Boolean, JsCmd) => JsCmd): NodeSeq = + toNode(in, cell, jsEffect)((t, ns) => Text(t.toString)) + + /** + * Given a NodeSeq, a Cell register the + * postPageJavaScript that will update the element with + * a new value. + * + * @param in the NodeSeq that contains the view markup + * @param cell the cell to associate with + * + * @param jsEffect a function that wraps the SetHtml JsCmd so + * you can, for example, fade out the old value, set the new value and + * fade it in. The first parameter is the id of the element, the + * second is a flag that's true if this is the first time the element is + * being rendered (you might want to skip effects for the inital page load), + * and the third parameter is the SetHtml JavaScript code. + * + * @return a function that will mutate the NodeSeq (an id attribute may be added if + * there's none already defined) + */ + def asText[T](cell: Cell[T], jsEffect: (String, Boolean, JsCmd) => JsCmd): NodeSeq => NodeSeq = + in => toNode(in, cell, jsEffect)((t, ns) => Text(t.toString)) + + /** + * Given a NodeSeq, a Cell and a function that can generate + * a NodeSeq from the cell's value and the template value, register the + * postPageJavaScript that will update the element with + * a new value. + * + * @param in the NodeSeq that contains the view markup + * @param cell the cell to associate with + * + * @param jsEffect a function that wraps the SetHtml JsCmd so + * you can, for example, fade out the old value, set the new value and + * fade it in. The first parameter is the id of the element, the + * second is a flag that's true if this is the first time the element is + * being rendered (you might want to skip effects for the inital page load), + * and the third parameter is the SetHtml JavaScript code. + * + * @param f the function that performs the drawing + * + * @return the mutated NodeSeq (an id attribute may be added if + * there's none already defined) + */ + def toNode[T](in: NodeSeq, cell: Cell[T], jsEffect: (String, Boolean, JsCmd) => JsCmd)(f: (T, NodeSeq) => NodeSeq): NodeSeq = { + val myElem: Elem = in.find { + case e: Elem => true + case _ => false + }.map(_.asInstanceOf[Elem]).getOrElse({in}) + + val (elem: Elem, id: String) = Helpers.findOrAddId(myElem) + addJsFunc(cell, (t: T, first: Boolean) => { + jsEffect(id, first, SetHtml(id, f(t, elem.child))) + }) + elem + } + + /** + * Given a Cell and a function that can generate + * a NodeSeq from the cell's value and the template value, register the + * postPageJavaScript that will update the element with + * a new value. + * + * @param cell the cell to associate with + * + * @param jsEffect a function that wraps the SetHtml JsCmd so + * you can, for example, fade out the old value, set the new value and + * fade it in. The first parameter is the id of the element, the + * second is a flag that's true if this is the first time the element is + * being rendered (you might want to skip effects for the inital page load), + * and the third parameter is the SetHtml JavaScript code. + * + * @param f the function that performs the drawing + * + * @return the mutated NodeSeq (an id attribute may be added if + * there's none already defined) + */ + def toNode[T](cell: Cell[T], jsEffect: (String, Boolean, JsCmd) => JsCmd)(f: (T, NodeSeq) => NodeSeq): NodeSeq => NodeSeq = + in => { + val myElem: Elem = in.find { + case e: Elem => true + case _ => false + }.map(_.asInstanceOf[Elem]).getOrElse({in}) + + val (elem: Elem, id: String) = Helpers.findOrAddId(myElem) + addJsFunc(cell, (t: T, first: Boolean) => { + jsEffect(id, first, SetHtml(id, f(t, elem.child))) + }) + elem + } + + /** + * Associate a Cell and a function that converts from the + * cell's value to a JavaScript command to be sent to the + * browser as part of the page's post-processing. + * + * @param cell the cell to associate the JavaScript to + * @param f the function that takes the cell's value and a flag indicating + * if this is the first time + */ + def addJsFunc[T](cell: Cell[T], f: (T, Boolean) => JsCmd) { + for { + cometActor <- S.currentCometActor + } cell.addDependent(cometActor) + + val trc = TransientRequestCell(cell) + var lastTime: Long = 0L + var lastValue: T = null.asInstanceOf[T] + for { + sess <- S.session + } sess.addPostPageJavaScript(() => { + val (value, ct) = trc.get + val first = lastTime == 0L + if (first || (ct > lastTime && value != lastValue)) { + lastValue = value + lastTime = ct + f(value, first) + } else Noop + }) + } + + + /** + * Associate a Cell and a function that converts from the + * cell's value to a JavaScript command to be sent to the + * browser as part of the page's post-processing. + * + * @param cell the cell to associate the JavaScript to + * @param f the function that takes the cell's value and a flag indicating + * if this is the first time + */ + def addHistJsFunc[T](cell: Cell[T], f: (Box[T], T) => JsCmd) { + for { + cometActor <- S.currentCometActor + } cell.addDependent(cometActor) + + val trc = TransientRequestCell(cell) + var lastTime: Long = 0L + var lastValue: Box[T] = Empty + for { + sess <- S.session + } sess.addPostPageJavaScript(() => { + val (value, ct) = trc.get + val first = lastTime == 0L + if (first || (ct > lastTime && Full(value) != lastValue)) { + val oldValue = lastValue + lastValue = Full(value) + lastTime = ct + f(oldValue, value) + } else Noop + }) + } + +} + +/** + * Cache the value of the cell for the duration of the transient request + */ +private final case class TransientRequestCell[T](cell: Cell[T]) extends TransientRequestVar[(T, Long)](cell.currentValue) { + override val __nameSalt = Helpers.nextFuncName +} + diff --git a/web/webkit/src/main/scala/net/liftweb/http/package.scala b/web/webkit/src/main/scala/net/liftweb/http/package.scala index 9ec48491aa..36276d1cb4 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/package.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/package.scala @@ -23,7 +23,17 @@ import java.util.{ResourceBundle, Locale, Date} package object http { - object WiringUI extends net.liftweb.http.wiring.WiringUI + /* + type CssBoundLiftScreen = screen.CssBoundLiftScreen + type CssBoundScreen = screen.CssBoundScreen + type LiftScreen = screen.LiftScreen + type LiftScreenRules = screen.LiftScreenRules + + type Wizard = screen.Wizard + type WizardRules = screen.WizardRules + + type WiringUI = wiring.WiringUI + */ } diff --git a/web/webkit/src/main/scala/net/liftweb/javascript/JavaScriptContext.scala b/web/webkit/src/main/scala/net/liftweb/javascript/JavaScriptContext.scala index b2ea23ee0e..985b9c7533 100644 --- a/web/webkit/src/main/scala/net/liftweb/javascript/JavaScriptContext.scala +++ b/web/webkit/src/main/scala/net/liftweb/javascript/JavaScriptContext.scala @@ -17,7 +17,6 @@ import net.liftweb.common._ * and you get a JavaScript execution context around * all HTTP requests. */ - object JavaScriptContext { /** * Hook into LiftRules to put a JavaScript From fd9eab1b48500ae8c27968f6fe853821c4b8f263 Mon Sep 17 00:00:00 2001 From: Diego Medina Date: Thu, 7 Nov 2013 00:15:12 -0500 Subject: [PATCH 0630/1949] Revert "Added WiringUI" This reverts commit 0d0b4901e80706449264131e825c9398cad67db9. --- .../net/liftweb/http/wiring/WiringUI.scala | 365 ------------------ 1 file changed, 365 deletions(-) delete mode 100644 web/webkit/src/main/scala/net/liftweb/http/wiring/WiringUI.scala diff --git a/web/webkit/src/main/scala/net/liftweb/http/wiring/WiringUI.scala b/web/webkit/src/main/scala/net/liftweb/http/wiring/WiringUI.scala deleted file mode 100644 index 48130db185..0000000000 --- a/web/webkit/src/main/scala/net/liftweb/http/wiring/WiringUI.scala +++ /dev/null @@ -1,365 +0,0 @@ -/* - * Copyright 2010-2011 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.http.wiring - -import util._ - -import scala.xml.{NodeSeq, Elem, Text} -import net.liftweb.util.{Helpers, Cell} -import net.liftweb.http.js.JsCmd -import net.liftweb.common.{Full, Empty, Box} -import net.liftweb.http.js.JsCmds._ -import net.liftweb.http.{TransientRequestVar, S} -import net.liftweb.http.wiring.TransientRequestCell -import net.liftweb.http.js.JsCmds.SetHtml - -/** - * Surface a user interface on top of Wiring - */ -trait WiringUI { - /** - * Given a NodeSeq, a Cell and a function that can generate - * a NodeSeq => NodeSeq from the cell's value, register the - * postPageJavaScript that will update the element with - * a new value. - * - * @param in the NodeSeq that contains the view markup - * @param cell the cell to associate with - * @param f the function that performs the drawing - * - * @return the mutated NodeSeq (an id attribute may be added if - * there's none already defined) - */ - def apply[T](in: NodeSeq, cell: Cell[T])(f: T => NodeSeq => NodeSeq): NodeSeq = toNode(in, cell)((t, ns) => f(t)(ns)) - - /** - * Given a Cell and a function that can generate - * a NodeSeq => NodeSeq from the cell's value, return a function that - * takes a NodeSeq and registers the - * postPageJavaScript that will update the element with - * a new value. - * - * @param cell the cell to associate with - * @param f the function that performs the drawing - * - * @return a function that mutates NodeSeq (an id attribute may be added if - * there's none already defined) - */ - def apply[T](cell: Cell[T])(f: T => NodeSeq => NodeSeq): NodeSeq => NodeSeq = (in: NodeSeq) => toNode(in, cell)((t, ns) => f(t)(ns)) - - /** - * Given a NodeSeq, a Cell and a function that can generate - * a NodeSeq => NodeSeq from the cell's value, register the - * postPageJavaScript that will update the element with - * a new value. - * - * @param in the NodeSeq that contains the view markup - * @param cell the cell to associate with - * - * @param jsEffect a function that wraps the SetHtml JsCmd so - * you can, for example, fade out the old value, set the new value and - * fade it in. The first parameter is the id of the element, the - * second is a flag that's true if this is the first time the element is - * being rendered (you might want to skip effects for the inital page load), - * and the third parameter is the SetHtml JavaScript code. - * - * @param f the function that performs the drawing - * - * @return the mutated NodeSeq (an id attribute may be added if - * there's none already defined) - */ - def apply[T](in: NodeSeq, cell: Cell[T], jsEffect: (String, Boolean, JsCmd) => JsCmd)(f: T => NodeSeq => NodeSeq): NodeSeq = toNode(in, cell, jsEffect)((t, ns) => f(t)(ns)) - - /** - * Given a Cell and a function that can generate - * a NodeSeq => NodeSeq from the cell's value, return a function that with a NodeSeq - * will register the - * postPageJavaScript that will update the element with - * a new value. - * - * @param cell the cell to associate with - * - * @param jsEffect a function that wraps the SetHtml JsCmd so - * you can, for example, fade out the old value, set the new value and - * fade it in. The first parameter is the id of the element, the - * second is a flag that's true if this is the first time the element is - * being rendered (you might want to skip effects for the inital page load), - * and the third parameter is the SetHtml JavaScript code. - * - * @param f the function that performs the drawing - * - * @return the mutated NodeSeq (an id attribute may be added if - * there's none already defined) - */ - def apply[T](cell: Cell[T], jsEffect: (String, Boolean, JsCmd) => JsCmd)(f: T => NodeSeq => NodeSeq): NodeSeq => NodeSeq = - in => toNode(in, cell, jsEffect)((t, ns) => f(t)(ns)) - - /** - * Given a NodeSeq, a Cell and a function that can generate - * a NodeSeq from the cell's value and the template value, register the - * postPageJavaScript that will update the element with - * a new value. - * - * @param in the NodeSeq that contains the view markup - * @param cell the cell to associate with - * @param f the function that performs the drawing - * - * @return the mutated NodeSeq (an id attribute may be added if - * there's none already defined) - */ - def toNode[T](in: NodeSeq, cell: Cell[T])(f: (T, NodeSeq) => NodeSeq): NodeSeq = toNode(in, cell, (id, first, js) => js)(f) - - def history[T](cell: Cell[T])(f: (Box[T], T, NodeSeq) => JsCmd): NodeSeq => NodeSeq = - in => { - val myElem: Elem = in.find { - case e: Elem => true - case _ => false - }.map(_.asInstanceOf[Elem]).getOrElse({in}) - - - addHistJsFunc(cell, (old: Box[T], nw: T) => f(old, nw, in)) - - new Elem(myElem.prefix, - myElem.label, - myElem.attributes, - myElem.scope) - } - - - /** - * Given a NodeSeq, a Cell and a function that can generate - * a NodeSeq from the cell's value and the template value, register the - * postPageJavaScript that will update the element with - * a new value. - * - * @param in the NodeSeq that contains the view markup - * @param cell the cell to associate with - * @param f the function that performs the drawing - * - * @return the mutated NodeSeq (an id attribute may be added if - * there's none already defined) - */ - def toNode[T](cell: Cell[T])(f: (T, NodeSeq) => NodeSeq): NodeSeq => NodeSeq = in => toNode(in, cell, (id, first, js) => js)(f) - - /** - * Given a NodeSeq, a Cell register the - * postPageJavaScript that will update the element with - * a new value. - * - * @param in the NodeSeq that contains the view markup - * @param cell the cell to associate with - * - * @return the mutated NodeSeq (an id attribute may be added if - * there's none already defined) - */ - def asText[T](in: NodeSeq, cell: Cell[T]): NodeSeq = - toNode(in, cell, (id, first, js) => js)((t, ns) => Text(t.toString)) - - /** - * Given a Cell register the - * postPageJavaScript that will update the element with - * a new value. - * - * @param cell the cell to associate with - * - * @return a function that will mutate the NodeSeq (an id attribute may be added if - * there's none already defined) - */ - def asText[T](cell: Cell[T]): NodeSeq => NodeSeq = - in => toNode(in, cell, (id, first, js) => js)((t, ns) => Text(t.toString)) - - /** - * Given a NodeSeq, a Cell register the - * postPageJavaScript that will update the element with - * a new value. - * - * @param in the NodeSeq that contains the view markup - * @param cell the cell to associate with - * - * @param jsEffect a function that wraps the SetHtml JsCmd so - * you can, for example, fade out the old value, set the new value and - * fade it in. The first parameter is the id of the element, the - * second is a flag that's true if this is the first time the element is - * being rendered (you might want to skip effects for the inital page load), - * and the third parameter is the SetHtml JavaScript code. - * - * @return the mutated NodeSeq (an id attribute may be added if - * there's none already defined) - */ - def asText[T](in: NodeSeq, cell: Cell[T], jsEffect: (String, Boolean, JsCmd) => JsCmd): NodeSeq = - toNode(in, cell, jsEffect)((t, ns) => Text(t.toString)) - - /** - * Given a NodeSeq, a Cell register the - * postPageJavaScript that will update the element with - * a new value. - * - * @param in the NodeSeq that contains the view markup - * @param cell the cell to associate with - * - * @param jsEffect a function that wraps the SetHtml JsCmd so - * you can, for example, fade out the old value, set the new value and - * fade it in. The first parameter is the id of the element, the - * second is a flag that's true if this is the first time the element is - * being rendered (you might want to skip effects for the inital page load), - * and the third parameter is the SetHtml JavaScript code. - * - * @return a function that will mutate the NodeSeq (an id attribute may be added if - * there's none already defined) - */ - def asText[T](cell: Cell[T], jsEffect: (String, Boolean, JsCmd) => JsCmd): NodeSeq => NodeSeq = - in => toNode(in, cell, jsEffect)((t, ns) => Text(t.toString)) - - /** - * Given a NodeSeq, a Cell and a function that can generate - * a NodeSeq from the cell's value and the template value, register the - * postPageJavaScript that will update the element with - * a new value. - * - * @param in the NodeSeq that contains the view markup - * @param cell the cell to associate with - * - * @param jsEffect a function that wraps the SetHtml JsCmd so - * you can, for example, fade out the old value, set the new value and - * fade it in. The first parameter is the id of the element, the - * second is a flag that's true if this is the first time the element is - * being rendered (you might want to skip effects for the inital page load), - * and the third parameter is the SetHtml JavaScript code. - * - * @param f the function that performs the drawing - * - * @return the mutated NodeSeq (an id attribute may be added if - * there's none already defined) - */ - def toNode[T](in: NodeSeq, cell: Cell[T], jsEffect: (String, Boolean, JsCmd) => JsCmd)(f: (T, NodeSeq) => NodeSeq): NodeSeq = { - val myElem: Elem = in.find { - case e: Elem => true - case _ => false - }.map(_.asInstanceOf[Elem]).getOrElse({in}) - - val (elem: Elem, id: String) = Helpers.findOrAddId(myElem) - addJsFunc(cell, (t: T, first: Boolean) => { - jsEffect(id, first, SetHtml(id, f(t, elem.child))) - }) - elem - } - - /** - * Given a Cell and a function that can generate - * a NodeSeq from the cell's value and the template value, register the - * postPageJavaScript that will update the element with - * a new value. - * - * @param cell the cell to associate with - * - * @param jsEffect a function that wraps the SetHtml JsCmd so - * you can, for example, fade out the old value, set the new value and - * fade it in. The first parameter is the id of the element, the - * second is a flag that's true if this is the first time the element is - * being rendered (you might want to skip effects for the inital page load), - * and the third parameter is the SetHtml JavaScript code. - * - * @param f the function that performs the drawing - * - * @return the mutated NodeSeq (an id attribute may be added if - * there's none already defined) - */ - def toNode[T](cell: Cell[T], jsEffect: (String, Boolean, JsCmd) => JsCmd)(f: (T, NodeSeq) => NodeSeq): NodeSeq => NodeSeq = - in => { - val myElem: Elem = in.find { - case e: Elem => true - case _ => false - }.map(_.asInstanceOf[Elem]).getOrElse({in}) - - val (elem: Elem, id: String) = Helpers.findOrAddId(myElem) - addJsFunc(cell, (t: T, first: Boolean) => { - jsEffect(id, first, SetHtml(id, f(t, elem.child))) - }) - elem - } - - /** - * Associate a Cell and a function that converts from the - * cell's value to a JavaScript command to be sent to the - * browser as part of the page's post-processing. - * - * @param cell the cell to associate the JavaScript to - * @param f the function that takes the cell's value and a flag indicating - * if this is the first time - */ - def addJsFunc[T](cell: Cell[T], f: (T, Boolean) => JsCmd) { - for { - cometActor <- S.currentCometActor - } cell.addDependent(cometActor) - - val trc = TransientRequestCell(cell) - var lastTime: Long = 0L - var lastValue: T = null.asInstanceOf[T] - for { - sess <- S.session - } sess.addPostPageJavaScript(() => { - val (value, ct) = trc.get - val first = lastTime == 0L - if (first || (ct > lastTime && value != lastValue)) { - lastValue = value - lastTime = ct - f(value, first) - } else Noop - }) - } - - - /** - * Associate a Cell and a function that converts from the - * cell's value to a JavaScript command to be sent to the - * browser as part of the page's post-processing. - * - * @param cell the cell to associate the JavaScript to - * @param f the function that takes the cell's value and a flag indicating - * if this is the first time - */ - def addHistJsFunc[T](cell: Cell[T], f: (Box[T], T) => JsCmd) { - for { - cometActor <- S.currentCometActor - } cell.addDependent(cometActor) - - val trc = TransientRequestCell(cell) - var lastTime: Long = 0L - var lastValue: Box[T] = Empty - for { - sess <- S.session - } sess.addPostPageJavaScript(() => { - val (value, ct) = trc.get - val first = lastTime == 0L - if (first || (ct > lastTime && Full(value) != lastValue)) { - val oldValue = lastValue - lastValue = Full(value) - lastTime = ct - f(oldValue, value) - } else Noop - }) - } - -} - -/** - * Cache the value of the cell for the duration of the transient request - */ -private final case class TransientRequestCell[T](cell: Cell[T]) extends TransientRequestVar[(T, Long)](cell.currentValue) { - override val __nameSalt = Helpers.nextFuncName -} - From 0ff28767cd0e01737387dae8c02abf7ebea71cb5 Mon Sep 17 00:00:00 2001 From: Diego Medina Date: Thu, 7 Nov 2013 00:28:46 -0500 Subject: [PATCH 0631/1949] Added LiftFilter back --- web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala index 8117614f60..9919bb6d0a 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala @@ -1043,3 +1043,5 @@ private class SessionIdCalc(req: Req) { } } } + +class LiftFilter extends ServletFilterProvider From a89ce269ed0f0686f37e59eaed357db599cade16 Mon Sep 17 00:00:00 2001 From: Diego Medina Date: Thu, 7 Nov 2013 01:51:09 -0500 Subject: [PATCH 0632/1949] To build the project for java6, you have to have java6, you cannot simply use the source: ... option --- build.sbt | 2 -- 1 file changed, 2 deletions(-) diff --git a/build.sbt b/build.sbt index 2e1eebe2ef..d753e6d813 100644 --- a/build.sbt +++ b/build.sbt @@ -14,8 +14,6 @@ organizationName in ThisBuild := "WorldWide Conferencing, LLC" crossScalaVersions in ThisBuild := Seq("2.10.0", "2.9.2", "2.9.1-1", "2.9.1") -javacOptions ++= Seq("-source", "1.5") - libraryDependencies in ThisBuild <++= scalaVersion {sv => Seq(specs2(sv), scalacheck) } // Settings for Sonatype compliance From bdffe181e373b9bcc01e69261d2e71702576d2be Mon Sep 17 00:00:00 2001 From: Diego Medina Date: Thu, 7 Nov 2013 22:34:38 -0500 Subject: [PATCH 0633/1949] the latest pgp plugin does not sign jars with publish, now you have to use publish-signed --- unsafePublishLift.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/unsafePublishLift.sh b/unsafePublishLift.sh index 803e56dcc2..74949f632e 100755 --- a/unsafePublishLift.sh +++ b/unsafePublishLift.sh @@ -24,7 +24,7 @@ BUILDLOG=/tmp/Lift-do-release-`date "+%Y%m%d-%H%M%S"`.log # 5. git push origin # 6. git tag -release # 7. git push origin -release -# 8. LIFTSH_OPTS="-Dpublish.remote=true -Dsbt.log.noformat=true" ./liftsh clean-cache clean-plugins reload +clean-lib +update +clean +publish +# 8. LIFTSH_OPTS="-Dpublish.remote=true -Dsbt.log.noformat=true" ./liftsh clean-cache clean-plugins reload +clean-lib +update +clean +publish-signed # 9. Wait for happiness SCRIPTVERSION=0.1 @@ -157,7 +157,7 @@ for MODULE in framework ; do # Do a separate build for each configured Scala version so we don't blow the heap for SCALA_VERSION in $(grep crossScalaVersions build.sbt | cut -d '(' -f 2 | sed s/[,\)\"]//g ); do echo -n " Building against Scala ${SCALA_VERSION}..." - if ! ./liftsh ++${SCALA_VERSION} clean update test publish >> ${BUILDLOG} ; then + if ! ./liftsh ++${SCALA_VERSION} clean update test publish-signed >> ${BUILDLOG} ; then echo "failed! See build log for details" exit fi From f30a1842c922059842b4063b7b2ca123ac487d71 Mon Sep 17 00:00:00 2001 From: Vasya Novikov Date: Mon, 11 Nov 2013 00:54:39 +0400 Subject: [PATCH 0634/1949] code clean-up --- core/util/src/main/scala/net/liftweb/util/Log.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/util/src/main/scala/net/liftweb/util/Log.scala b/core/util/src/main/scala/net/liftweb/util/Log.scala index b98a96d48d..d75f5f8713 100644 --- a/core/util/src/main/scala/net/liftweb/util/Log.scala +++ b/core/util/src/main/scala/net/liftweb/util/Log.scala @@ -49,7 +49,7 @@ object LoggingAutoConfigurer { private def findTheFile(files: String*): Box[(java.net.URL)] = { val namesToTry = Props.toTry.flatMap(f => files.toList.map(file => f()+file)) - first(namesToTry) (name => tryo(getClass.getResource(name)).filter(_ ne null).map(s => s)) + first(namesToTry) (name => tryo(getClass.getResource(name)).filter(_ ne null)) } def apply(): () => Unit = () => { From 50399a80d07cd2ca0db5282553642329fa5e623f Mon Sep 17 00:00:00 2001 From: websterc Date: Sun, 3 Nov 2013 07:20:35 -0800 Subject: [PATCH 0635/1949] Improve performance of decomposing case classes to Json --- contributors.md | 7 +++++++ core/json/src/main/scala/net/liftweb/json/Extraction.scala | 6 +++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/contributors.md b/contributors.md index 7e1a2c1ccd..8ea731487d 100644 --- a/contributors.md +++ b/contributors.md @@ -186,3 +186,10 @@ Kenji Yoshida ### Email: ### 6b656e6a69 at gmail dot com +### Name: ### +Christopher Webster + +### Email: ### +cwebster93 at gmail .. com + + diff --git a/core/json/src/main/scala/net/liftweb/json/Extraction.scala b/core/json/src/main/scala/net/liftweb/json/Extraction.scala index e354675ad7..c912e4f012 100644 --- a/core/json/src/main/scala/net/liftweb/json/Extraction.scala +++ b/core/json/src/main/scala/net/liftweb/json/Extraction.scala @@ -84,9 +84,9 @@ object Extraction { case x if (x.getClass.isArray) => JArray(x.asInstanceOf[Array[_]].toList map decompose) case x: Option[_] => x.flatMap[JValue] { y => Some(decompose(y)) }.getOrElse(JNothing) case x => - val constructorArgs = primaryConstructorArgs(x.getClass) - constructorArgs.collect { case (name, _) if Reflection.hasDeclaredField(x.getClass, name) => - val f = x.getClass.getDeclaredField(name) + val fields = x.getClass.getDeclaredFields.map(field => (field.getName, field)).toMap + val constructorArgs = primaryConstructorArgs(x.getClass).map{ case (name, _) => (name,fields.get(name)) } + constructorArgs.collect { case (name, Some(f)) => f.setAccessible(true) JField(unmangleName(name), decompose(f get x)) } match { From ef11df28cdd1aea54dcb629e1c14250747ca9478 Mon Sep 17 00:00:00 2001 From: Diego Medina Date: Thu, 7 Nov 2013 22:34:38 -0500 Subject: [PATCH 0636/1949] the latest pgp plugin does not sign jars with publish, now you have to use publish-signed --- unsafePublishLift.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/unsafePublishLift.sh b/unsafePublishLift.sh index 803e56dcc2..74949f632e 100755 --- a/unsafePublishLift.sh +++ b/unsafePublishLift.sh @@ -24,7 +24,7 @@ BUILDLOG=/tmp/Lift-do-release-`date "+%Y%m%d-%H%M%S"`.log # 5. git push origin # 6. git tag -release # 7. git push origin -release -# 8. LIFTSH_OPTS="-Dpublish.remote=true -Dsbt.log.noformat=true" ./liftsh clean-cache clean-plugins reload +clean-lib +update +clean +publish +# 8. LIFTSH_OPTS="-Dpublish.remote=true -Dsbt.log.noformat=true" ./liftsh clean-cache clean-plugins reload +clean-lib +update +clean +publish-signed # 9. Wait for happiness SCRIPTVERSION=0.1 @@ -157,7 +157,7 @@ for MODULE in framework ; do # Do a separate build for each configured Scala version so we don't blow the heap for SCALA_VERSION in $(grep crossScalaVersions build.sbt | cut -d '(' -f 2 | sed s/[,\)\"]//g ); do echo -n " Building against Scala ${SCALA_VERSION}..." - if ! ./liftsh ++${SCALA_VERSION} clean update test publish >> ${BUILDLOG} ; then + if ! ./liftsh ++${SCALA_VERSION} clean update test publish-signed >> ${BUILDLOG} ; then echo "failed! See build log for details" exit fi From adddad4636ad6c724dd8094462599dbbb2afad0f Mon Sep 17 00:00:00 2001 From: David Barri Date: Thu, 28 Nov 2013 22:36:58 +1100 Subject: [PATCH 0637/1949] Added LiftRules.funcNameGenerator which controls the logic of S.formFuncName. Just as before, TestMode gets different logic until names are generated when S._disableTestFuncNames is true. --- .../scala/net/liftweb/http/LiftRules.scala | 8 +++ .../src/main/scala/net/liftweb/http/S.scala | 20 +++++-- .../test/scala/net/liftweb/http/SSpec.scala | 56 +++++++++++++++++++ 3 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 web/webkit/src/test/scala/net/liftweb/http/SSpec.scala diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 262c04065e..fcef2ade03 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -146,6 +146,11 @@ object LiftRules extends LiftRulesMocker { val ExecutionFailure = Value(11, "Execution Failure") } + def defaultFuncNameGenerator(runMode: Props.RunModes.Value): () => String = + runMode match { + case Props.RunModes.Test => S.generateTestFuncName _ + case _ => S.generateFuncName _ + } } /** @@ -1791,6 +1796,9 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { /** Controls whether or not the service handling timing messages (Service request (GET) ... took ... Milliseconds) are logged. Defaults to true. */ @volatile var logServiceRequestTiming = true + /** Provides a function that returns random names for form variables, page ids, callbacks, etc. */ + @volatile var funcNameGenerator: () => String = defaultFuncNameGenerator(Props.mode) + import provider.servlet._ import containers._ diff --git a/web/webkit/src/main/scala/net/liftweb/http/S.scala b/web/webkit/src/main/scala/net/liftweb/http/S.scala index 08582bc025..cb534c0de0 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/S.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/S.scala @@ -2600,7 +2600,17 @@ trait S extends HasParams with Loggable with UserAgentCalculator { f } - def formFuncName: String = if (Props.testMode && !disableTestFuncNames_?) { + def formFuncName: String = LiftRules.funcNameGenerator() + + /** Default func-name logic during test-mode. */ + def generateTestFuncName: String = + if (disableTestFuncNames_?) + generateFuncName + else + generatePredictableFuncName + + /** Generates a func-name based on the location in the call-site source code. */ + def generatePredictableFuncName: String = { val bump: Long = ((_formGroup.is openOr 0) + 1000L) * 100000L val num: Int = formItemNumber.is formItemNumber.set(num + 1) @@ -2608,12 +2618,14 @@ trait S extends HasParams with Loggable with UserAgentCalculator { val prefix: String = new DecimalFormat("00000000000000000").format(bump + num) // take the first 2 non-Lift/non-Scala stack frames for use as hash issue 174 "f" + prefix + "_" + Helpers.hashHex((new Exception).getStackTrace.toList.filter(notLiftOrScala).take(2).map(_.toString).mkString(",")) - } else { + } + + /** Standard func-name logic. This is the default routine. */ + def generateFuncName: String = _formGroup.is match { case Full(x) => Helpers.nextFuncName(x.toLong * 100000L) - case _ => Helpers.nextFuncName + case _ => Helpers.nextFuncName } - } def formGroup[T](group: Int)(f: => T): T = { val x = _formGroup.is diff --git a/web/webkit/src/test/scala/net/liftweb/http/SSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/SSpec.scala new file mode 100644 index 0000000000..31bb8d382c --- /dev/null +++ b/web/webkit/src/test/scala/net/liftweb/http/SSpec.scala @@ -0,0 +1,56 @@ +/* + * Copyright 2013 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 org.specs2.mutable.Specification +import util.Helpers +import util.Props.RunModes +import LiftRules.defaultFuncNameGenerator + +object SSpec extends Specification { + "S Specification".title + + "formFuncName" should { + "generate random names when not in Test mode" in { + for (mode <- RunModes.values if mode != RunModes.Test) { + val a,b = defaultFuncNameGenerator(mode)() + a must startWith("F") + a.length must_== Helpers.nextFuncName.length + a must_!= b + } + } + + "generate predictable names in Test mode" in { + val a,b = S.formFuncName + a must startWith("f") + a.length must_!= Helpers.nextFuncName.length + a must_== b + a must_!= S.formFuncName + } + + "generate resort back to random names when test func-names disabled" in { + S.disableTestFuncNames { + val a,b = S.formFuncName + a must startWith("F") + a.length must_== Helpers.nextFuncName.length + a must_!= b + } + } + + } +} From 81721830a44ab3a8e15a0caab5b7031782e47e19 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 14 Dec 2013 15:32:54 -0500 Subject: [PATCH 0638/1949] Add spec to ReqSpec for correctly identifying IE versions. --- .../test/scala/net/liftweb/http/ReqSpec.scala | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala index 0b871f2f57..7accc87437 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala @@ -36,6 +36,15 @@ object ReqSpec extends Specification { List("Mozilla/5.0 (iPad; U; CPU OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B367 Safari/531.21.10", "Mozilla/5.0 (iPad; U; CPU OS 4_2_1 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8C148 Safari/6533.18.5") + private val ieUserAgents = + "Mozilla/5.0 (Windows; U; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727)" :: + "Mozilla/5.0 (Windows; U; MSIE 7.0; Windows NT 6.0; en-US)" :: + "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; GTB7.4; InfoPath.2; SV1; .NET CLR 3.3.69573; WOW64; en-US)" :: + "Mozilla/5.0 (Windows; U; MSIE 9.0; WIndows NT 9.0; en-US))" :: + "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)" :: + "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko" :: + Nil + "Req" should { "recognize safari 5" in { val uac = new UserAgentCalculator { @@ -67,6 +76,18 @@ object ReqSpec extends Specification { } } } + + "Correctly recognize IE versions 6-11" in { + val ieVersions = ieUserAgents.flatMap { ieUserAgent => + val userAgentCalculator = new UserAgentCalculator { + def userAgent = Full(ieUserAgent) + } + + userAgentCalculator.ieVersion + } + + ieVersions must_== List(6, 7, 8, 9, 10, 11) + } } } From 758d87286cdf69f2632df170966d4e10da8c11e0 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 14 Dec 2013 15:33:08 -0500 Subject: [PATCH 0639/1949] Update defaultIeCalcFunction to correctly detect IE 11. --- web/webkit/src/main/scala/net/liftweb/http/Req.scala | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/Req.scala b/web/webkit/src/main/scala/net/liftweb/http/Req.scala index 704c4ec042..b82563087f 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Req.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Req.scala @@ -33,7 +33,7 @@ object UserAgentCalculator extends Factory { /** * The default regular expression for IE */ - val iePattern = """MSIE ([0-9]+)""".r + val iePattern = """(MSIE ([0-9]+)|Trident/7.*rv:([0-9]+))""".r /** * You can change the mechanism by which the user agent for IE @@ -47,9 +47,11 @@ object UserAgentCalculator extends Factory { */ def defaultIeCalcFunction(userAgent: Box[String]): Box[Double] = for { - ua <- userAgent - m = iePattern.pattern.matcher(ua) - ver <- if (m.find) Helpers.asDouble(m.group(1)) else Empty + userAgent <- userAgent + ieMatch = iePattern.pattern.matcher(userAgent) + findResult = ieMatch.find if findResult + ieVersionString <- Box.legacyNullTest(ieMatch.group(2)) or Box.legacyNullTest(ieMatch.group(3)) + ver <- Helpers.asDouble(ieVersionString) } yield ver /** From 8d5c9174c8093b505d51058e72898f989282be9e Mon Sep 17 00:00:00 2001 From: Peter Brant Date: Fri, 20 Dec 2013 13:23:49 +0100 Subject: [PATCH 0640/1949] Fix compile in Scala 2.10.1+ Scala 2.10.1 includes a type checking bug fix that causes compilation to fail. Add casts to work around this (effectively restoring the previous behavior). --- .../src/main/scala/net/liftweb/http/LiftScreen.scala | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala index 2a415b31f5..1157dc8e57 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala @@ -360,7 +360,7 @@ trait AbstractScreen extends Factory { case AVal(v: (T => List[FieldError])) => v }, stuff.toList.collect { - case AFilter(v) => v + case AFilter(v) => v.asInstanceOf[T => T] }, stuff) } @@ -484,7 +484,7 @@ trait AbstractScreen extends Factory { }.toList override def setFilter = stuff.collect { - case AFilter(f) => f + case AFilter(f) => f.asInstanceOf[ValueType => ValueType] }.toList override def is = underlying.get @@ -586,7 +586,7 @@ trait AbstractScreen extends Factory { }.toList override def setFilter = stuff.collect { - case AFilter(f) => f + case AFilter(f) => f.asInstanceOf[ValueType => ValueType] }.toList override def is = underlying.openOrThrowException("Legacy code").get @@ -617,7 +617,7 @@ trait AbstractScreen extends Factory { case AVal(v: (T => List[FieldError])) => List(v) case _ => Nil }, stuff.toList.flatMap { - case AFilter(v) => List(v) + case AFilter(v) => List(v.asInstanceOf[T => T]) case _ => Nil }, stuff).make @@ -755,7 +755,7 @@ trait AbstractScreen extends Factory { override lazy val formElemAttrs: Seq[SHtml.ElemAttr] = grabParams(stuff) override val setFilter = stuff.flatMap { - case AFilter(f) => List(f) + case AFilter(f) => List(f.asInstanceOf[ValueType => ValueType]) case _ => Nil }.toList override val validations = stuff.flatMap { @@ -792,7 +792,7 @@ trait AbstractScreen extends Factory { override lazy val formElemAttrs: Seq[SHtml.ElemAttr] = grabParams(stuff) override val setFilter = stuff.flatMap { - case AFilter(f) => List(f) + case AFilter(f) => List(f.asInstanceOf[ValueType => ValueType]) case _ => Nil }.toList override val validations = stuff.flatMap { From 0f9b9b5ffaff16d9c0fc1b2f9f813701e31e492f Mon Sep 17 00:00:00 2001 From: Vasya Novikov Date: Mon, 23 Dec 2013 12:54:58 +0400 Subject: [PATCH 0641/1949] Box.forall method --- contributors.md | 5 +++++ .../src/main/scala/net/liftweb/common/Box.scala | 10 +++++++++- .../test/scala/net/liftweb/common/BoxSpec.scala | 14 ++++++++++++-- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/contributors.md b/contributors.md index 8ea731487d..e271ecc200 100644 --- a/contributors.md +++ b/contributors.md @@ -192,4 +192,9 @@ Christopher Webster ### Email: ### cwebster93 at gmail .. com +### Name: ### +Vasya Novikov + +### Email: ### +n1dr+cm3053lift@yaaandex.com (replace "aaa" with "a") diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index 0e6af67052..694bae65df 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -310,10 +310,16 @@ sealed abstract class Box[+A] extends Product with Serializable{ /** * Determine whether this Box contains a value which satisfies the specified predicate - * @return true if this Box's value satisfies the specified predicate + * @return true if this Box does contain a value and it satisfies the predicate */ def exists(func: A => Boolean): Boolean = false + /** + * Determine whether all Box values satisfy the predicate + * @return true if the Box is empty, or if Box's value satisfies the predicate + */ + def forall(func: A => Boolean): Boolean = true + /** * Creates a Box if the current Box is Full and the value does not satisfy the predicate, f. * @@ -546,6 +552,8 @@ final case class Full[+A](value: A) extends Box[A]{ override def exists(func: A => Boolean): Boolean = func(value) + override def forall(func: A => Boolean): Boolean = func(value) + override def filter(p: A => Boolean): Box[A] = if (p(value)) this else Empty override def foreach[U](f: A => U): Unit = f(value) diff --git a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala index 1e3ee0c104..85b39ef134 100644 --- a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala +++ b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala @@ -106,12 +106,18 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { "return itself when or'ed with another Box" in { Full(1) or Full(2) must_== Full(1) } - "define an 'exists' method returning true if the Box value verifies the function" in { + "define an 'exists' method returning true if the Box value satisfies the function" in { Full(1) exists {_ > 0} must beTrue } - "define an exists method returning false if the Box value doesn't verify the function" in { + "define an exists method returning false if the Box value doesn't satisfy the function" in { Full(0) exists {_ > 0} must beFalse } + "define a forall method returning true if the Box value satisfies the function" in { + Full(1) forall {_ > 0} must beTrue + } + "define a forall method returning false if the Box value doesn't satisfy the function" in { + Full(0) forall {_ > 0} must beFalse + } "define a 'filter' method, returning a Full Box if the filter is satisfied" in { Full(1) filter {_ > 0} must_== Full(1) } @@ -239,6 +245,10 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { val empty: Box[Int] = Empty empty exists {_ > 0} must beFalse } + "define a 'forall' method returning false" in { + val empty: Box[Int] = Empty + empty forall {_ > 0} must beTrue + } "define a 'filter' method, returning Empty" in { val empty: Box[Int] = Empty empty filter {_ > 0} must beEmpty From 1234b9674ea0060a1753f1b659e95312ea9627fd Mon Sep 17 00:00:00 2001 From: Vasya Novikov Date: Tue, 24 Dec 2013 00:20:29 +0400 Subject: [PATCH 0642/1949] tests for Failure.forall, Failure.exists methods --- .../src/test/scala/net/liftweb/common/BoxSpec.scala | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala index 85b39ef134..bff37ba19d 100644 --- a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala +++ b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala @@ -245,7 +245,7 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { val empty: Box[Int] = Empty empty exists {_ > 0} must beFalse } - "define a 'forall' method returning false" in { + "define a 'forall' method returning true" in { val empty: Box[Int] = Empty empty forall {_ > 0} must beTrue } @@ -314,6 +314,12 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { "create a new failure with a chained message if asked for its status with the operator ?~!" in { Failure("error", Empty, Empty) ?~! "error2" must_== Failure("error2", Empty, Full(Failure("error", Empty, Empty))) } + "return false for exist method" in { + Failure("error", Empty, Empty) exists {_ => true } must beFalse + } + "return true for forall method" in { + Failure("error", Empty, Empty) forall {_ => false } must beTrue + } } "A Box equals method" should { From b3ea46efd09200dbc8284b43c95a78ea7de764d9 Mon Sep 17 00:00:00 2001 From: KevG Date: Sun, 5 Jan 2014 18:14:08 -0800 Subject: [PATCH 0643/1949] Update README.md to clarify pull request guidelines --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 811ca5957e..16451828bf 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ Because Lift applications are written in [Scala](http://www.scala-lang.org), an We will accept pull requests into the [Lift codebase](https://github.com/lift) if the pull requests meet the following criteria: +* Before creating a pull request, non-committers must discuss it on the [Lift mailing list](http://groups.google.com/forum/#!forum/liftweb). * One or more of the following: * Documentation including ScalaDoc comments in code * Example code @@ -23,6 +24,8 @@ if the pull requests meet the following criteria: * Each pull request must include a signature at the bottom of the `/contributors.md` file. +For more details, see [CONTRIBUTING.md](https://github.com/lift/framework/blob/master/CONTRIBUTING.md). + ## Getting Started You can create a new Lift project using your favorite build system by adding Lift as a dependency: From 71b80c04e6d6aa2efa743bf99ebfc1a03c8fccf6 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 5 Jan 2014 23:55:35 -0500 Subject: [PATCH 0644/1949] Explicitly note list discussions in PR requirements. --- CONTRIBUTING.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bb915f1259..59f3c96c79 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,6 +18,9 @@ The policy on issues is: We will accept pull requests into the Lift codebase if the pull requests meet the following criteria: +* The request handles an issue that has been discussed on the Lift mailing list + and whose solution has been requested (and in general adheres to the spirit of + the issue guidelines above). * The request represents one or more of the following: * Documentation including ScalaDoc comments in code * Example code From d4b3bdb6f721f22639a710de19ff59ca0515cb54 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 5 Jan 2014 23:58:26 -0500 Subject: [PATCH 0645/1949] Align readme PR verbiage with contributing.md. --- README.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 16451828bf..6e08c44ad7 100644 --- a/README.md +++ b/README.md @@ -16,13 +16,14 @@ Because Lift applications are written in [Scala](http://www.scala-lang.org), an We will accept pull requests into the [Lift codebase](https://github.com/lift) if the pull requests meet the following criteria: -* Before creating a pull request, non-committers must discuss it on the [Lift mailing list](http://groups.google.com/forum/#!forum/liftweb). -* One or more of the following: - * Documentation including ScalaDoc comments in code - * Example code - * Small changes, enhancements, or bug fixes to Lift's code -* Each pull request must include a signature at the bottom of the - `/contributors.md` file. +* The request handles an issue that has been discussed on the [Lift mailing list](http://groups.google.com/forum/#!forum/liftweb) + and whose solution has been requested (and in general adheres to the spirit of + the issue guidelines outlined in [CONTRIBUTING.md](https://github.com/lift/framework/blob/master/CONTRIBUTING.md)). +* The request represents one or more of the following: + * Documentation including ScalaDoc comments in code + * Example code + * Small changes, enhancements, or bug fixes to Lift’s code +* The request includes a signature at the bottom of the `/contributors.md` file. For more details, see [CONTRIBUTING.md](https://github.com/lift/framework/blob/master/CONTRIBUTING.md). From 57963c4a860be15f3adc27309630caaf221404de Mon Sep 17 00:00:00 2001 From: antidata Date: Tue, 21 Jan 2014 11:35:43 -0600 Subject: [PATCH 0646/1949] #1513 Allow to return boolean values using the roundtrip function --- .../src/main/scala/net/liftweb/http/js/ScriptRenderer.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala b/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala index dbe3515ec9..b1b8164d75 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/js/ScriptRenderer.scala @@ -92,7 +92,7 @@ object ScriptRenderer { processMsg: function(evt) {if (this._done || this._failed) return; this._events.push(evt); for (var v in this._eventFuncs) {try {this._eventFuncs[v](evt);} catch (e) {""" + (LiftRules.jsLogFunc.map(_(JsVar("e")).toJsCmd) openOr "") + """}}; - if (evt.done) {this.doneMsg();} else if (evt.success) {this.successMsg(evt.success);} else if (evt.failure) {this.failMsg(evt.failure);}}, + if (evt.done != null) {this.doneMsg();} else if (evt.success != null) {this.successMsg(evt.success);} else if (evt.failure != null) {this.failMsg(evt.failure);}}, successMsg: function(value) {if (this._done || this._failed) return; this._values.push(value); for (var f in this._valueFuncs) {this._valueFuncs[f](value);}}, failMsg: function(msg) {if (this._done || this._failed) return; liftAjax._removeIt(this.guid); this._failed = true; this._failMsg = msg; for (var f in this._failureFuncs) {this._failureFuncs[f](msg);}}, doneMsg: function() {if (this._done || this._failed) return; liftAjax._removeIt(this.guid); this._done = true; for (var f in this._doneFuncs) {this._doneFuncs[f]();}}, From bc6930985e4e7be1f60905bfb8bc7d2c55ce0067 Mon Sep 17 00:00:00 2001 From: Donald McLean Date: Thu, 23 Jan 2014 12:11:01 -0500 Subject: [PATCH 0647/1949] Update MockHttpRequestSpec.scala Added test for parameters with empty values --- .../http/testing/MockHttpRequestSpec.scala | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/web/testkit/src/test/scala/net/liftweb/http/testing/MockHttpRequestSpec.scala b/web/testkit/src/test/scala/net/liftweb/http/testing/MockHttpRequestSpec.scala index 29bcdd376b..2dceccea60 100644 --- a/web/testkit/src/test/scala/net/liftweb/http/testing/MockHttpRequestSpec.scala +++ b/web/testkit/src/test/scala/net/liftweb/http/testing/MockHttpRequestSpec.scala @@ -29,6 +29,7 @@ object MockHttpRequestSpec extends Specification { val IF_MODIFIED_HEADER = "If-Modified-Since" val TEST_URL = "https://foo.com/test/this/page?a=b&b=a&a=c" + val TEST_URL_BP = "https://foo.com/test/this/page?a=b&b=a&c=&d" "MockHttpRequest" should { @@ -46,6 +47,20 @@ object MockHttpRequestSpec extends Specification { testRequest.getParameter("b") must_== "a" } + "parse parameters with empty values" in { + val testRequest = new MockHttpServletRequest(TEST_URL_BP, "/test") + + testRequest.getScheme must_== "https" + testRequest.isSecure must_== true + testRequest.getServerName must_== "foo.com" + testRequest.getContextPath must_== "/test" + testRequest.getRequestURI must_== "/test/this/page" + testRequest.getRequestURL.toString must_== TEST_URL + testRequest.getQueryString must_== "a=b&b=a&a=c" + testRequest.getParameter("c") must_== "" + testRequest.getParameter("d") must_== "" + } + "correctly add and parse a date header" in { val testRequest = new MockHttpServletRequest(TEST_URL, "/test") From 9b6938f1501a5ed28cd211721fcf7725ce782c9d Mon Sep 17 00:00:00 2001 From: Donald McLean Date: Thu, 23 Jan 2014 12:13:49 -0500 Subject: [PATCH 0648/1949] Update MockHttpServletRequest.scala Add case matching a parameter with no/empty value ("a=" or just "a") --- .../main/scala/net/liftweb/mocks/MockHttpServletRequest.scala | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/web/testkit/src/main/scala/net/liftweb/mocks/MockHttpServletRequest.scala b/web/testkit/src/main/scala/net/liftweb/mocks/MockHttpServletRequest.scala index 2c02c84148..7cdb254322 100644 --- a/web/testkit/src/main/scala/net/liftweb/mocks/MockHttpServletRequest.scala +++ b/web/testkit/src/main/scala/net/liftweb/mocks/MockHttpServletRequest.scala @@ -216,6 +216,10 @@ class MockHttpServletRequest(val url : String = null, var contextPath : String = // Append to the current key's value newParams += key -> value } + case Array(key) => { + // Append to the current key's value + newParams += key -> "" + } case invalid => throw new IllegalArgumentException("Invalid query string: \"" + q + "\"") } } From b0f4b95c81382034e7756ca8e3b944eacf09ce24 Mon Sep 17 00:00:00 2001 From: Donald McLean Date: Thu, 23 Jan 2014 13:40:33 -0500 Subject: [PATCH 0649/1949] Added my name and email --- contributors.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contributors.md b/contributors.md index 8ea731487d..f9f824e0af 100644 --- a/contributors.md +++ b/contributors.md @@ -192,4 +192,5 @@ Christopher Webster ### Email: ### cwebster93 at gmail .. com - +### Name: ### +dmclean62 @@ gmail .. com From 12000b2e397bcadc4905d17a13616ae7aca93634 Mon Sep 17 00:00:00 2001 From: Donald McLean Date: Thu, 23 Jan 2014 13:42:09 -0500 Subject: [PATCH 0650/1949] Corrected formatting --- contributors.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/contributors.md b/contributors.md index f9f824e0af..a68fc7b0c1 100644 --- a/contributors.md +++ b/contributors.md @@ -193,4 +193,7 @@ Christopher Webster cwebster93 at gmail .. com ### Name: ### +Donald McLean + +### Email: ### dmclean62 @@ gmail .. com From 2ad7379de63957ff9ba1e1aa758ea880fc7fe5d2 Mon Sep 17 00:00:00 2001 From: websterc Date: Fri, 31 Jan 2014 12:02:41 -0800 Subject: [PATCH 0651/1949] Memoize declared fields to avoid reflection during case class decomposition --- core/json/src/main/scala/net/liftweb/json/Extraction.scala | 2 +- core/json/src/main/scala/net/liftweb/json/Meta.scala | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/core/json/src/main/scala/net/liftweb/json/Extraction.scala b/core/json/src/main/scala/net/liftweb/json/Extraction.scala index c912e4f012..56ec08227f 100644 --- a/core/json/src/main/scala/net/liftweb/json/Extraction.scala +++ b/core/json/src/main/scala/net/liftweb/json/Extraction.scala @@ -84,7 +84,7 @@ object Extraction { case x if (x.getClass.isArray) => JArray(x.asInstanceOf[Array[_]].toList map decompose) case x: Option[_] => x.flatMap[JValue] { y => Some(decompose(y)) }.getOrElse(JNothing) case x => - val fields = x.getClass.getDeclaredFields.map(field => (field.getName, field)).toMap + val fields = getDeclaredFields(x.getClass) val constructorArgs = primaryConstructorArgs(x.getClass).map{ case (name, _) => (name,fields.get(name)) } constructorArgs.collect { case (name, Some(f)) => f.setAccessible(true) diff --git a/core/json/src/main/scala/net/liftweb/json/Meta.scala b/core/json/src/main/scala/net/liftweb/json/Meta.scala index 68ddb3a321..f14aeff221 100644 --- a/core/json/src/main/scala/net/liftweb/json/Meta.scala +++ b/core/json/src/main/scala/net/liftweb/json/Meta.scala @@ -222,6 +222,7 @@ private[json] object Meta { private val primaryConstructors = new Memo[Class[_], List[(String, Type)]] private val declaredFields = new Memo[(Class[_], String), Boolean] + private val declaredFieldsMap = new Memo[Class[_], Map[String,Field]] def constructors(t: Type, names: ParameterNameReader, context: Option[Context]): List[(JConstructor[_], List[(String, Type)])] = rawClassOf(t).getDeclaredConstructors.map(c => (c, constructorArgs(t, c, names, context))).toList @@ -355,6 +356,11 @@ private[json] object Meta { else findField(clazz.getSuperclass, name) } + def getDeclaredFields(clazz: Class[_]) : Map[String,Field] = { + def extractDeclaredFields = clazz.getDeclaredFields.map(field => (field.getName, field)).toMap + declaredFieldsMap.memoize(clazz, _ => extractDeclaredFields) + } + def hasDeclaredField(clazz: Class[_], name: String): Boolean = { def declaredField = try { clazz.getDeclaredField(name) From 4b4943889933ada4a406dcdac610ba8ab38c58c5 Mon Sep 17 00:00:00 2001 From: Donald McLean Date: Fri, 7 Feb 2014 09:51:17 -0500 Subject: [PATCH 0652/1949] Update MockHttpServletRequest.scala --- .../main/scala/net/liftweb/mocks/MockHttpServletRequest.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/web/testkit/src/main/scala/net/liftweb/mocks/MockHttpServletRequest.scala b/web/testkit/src/main/scala/net/liftweb/mocks/MockHttpServletRequest.scala index 7cdb254322..37635c8d90 100644 --- a/web/testkit/src/main/scala/net/liftweb/mocks/MockHttpServletRequest.scala +++ b/web/testkit/src/main/scala/net/liftweb/mocks/MockHttpServletRequest.scala @@ -216,6 +216,7 @@ class MockHttpServletRequest(val url : String = null, var contextPath : String = // Append to the current key's value newParams += key -> value } + case Array("") => throw new IllegalArgumentException("Invalid query string: \"" + q + "\"") case Array(key) => { // Append to the current key's value newParams += key -> "" From a4510e6d7ead213a48e1c00acc79fa276d867bfc Mon Sep 17 00:00:00 2001 From: Donald McLean Date: Fri, 7 Feb 2014 09:57:58 -0500 Subject: [PATCH 0653/1949] Update MockHttpRequestSpec.scala --- .../scala/net/liftweb/http/testing/MockHttpRequestSpec.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/testkit/src/test/scala/net/liftweb/http/testing/MockHttpRequestSpec.scala b/web/testkit/src/test/scala/net/liftweb/http/testing/MockHttpRequestSpec.scala index 2dceccea60..c07f4ad1a2 100644 --- a/web/testkit/src/test/scala/net/liftweb/http/testing/MockHttpRequestSpec.scala +++ b/web/testkit/src/test/scala/net/liftweb/http/testing/MockHttpRequestSpec.scala @@ -55,8 +55,8 @@ object MockHttpRequestSpec extends Specification { testRequest.getServerName must_== "foo.com" testRequest.getContextPath must_== "/test" testRequest.getRequestURI must_== "/test/this/page" - testRequest.getRequestURL.toString must_== TEST_URL - testRequest.getQueryString must_== "a=b&b=a&a=c" + testRequest.getRequestURL.toString must_== TEST_URL_BP + testRequest.getQueryString must_== "a=b&b=a&c=&d" testRequest.getParameter("c") must_== "" testRequest.getParameter("d") must_== "" } From bf56a6516701d94a53cd26111e67f9508c0069a2 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 15 Feb 2014 09:47:09 -0500 Subject: [PATCH 0654/1949] Clarify some of the Memo names used in Meta.Reflection. I noticed when we merged a PR that the names of the various Memos in Meta.Reflection were getting a bit confusing, so I've cleaned them up a bit to 1) reduce confusion between what are now called hasDeclaredFieldsMemo and declaredFieldsMemo and 2) to clarify what the primaryConstructorArgumentsMemo actually contained. --- core/json/src/main/scala/net/liftweb/json/Meta.scala | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/json/src/main/scala/net/liftweb/json/Meta.scala b/core/json/src/main/scala/net/liftweb/json/Meta.scala index f14aeff221..838928e51d 100644 --- a/core/json/src/main/scala/net/liftweb/json/Meta.scala +++ b/core/json/src/main/scala/net/liftweb/json/Meta.scala @@ -220,9 +220,9 @@ private[json] object Meta { classOf[java.lang.Short], classOf[Date], classOf[Timestamp], classOf[Symbol], classOf[JValue], classOf[JObject], classOf[JArray]).map((_, ()))) - private val primaryConstructors = new Memo[Class[_], List[(String, Type)]] - private val declaredFields = new Memo[(Class[_], String), Boolean] - private val declaredFieldsMap = new Memo[Class[_], Map[String,Field]] + private val primaryConstructorArgumentsMemo = new Memo[Class[_], List[(String, Type)]] + private val hasDeclaredFieldsMemo = new Memo[(Class[_], String), Boolean] + private val declaredFieldsMemo = new Memo[Class[_], Map[String,Field]] def constructors(t: Type, names: ParameterNameReader, context: Option[Context]): List[(JConstructor[_], List[(String, Type)])] = rawClassOf(t).getDeclaredConstructors.map(c => (c, constructorArgs(t, c, names, context))).toList @@ -267,7 +267,7 @@ private[json] object Meta { constructorArgs(c, primary, formats.parameterNameReader, None) } - primaryConstructors.memoize(c, findMostComprehensive(_)) + primaryConstructorArgumentsMemo.memoize(c, findMostComprehensive(_)) } def typeParameters(t: Type, k: Kind, context: Context): List[Class[_]] = { @@ -358,7 +358,7 @@ private[json] object Meta { def getDeclaredFields(clazz: Class[_]) : Map[String,Field] = { def extractDeclaredFields = clazz.getDeclaredFields.map(field => (field.getName, field)).toMap - declaredFieldsMap.memoize(clazz, _ => extractDeclaredFields) + declaredFieldsMemo.memoize(clazz, _ => extractDeclaredFields) } def hasDeclaredField(clazz: Class[_], name: String): Boolean = { @@ -369,7 +369,7 @@ private[json] object Meta { case e: NoSuchFieldException => false } - declaredFields.memoize((clazz, name), _ => declaredField) + hasDeclaredFieldsMemo.memoize((clazz, name), _ => declaredField) } def mkJavaArray(x: Any, componentType: Class[_]) = { From 3b1e70527cc8ad27dabfa37c51a9f68507a290c4 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 15 Mar 2014 16:07:56 -0400 Subject: [PATCH 0655/1949] Rename pageResourceId to instanceResourceId in LiftRules. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also drop a comment on it indicating what it’s for, and move it closer to its usage spot. --- .../src/main/scala/net/liftweb/http/LiftRules.scala | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index fcef2ade03..a1686ccc4c 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -167,8 +167,6 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { def doneBoot = _doneBoot def noticesContainerId = "lift__noticesContainer__" - private val pageResourceId = Helpers.nextFuncName - /** * If you want to make the Lift inactivity timeout shorter than @@ -254,6 +252,10 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { @volatile var getLiftSession: (Req) => LiftSession = (req) => _getLiftSession(req) + // Unique identifier for this particular instance of Lift, used for + // tagging resources below in attachResourceId. + private val instanceResourceId = Helpers.nextFuncName + /** * Attaches an ID entity for resource URI specified in * link or script tags. This allows controlling browser @@ -263,11 +265,11 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { * "forcing" browsers to refresh the resource only when the resource * file changes. Users can define other rules as well. Inside user's * function it is safe to use S context as attachResourceId is called - * from inside the <lift:with-resource-id> snippet + * from inside the <lift:with-resource-id> snippet * */ @volatile var attachResourceId: (String) => String = (name) => { - name + (if (name contains ("?")) "&" else "?") + pageResourceId + "=_" + name + (if (name contains ("?")) "&" else "?") + instanceResourceId + "=_" } /** From 651a17889fa6ffbf599a0d3f020e7b0935bdc908 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 15 Mar 2014 16:10:23 -0400 Subject: [PATCH 0656/1949] Add instance- prefix to instance resource id. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This makes it clearer what it is when looking at the output of lift:with-reosurce-id (before it looked like a function binding), and also ensures that we won’t warn you about an unbound function name when using with-resource-id. --- web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index a1686ccc4c..124c4ea0b5 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -254,7 +254,7 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { // Unique identifier for this particular instance of Lift, used for // tagging resources below in attachResourceId. - private val instanceResourceId = Helpers.nextFuncName + private val instanceResourceId = "instance-" + Helpers.nextFuncName /** * Attaches an ID entity for resource URI specified in From e564f5eb53d2d6118537f8634becbe057c29e654 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 16 Mar 2014 17:06:12 -0400 Subject: [PATCH 0657/1949] Fix expectation for blank parameter URI serialization. Also clarify variable names. --- .../net/liftweb/http/testing/MockHttpRequestSpec.scala | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/web/testkit/src/test/scala/net/liftweb/http/testing/MockHttpRequestSpec.scala b/web/testkit/src/test/scala/net/liftweb/http/testing/MockHttpRequestSpec.scala index c07f4ad1a2..d09602a479 100644 --- a/web/testkit/src/test/scala/net/liftweb/http/testing/MockHttpRequestSpec.scala +++ b/web/testkit/src/test/scala/net/liftweb/http/testing/MockHttpRequestSpec.scala @@ -29,7 +29,8 @@ object MockHttpRequestSpec extends Specification { val IF_MODIFIED_HEADER = "If-Modified-Since" val TEST_URL = "https://foo.com/test/this/page?a=b&b=a&a=c" - val TEST_URL_BP = "https://foo.com/test/this/page?a=b&b=a&c=&d" + val TEST_URL_BLANK_PARAMETER = "https://foo.com/test/this/page?a=b&b=a&c=&d" + val TEST_URL_BLANK_PARAMETER_SERIALIZED = "https://foo.com/test/this/page?a=b&b=a&c=&d=" "MockHttpRequest" should { @@ -48,15 +49,15 @@ object MockHttpRequestSpec extends Specification { } "parse parameters with empty values" in { - val testRequest = new MockHttpServletRequest(TEST_URL_BP, "/test") + val testRequest = new MockHttpServletRequest(TEST_URL_BLANK_PARAMETER, "/test") testRequest.getScheme must_== "https" testRequest.isSecure must_== true testRequest.getServerName must_== "foo.com" testRequest.getContextPath must_== "/test" testRequest.getRequestURI must_== "/test/this/page" - testRequest.getRequestURL.toString must_== TEST_URL_BP - testRequest.getQueryString must_== "a=b&b=a&c=&d" + testRequest.getRequestURL.toString must_== TEST_URL_BLANK_PARAMETER_SERIALIZED + testRequest.getQueryString must_== "a=b&b=a&c=&d=" testRequest.getParameter("c") must_== "" testRequest.getParameter("d") must_== "" } From 954ab42b8c5c8d224e20c39917f3d1de6392970b Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 16 Mar 2014 17:06:40 -0400 Subject: [PATCH 0658/1949] Remove expected IllegalArgumentException for blank parameter in spec. --- .../scala/net/liftweb/http/testing/MockHttpRequestSpec.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/web/testkit/src/test/scala/net/liftweb/http/testing/MockHttpRequestSpec.scala b/web/testkit/src/test/scala/net/liftweb/http/testing/MockHttpRequestSpec.scala index d09602a479..c8b13d0da0 100644 --- a/web/testkit/src/test/scala/net/liftweb/http/testing/MockHttpRequestSpec.scala +++ b/web/testkit/src/test/scala/net/liftweb/http/testing/MockHttpRequestSpec.scala @@ -89,7 +89,6 @@ object MockHttpRequestSpec extends Specification { val testRequest = new MockHttpServletRequest(TEST_URL, "/test") (testRequest.queryString ="this=a&&that=b") must throwA[IllegalArgumentException] - (testRequest.queryString = "foo") must throwA[IllegalArgumentException] } From 0d99445374970643d7cdb8ff5d37b58822a4cef0 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Mon, 17 Mar 2014 17:33:46 -0400 Subject: [PATCH 0659/1949] Remove unused hasDeclaredField and its memo. --- core/json/src/main/scala/net/liftweb/json/Meta.scala | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/core/json/src/main/scala/net/liftweb/json/Meta.scala b/core/json/src/main/scala/net/liftweb/json/Meta.scala index 838928e51d..f9ae5918a6 100644 --- a/core/json/src/main/scala/net/liftweb/json/Meta.scala +++ b/core/json/src/main/scala/net/liftweb/json/Meta.scala @@ -221,7 +221,6 @@ private[json] object Meta { classOf[JObject], classOf[JArray]).map((_, ()))) private val primaryConstructorArgumentsMemo = new Memo[Class[_], List[(String, Type)]] - private val hasDeclaredFieldsMemo = new Memo[(Class[_], String), Boolean] private val declaredFieldsMemo = new Memo[Class[_], Map[String,Field]] def constructors(t: Type, names: ParameterNameReader, context: Option[Context]): List[(JConstructor[_], List[(String, Type)])] = @@ -361,17 +360,6 @@ private[json] object Meta { declaredFieldsMemo.memoize(clazz, _ => extractDeclaredFields) } - def hasDeclaredField(clazz: Class[_], name: String): Boolean = { - def declaredField = try { - clazz.getDeclaredField(name) - true - } catch { - case e: NoSuchFieldException => false - } - - hasDeclaredFieldsMemo.memoize((clazz, name), _ => declaredField) - } - def mkJavaArray(x: Any, componentType: Class[_]) = { val arr = x.asInstanceOf[scala.Array[_]] val a = java.lang.reflect.Array.newInstance(componentType, arr.size) From 2e830f14f2552b6188d2810018b06e73e7b329d7 Mon Sep 17 00:00:00 2001 From: Robert Freytag Date: Thu, 3 Apr 2014 18:12:43 +0200 Subject: [PATCH 0660/1949] Fixed stale database connection release - getAutoCommit, rollback and commit can throw exceptions on stale connections, which was preventing a correct connection release --- contributors.md | 6 ++++++ .../db/src/main/scala/net/liftweb/db/DB.scala | 12 +++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/contributors.md b/contributors.md index ff5474dc68..bf743bd7f3 100644 --- a/contributors.md +++ b/contributors.md @@ -203,3 +203,9 @@ Vasya Novikov ### Email: ### n1dr+cm3053lift@yaaandex.com (replace "aaa" with "a") + +### Name: ### +Robert Freytag + +### Email: ### +robertfreytag+lift at gmail .. com diff --git a/persistence/db/src/main/scala/net/liftweb/db/DB.scala b/persistence/db/src/main/scala/net/liftweb/db/DB.scala index bd3b4423e4..c723f3122a 100644 --- a/persistence/db/src/main/scala/net/liftweb/db/DB.scala +++ b/persistence/db/src/main/scala/net/liftweb/db/DB.scala @@ -306,9 +306,15 @@ trait DB extends Loggable { (info.get(name): @unchecked) match { case Some(ConnectionHolder(c, 1, post, manualRollback)) => { - if (! (c.getAutoCommit() || manualRollback)) { - if (rollback) tryo{c.rollback} - else c.commit + // stale and unexpectedly closed connections may throw here + try { + if (! (c.getAutoCommit() || manualRollback)) { + if (rollback) c.rollback + else c.commit + } + } catch { + case e: Exception => + logger.debug("Swallowed exception during connection release. ", e) } tryo(c.releaseFunc()) info -= name From 5b8b7b70bc68f66f43bd2b5f3835cf2850bf1471 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Fri, 4 Apr 2014 13:27:47 -0400 Subject: [PATCH 0661/1949] Implement toSingleBox. The toSingleBox method takes a List[Box[T]] and turns it into a Box[List[T]], via an implicit conversion to ListOfBoxes. --- .../main/scala/net/liftweb/common/Box.scala | 43 ++++++++++++++++++- .../scala/net/liftweb/common/BoxSpec.scala | 23 ++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index 694bae65df..850fd946b5 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -21,6 +21,45 @@ import scala.reflect.Manifest import java.util.{Iterator => JavaIterator, ArrayList => JavaArrayList} +/** + * Helper class to provide an easy way for converting Lists of Boxes[T] into + * a Box of List[T]. +**/ +case class ListOfBoxes[T](theListOfBoxes: List[Box[T]]) { + /** + * Convert a List of Boxes into a single Box containting a List[T], where T is + * the parameterized type of the Boxes. + * + * This method is useful for those cases where you have a lot of operations being + * executed that all return some Box[T]. You want just a List[T] if all of those + * operations succeeded, but you don't want to have Failures disappear if any were + * present in the list. + * + * If all of the Boxes in the List are Full or Empty, we return a Full box containing + * a List of all of the Full Box values that were present. If any of the Boxes contain + * a Failure, a ParamFailure is returned, containing the original List[Box[T]] as the + * param. + * + * @param failureErrorMessage The string that should be placed in the message for the Failure. + * @return A Full[List[T]] if no Failures were present. ParamFailure[List[Box[T]]] otherwise. + **/ + def toSingleBox(failureErrorMessage: String): Box[List[T]] = { + val fulls = theListOfBoxes.collect { + case aFull: Full[T] => aFull + } + + val failures = theListOfBoxes.collect { + case failureBox: Failure => failureBox + } + + if (failures.isEmpty) { + Full(fulls.map(_.value)) + } else { + Failure(failureErrorMessage) ~> theListOfBoxes + } + } +} + /** * The bridge from Java to Scala Box */ @@ -46,7 +85,9 @@ class BoxJBridge { * * It also provides implicit methods to transform Option to Box, Box to Iterable, and Box to Option */ -object Box extends BoxTrait +object Box extends BoxTrait { + implicit def listToListOfBoxes[T](boxes: List[Box[T]]) = ListOfBoxes(boxes) +} /** * The Box companion object provides methods to create a Box from: diff --git a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala index bff37ba19d..90775b0c69 100644 --- a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala +++ b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala @@ -346,6 +346,29 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { } } + "A List[Box[T]]" should { + "be convertable to a Box[List[T]] when all are Full" in { + val someBoxes: List[Box[String]] = List(Full("bacon"), Full("sammich")) + val singleBox = someBoxes.toSingleBox("Box failed!") + + singleBox must_== Full(List("bacon", "sammich")) + } + + "be convertable to a Box[List[T]] when some are Full and some are Empty" in { + val someBoxes: List[Box[String]] = List(Full("bacon"), Full("sammich"), Empty) + val singleBox = someBoxes.toSingleBox("Box failed!") + + singleBox must_== Full(List("bacon", "sammich")) + } + + "be convertable to a ParamFailure[Box[List[T]]] when any are Failure" in { + val someBoxes: List[Box[String]] = List(Full("bacon"), Full("sammich"), Failure("I HATE BACON")) + val singleBox = someBoxes.toSingleBox("This should be in the param failure.") + + singleBox must_== ParamFailure("This should be in the param failure.", None, None, someBoxes) + } + } + } From b89d12453037f855f808239bd00db76fd7d3b368 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 6 Apr 2014 14:45:16 -0400 Subject: [PATCH 0662/1949] Fix Box->Option implicit conversion spec. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We were testing Empty to ensure isDefined returned false, but isDefined hasn’t triggered an implicit conversion to Option in a while. We now test the implicit conversion by verifying that orElse exists and behaves as expected on Options. --- core/common/src/test/scala/net/liftweb/common/BoxSpec.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala index bff37ba19d..ba0bb38c0a 100644 --- a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala +++ b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala @@ -67,8 +67,8 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { Full(1) reduceLeft {(x: Int, y: Int) => x + y} must_== 1 } "be used as an Option" in { - Full(1).get must_== 1 - Empty.isDefined must beFalse + Full(1) orElse Some(2) must_== Some(1) + Empty orElse Some(2) must_== Some(2) } "be implicitly defined from an Option. The open_! method can be used on an Option for example" in { Some(1).openOrThrowException("This is a test") must_== 1 From 38866be54746f079f4041392a7963d50ab531cf8 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 6 Apr 2014 14:45:32 -0400 Subject: [PATCH 0663/1949] Fix a reference to open_! in the Box specs. --- core/common/src/test/scala/net/liftweb/common/BoxSpec.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala index ba0bb38c0a..4cea569f0f 100644 --- a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala +++ b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala @@ -70,7 +70,7 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { Full(1) orElse Some(2) must_== Some(1) Empty orElse Some(2) must_== Some(2) } - "be implicitly defined from an Option. The open_! method can be used on an Option for example" in { + "be implicitly defined from an Option. The openOrThrowException method can be used on an Option for example" in { Some(1).openOrThrowException("This is a test") must_== 1 } "be defined from some legacy code (possibly passing null values). If the passed value is not null, a Full(value) is returned" in { From 0bdee6518ebe8ecf50715df206d8cdfa86c832fd Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 6 Apr 2014 14:46:59 -0400 Subject: [PATCH 0664/1949] Define a get method on Box and immediately deprecate it. We do this so that get no longer triggers an implicit conversion to Option, and triggers a deprecation warning. In the future, get will remain defined but will throw an exception and be defined as returning an internal vestigial type to break compilation. Those who still want to use .get can call .toOption.get. --- .../src/main/scala/net/liftweb/common/Box.scala | 12 ++++++++++++ .../src/test/scala/net/liftweb/common/BoxSpec.scala | 5 +++++ 2 files changed, 17 insertions(+) diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index 694bae65df..fc09f0ebfa 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -234,6 +234,7 @@ sealed abstract class Box[+A] extends Product with Serializable{ */ def openOrThrowException(justification: String): A + /** * Return the value contained in this Box if it is Full; * throw an exception otherwise. @@ -252,6 +253,17 @@ sealed abstract class Box[+A] extends Product with Serializable{ @deprecated("use openOrThrowException, or better yet, do the right thing with your code and use map, flatMap or foreach", "2.4") final def open_! : A = openOrThrowException("Legacy method implementation") + /** + * Return the value contained in this Box if it is Full; throw an + * exception otherwise. Please use openOrThrowException instead. In + * the past, this method triggered an implicit conversion to Option + * and could throw an unintended NullPointerException. That is no longer + * the case, and in Lift 3 this method will be changed to return + * a useless type so that the compiler will break attempts to use it. + */ + @deprecated("use map/flatMap/foreach if possible, or openOrThrowException if you must", "2.6") + final def get: A = open_! + /** * Return the value contained in this Box if it is Full; * throw an exception otherwise. diff --git a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala index 4cea569f0f..76735b6702 100644 --- a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala +++ b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala @@ -79,6 +79,11 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { "be defined from some legacy code (possibly passing null values). If the passed value is null, an Empty is returned" in { Box.legacyNullTest(null) must_== Empty } + + "have get defined in a way compatible with Option" in { + Full(1).get must_== 1 + (Empty: Box[Int]).get must throwA[NullPointerException] + } } "A Box" should { From 50d23b4148bb4124a6ba6c0cbe57b2c2671764b7 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 6 Apr 2014 15:39:46 -0400 Subject: [PATCH 0665/1949] Intentionally cripple Box's .get method for Lift 3. Box.get now returns Nothing, which should break anyone attempting to use its return type for anything, including tests. Also fix a Box test that relied on .get. --- .../src/main/scala/net/liftweb/common/Box.scala | 15 ++++++--------- .../test/scala/net/liftweb/common/BoxSpec.scala | 7 +------ 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index 1511f67f4d..a75da91bd7 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -255,15 +255,12 @@ sealed abstract class Box[+A] extends Product with Serializable{ final def open_! : A = openOrThrowException("Legacy method implementation") */ /** - * Return the value contained in this Box if it is Full; throw an - * exception otherwise. Please use openOrThrowException instead. In - * the past, this method triggered an implicit conversion to Option - * and could throw an unintended NullPointerException. That is no longer - * the case, and in Lift 3 this method will be changed to return - * a useless type so that the compiler will break attempts to use it. - */ - @deprecated("use map/flatMap/foreach if possible, or openOrThrowException if you must", "2.6") - final def get: A = open_! + * Exists to avoid the implicit conversion from Box to Option. Opening a Box + * unsafely should be done using openOrThrowException. + */ + final def get: Nothing = { + throw new Exception("Attempted to open a Box incorrectly. Please use openOrThrowException.") + } /** * Return the value contained in this Box if it is Full; diff --git a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala index 76735b6702..f565334d70 100644 --- a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala +++ b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala @@ -79,11 +79,6 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { "be defined from some legacy code (possibly passing null values). If the passed value is null, an Empty is returned" in { Box.legacyNullTest(null) must_== Empty } - - "have get defined in a way compatible with Option" in { - Full(1).get must_== 1 - (Empty: Box[Int]).get must throwA[NullPointerException] - } } "A Box" should { @@ -299,7 +294,7 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { "A Failure is an Empty Box which" can { "return its cause as an exception" in { case class LiftException(m: String) extends Exception - Failure("error", Full(new LiftException("broken")), Empty).exception.get must_== new LiftException("broken") + Failure("error", Full(new LiftException("broken")), Empty).exception must_== Full(new LiftException("broken")) } "return a chained list of causes" in { Failure("error", From e4aa14287b84b677f599b06477957423ef2c8620 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 6 Apr 2014 15:40:04 -0400 Subject: [PATCH 0666/1949] Drop open_! and openTheBox, which are deprecated. --- .../main/scala/net/liftweb/common/Box.scala | 35 ------------------- 1 file changed, 35 deletions(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index a75da91bd7..5d8321bfb2 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -234,26 +234,6 @@ sealed abstract class Box[+A] extends Product with Serializable{ */ def openOrThrowException(justification: String): A - - /** - * Return the value contained in this Box if it is Full; - * throw an exception otherwise. - * - * Using open_! in an example posted to the Lift mailing list - * may disqualify you for a helpful response. - * - * The method has a '!' in its name. This means "don't use it unless - * you are 100% sure that the Box is Full and you should probably - * comment your code with the explanation of the guaranty." - * The better case for extracting the value out of a Box can - * be found at http://lift.la/scala-option-lift-box-and-how-to-make-your-co - * - * @return the value contained in this Box if it is full; throw an exception otherwise - */ - /* - @deprecated("use openOrThrowException, or better yet, do the right thing with your code and use map, flatMap or foreach", "2.4") - final def open_! : A = openOrThrowException("Legacy method implementation") - */ /** * Exists to avoid the implicit conversion from Box to Option. Opening a Box * unsafely should be done using openOrThrowException. @@ -262,21 +242,6 @@ sealed abstract class Box[+A] extends Product with Serializable{ throw new Exception("Attempted to open a Box incorrectly. Please use openOrThrowException.") } - /** - * Return the value contained in this Box if it is Full; - * throw an exception otherwise. - * This means "don't use it unless - * you are 100% sure that the Box is Full and you should probably - * comment your code with the explanation of the guaranty. - * The better case for extracting the value out of a Box can - * be found at http://lift.la/scala-option-lift-box-and-how-to-make-your-co - * - * @return the value contained in this Box if it is full; throw an exception otherwise - */ - /* - @deprecated("use openOrThrowException, or better yet, do the right thing with your code and use map, flatMap or foreach", "2.4") - final def openTheBox: A = openOrThrowException("Legacy method implementation") - */ /** * Return the value contained in this Box if it is full; otherwise return the specified default * @return the value contained in this Box if it is full; otherwise return the specified default From e375dae9f02b0823dc49c09b8db2e498f39e7dad Mon Sep 17 00:00:00 2001 From: Robert Freytag Date: Mon, 7 Apr 2014 10:53:11 +0200 Subject: [PATCH 0667/1949] narrowed catch clause in connection-release, changed loglevel to error --- .../db/src/main/scala/net/liftweb/db/DB.scala | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/persistence/db/src/main/scala/net/liftweb/db/DB.scala b/persistence/db/src/main/scala/net/liftweb/db/DB.scala index c723f3122a..fb30ea8028 100644 --- a/persistence/db/src/main/scala/net/liftweb/db/DB.scala +++ b/persistence/db/src/main/scala/net/liftweb/db/DB.scala @@ -24,7 +24,7 @@ import Helpers._ import net.liftweb.http.S import javax.sql.{DataSource} -import java.sql.ResultSetMetaData +import java.sql.{ResultSetMetaData, SQLException} import java.sql.{Statement, ResultSet, Types, PreparedStatement, Connection, DriverManager} import scala.collection.mutable.{HashMap, ListBuffer} import javax.naming.{Context, InitialContext} @@ -313,15 +313,16 @@ trait DB extends Loggable { else c.commit } } catch { - case e: Exception => - logger.debug("Swallowed exception during connection release. ", e) + case e: SQLException => + logger.error("Swallowed exception during connection release. ", e) + } finally { + tryo(c.releaseFunc()) + info -= name + val rolledback = rollback | manualRollback + logger.trace("Invoking %d postTransaction functions. rollback=%s".format(post.size, rolledback)) + post.reverse.foreach(f => tryo(f(!rolledback))) + logger.trace("Released %s on thread %s".format(name,Thread.currentThread)) } - tryo(c.releaseFunc()) - info -= name - val rolledback = rollback | manualRollback - logger.trace("Invoking %d postTransaction functions. rollback=%s".format(post.size, rolledback)) - post.reverse.foreach(f => tryo(f(!rolledback))) - logger.trace("Released %s on thread %s".format(name,Thread.currentThread)) } case Some(ConnectionHolder(c, n, post, rb)) => logger.trace("Did not release " + name + " on thread " + Thread.currentThread + " count " + (n - 1)) From 2f4abf24eada1d3a3f28a57f04b2300798a3207f Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Tue, 8 Apr 2014 15:04:38 -0400 Subject: [PATCH 0668/1949] Fix several lift-util tests that used Box.get. --- .../src/test/scala/net/liftweb/util/BindHelpersSpec.scala | 5 ++++- .../src/test/scala/net/liftweb/util/ClassHelpersSpec.scala | 2 +- .../src/test/scala/net/liftweb/util/TimeHelpersSpec.scala | 6 +++++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/core/util/src/test/scala/net/liftweb/util/BindHelpersSpec.scala b/core/util/src/test/scala/net/liftweb/util/BindHelpersSpec.scala index 2937e29167..f0cb30236c 100644 --- a/core/util/src/test/scala/net/liftweb/util/BindHelpersSpec.scala +++ b/core/util/src/test/scala/net/liftweb/util/BindHelpersSpec.scala @@ -87,7 +87,10 @@ object BindHelpersSpec extends Specification { val liftbind = changethis - bindlist(maps, liftbind).get must ==/(

    ) + bindlist(maps, liftbind) must beLike { + case Full(result) => + result must ==/(

    ) + } } } "the bind(namespace, NodeSeq, BindParams*) function" should { diff --git a/core/util/src/test/scala/net/liftweb/util/ClassHelpersSpec.scala b/core/util/src/test/scala/net/liftweb/util/ClassHelpersSpec.scala index 9dca96c2a8..90b4e88a58 100644 --- a/core/util/src/test/scala/net/liftweb/util/ClassHelpersSpec.scala +++ b/core/util/src/test/scala/net/liftweb/util/ClassHelpersSpec.scala @@ -174,7 +174,7 @@ object ClassHelpersSpec extends Specification { createInvoker("length", null) must_== Empty } "return a Full Box with the function from Unit to a Box containing the result of the method to invoke" in { - createInvoker("length", "").openOrThrowException("Test").apply().get must_== 0 + createInvoker("length", "").openOrThrowException("Test").apply() must_== Full(0) } "The invoker function will throw the cause exception if the method can't be called" in { (() => createInvoker("get", "").openOrThrowException("Test").apply)() must throwA[Exception] diff --git a/core/util/src/test/scala/net/liftweb/util/TimeHelpersSpec.scala b/core/util/src/test/scala/net/liftweb/util/TimeHelpersSpec.scala index 2f6c1ddff8..d2d334a0ea 100644 --- a/core/util/src/test/scala/net/liftweb/util/TimeHelpersSpec.scala +++ b/core/util/src/test/scala/net/liftweb/util/TimeHelpersSpec.scala @@ -192,7 +192,11 @@ object TimeHelpersSpec extends Specification with ScalaCheck with TimeAmountsGen val d = now List(null, Nil, None, Failure("", Empty, Empty)) forall { toDate(_) must_== Empty } List(Full(d), Some(d), List(d)) forall { toDate(_) must_== Full(d) } - toDate(internetDateFormatter.format(d)).get.getTime.toLong must beCloseTo(d.getTime.toLong, 1000L) + + toDate(internetDateFormatter.format(d)) must beLike { + case Full(converted) => + converted.getTime.toLong must beCloseTo(d.getTime.toLong, 1000L) + } } } From b70ac63aedb7a59f2b3367272908eff05a4fa84f Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Tue, 8 Apr 2014 18:38:39 -0400 Subject: [PATCH 0669/1949] Add mockito test dependency to lift-webkit. --- project/Build.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/project/Build.scala b/project/Build.scala index 959d9b2d22..40104fc807 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -103,6 +103,7 @@ object BuildDef extends Build { lazy val webkit = webProject("webkit") .dependsOn(util, testkit % "provided") + .settings(libraryDependencies += mockito_all) .settings(yuiCompressor.Plugin.yuiSettings: _*) .settings(description := "Webkit Library", parallelExecution in Test := false, From 2b93d7655ab5a676c2d4c2ec7023fc26201edd0f Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Tue, 8 Apr 2014 18:39:45 -0400 Subject: [PATCH 0670/1949] Simplify the toSingleBox implementation. --- .../src/main/scala/net/liftweb/common/Box.scala | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index 850fd946b5..cb0126931d 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -44,18 +44,10 @@ case class ListOfBoxes[T](theListOfBoxes: List[Box[T]]) { * @return A Full[List[T]] if no Failures were present. ParamFailure[List[Box[T]]] otherwise. **/ def toSingleBox(failureErrorMessage: String): Box[List[T]] = { - val fulls = theListOfBoxes.collect { - case aFull: Full[T] => aFull - } - - val failures = theListOfBoxes.collect { - case failureBox: Failure => failureBox - } - - if (failures.isEmpty) { - Full(fulls.map(_.value)) - } else { + if (theListOfBoxes.exists(_.isInstanceOf[Failure])) { Failure(failureErrorMessage) ~> theListOfBoxes + } else { + Full(theListOfBoxes.flatten) } } } From 70ee2014848f13f67371c757438c4df6899ee58e Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Tue, 8 Apr 2014 18:41:38 -0400 Subject: [PATCH 0671/1949] Make Req.json return a Failure on incorrect content-type. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Until now, it returned Empty. This helps clarify what’s going on to consumers. Additionally, added a test to verify this, and some clarifying documentation to the method. --- .../src/main/scala/net/liftweb/http/Req.scala | 41 ++++++++++++------- .../test/scala/net/liftweb/http/ReqSpec.scala | 25 +++++++++++ 2 files changed, 52 insertions(+), 14 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/Req.scala b/web/webkit/src/main/scala/net/liftweb/http/Req.scala index b82563087f..74765a1322 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Req.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Req.scala @@ -984,24 +984,37 @@ class Req(val path: ParsePath, sid <- httpRequest.sessionId } yield sid - lazy val json: Box[JsonAST.JValue] = - if (!json_?) Empty - else try { - import java.io._ + /** + * The JValue representation of this Req's body, if the body is JSON-parsable + * AND the content-type of the request is JSON. Returns a Failure if + * the request is not considered a JSON request (see json_?), or if + * there was an error parsing the JSON. + * + * If you want to forcibly evaluate the request body as JSON, ignoring + * content type, see bodyAsJson. + */ + lazy val json: Box[JsonAST.JValue] = { + if (!json_?) { + Failure("Cannot parse non-JSON request as JSON; please check content-type.") + } else { + try { + import java.io._ - def r = """; *charset=(.*)""".r - def r2 = """[^=]*$""".r + def r = """; *charset=(.*)""".r + def r2 = """[^=]*$""".r - def charSet: String = contentType.flatMap(ct => r.findFirstIn(ct).flatMap(r2.findFirstIn)).getOrElse("UTF-8") + def charSet: String = contentType.flatMap(ct => r.findFirstIn(ct).flatMap(r2.findFirstIn)).getOrElse("UTF-8") - body.map(b => - JsonParser.parse(new - InputStreamReader(new - ByteArrayInputStream(b), charSet))) - } catch { - case e: LiftFlowOfControlException => throw e - case e: Exception => Failure(e.getMessage, Full(e), Empty) + body.map(b => + JsonParser.parse(new + InputStreamReader(new + ByteArrayInputStream(b), charSet))) + } catch { + case e: LiftFlowOfControlException => throw e + case e: Exception => Failure(e.getMessage, Full(e), Empty) + } } + } private def containerRequest = Box !! request /** diff --git a/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala index 7accc87437..673d5ee641 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala @@ -17,10 +17,15 @@ package net.liftweb package http +import java.io.ByteArrayInputStream + import org.specs2.mutable.Specification +import org.mockito.Mockito._ + import common._ +import provider._ /** * System under specification for Req. @@ -88,6 +93,26 @@ object ReqSpec extends Specification { ieVersions must_== List(6, 7, 8, 9, 10, 11) } + + "when trying to JSON parse the request body" in { + "with an invalid Content-Type should return a Failure" in { + val testJson = """{ "booyan": "shazam", "booyak": 5, "bazam": 2.5 }""" + val mockHttpRequest = mock(classOf[HTTPRequest]) + var paramCalcInfo = ParamCalcInfo(Nil, Map.empty, Nil, Full(BodyOrInputStream(new ByteArrayInputStream(testJson.getBytes("UTF-8"))))) + + val req = + new Req( + Req.NilPath, "/", GetRequest, + Full("text/plain"), + mockHttpRequest, + nanoStart = 0l, nanoEnd = 1l, _stateless_? = true, + () => paramCalcInfo, + addlParams = Map.empty + ) + + req.json should beAnInstanceOf[Failure] + } + } } } From 1d2cf1477a3e0775669c6391b7468d49c32683c7 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Tue, 8 Apr 2014 18:42:54 -0400 Subject: [PATCH 0672/1949] Update toSingleBox doc to state that List sizes may differ. --- core/common/src/main/scala/net/liftweb/common/Box.scala | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index cb0126931d..4b7507e805 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -40,6 +40,10 @@ case class ListOfBoxes[T](theListOfBoxes: List[Box[T]]) { * a Failure, a ParamFailure is returned, containing the original List[Box[T]] as the * param. * + * It is worth noting that the size of the list in the resulting Box[List[T]] may not be equal + * to the size of the List[Box[T]] that is fed as Empty values will disappear altogether in the + * conversion. + * * @param failureErrorMessage The string that should be placed in the message for the Failure. * @return A Full[List[T]] if no Failures were present. ParamFailure[List[Box[T]]] otherwise. **/ From c3128a5da53d76f24aec5f0e6a7dfbd9d1644d4a Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Tue, 8 Apr 2014 21:51:46 -0400 Subject: [PATCH 0673/1949] Abstract req setup for Req JSON tests into mockReq trait. --- .../test/scala/net/liftweb/http/ReqSpec.scala | 45 +++++++++++-------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala index 673d5ee641..5b0a9e92f6 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala @@ -19,18 +19,21 @@ package http import java.io.ByteArrayInputStream -import org.specs2.mutable.Specification - import org.mockito.Mockito._ +import org.specs2.mutable.Specification +import org.specs2.mock.Mockito +import org.specs2.specification.Scope + import common._ +import json.JsonDSL._ import provider._ /** * System under specification for Req. */ -object ReqSpec extends Specification { +object ReqSpec extends Specification with Mockito { "Req Specification".title private val iPhoneUserAgents = @@ -94,23 +97,27 @@ object ReqSpec extends Specification { ieVersions must_== List(6, 7, 8, 9, 10, 11) } + class mockReq extends Scope { + val testJson = """{ "booyan": "shazam", "booyak": 5, "bazam": 2.5 }""" + val mockHttpRequest = mock[HTTPRequest] + var paramCalcInfo = ParamCalcInfo(Nil, Map.empty, Nil, Full(BodyOrInputStream(new ByteArrayInputStream(testJson.getBytes("UTF-8"))))) + + def req(contentType: String) = { + new Req( + Req.NilPath, "/", GetRequest, + Full(contentType), + mockHttpRequest, + 0l, 1l, true, + () => paramCalcInfo, + Map.empty + ) + } + } + "when trying to JSON parse the request body" in { - "with an invalid Content-Type should return a Failure" in { - val testJson = """{ "booyan": "shazam", "booyak": 5, "bazam": 2.5 }""" - val mockHttpRequest = mock(classOf[HTTPRequest]) - var paramCalcInfo = ParamCalcInfo(Nil, Map.empty, Nil, Full(BodyOrInputStream(new ByteArrayInputStream(testJson.getBytes("UTF-8"))))) - - val req = - new Req( - Req.NilPath, "/", GetRequest, - Full("text/plain"), - mockHttpRequest, - nanoStart = 0l, nanoEnd = 1l, _stateless_? = true, - () => paramCalcInfo, - addlParams = Map.empty - ) - - req.json should beAnInstanceOf[Failure] + "with an invalid Content-Type should return a Failure" in new mockReq { + req("text/plain").json should beAnInstanceOf[Failure] + } } } } From ac293f84c1c1c380312a4b1643d6d7861b7c5d3b Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Tue, 8 Apr 2014 21:52:08 -0400 Subject: [PATCH 0674/1949] Add specs for valid Req.json for valid JSON content types. --- .../test/scala/net/liftweb/http/ReqSpec.scala | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala index 5b0a9e92f6..ea26bd84e9 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala @@ -118,6 +118,25 @@ object ReqSpec extends Specification with Mockito { "with an invalid Content-Type should return a Failure" in new mockReq { req("text/plain").json should beAnInstanceOf[Failure] } + + "with an application/json Content-Type should return the result of parsing the JSON" in new mockReq { + req("application/json").json should_== + Full( + ("booyan" -> "shazam") ~ + ("booyak" -> 5) ~ + ("bazam" -> 2.5) + ) + } + + "with a text/json Content-Type should return the result of parsing the JSON" in new mockReq { + req("text/json").json should_== + Full( + ("booyan" -> "shazam") ~ + ("booyak" -> 5) ~ + ("bazam" -> 2.5) + ) + } + } } } } From dd866466673adecf9c8fac3ea3d1f16b1cd97f45 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Tue, 8 Apr 2014 21:53:55 -0400 Subject: [PATCH 0675/1949] Rename mockReq to mockJsonReq. This reflects the motivation for using it a little better. --- web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala index ea26bd84e9..cf15e5c92a 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala @@ -97,8 +97,9 @@ object ReqSpec extends Specification with Mockito { ieVersions must_== List(6, 7, 8, 9, 10, 11) } - class mockReq extends Scope { + class mockJsonReq extends Scope { val testJson = """{ "booyan": "shazam", "booyak": 5, "bazam": 2.5 }""" + val mockHttpRequest = mock[HTTPRequest] var paramCalcInfo = ParamCalcInfo(Nil, Map.empty, Nil, Full(BodyOrInputStream(new ByteArrayInputStream(testJson.getBytes("UTF-8"))))) @@ -115,11 +116,11 @@ object ReqSpec extends Specification with Mockito { } "when trying to JSON parse the request body" in { - "with an invalid Content-Type should return a Failure" in new mockReq { + "with an invalid Content-Type should return a Failure" in new mockJsonReq { req("text/plain").json should beAnInstanceOf[Failure] } - "with an application/json Content-Type should return the result of parsing the JSON" in new mockReq { + "with an application/json Content-Type should return the result of parsing the JSON" in new mockJsonReq { req("application/json").json should_== Full( ("booyan" -> "shazam") ~ @@ -128,7 +129,7 @@ object ReqSpec extends Specification with Mockito { ) } - "with a text/json Content-Type should return the result of parsing the JSON" in new mockReq { + "with a text/json Content-Type should return the result of parsing the JSON" in new mockJsonReq { req("text/json").json should_== Full( ("booyan" -> "shazam") ~ From c44bda90d0f377fef91b33a690e4e933f3cf66ec Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Tue, 8 Apr 2014 21:56:09 -0400 Subject: [PATCH 0676/1949] Move expected parsed JSON into mockJsonReq for spec use. --- .../test/scala/net/liftweb/http/ReqSpec.scala | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala index cf15e5c92a..a407c180eb 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala @@ -99,6 +99,10 @@ object ReqSpec extends Specification with Mockito { class mockJsonReq extends Scope { val testJson = """{ "booyan": "shazam", "booyak": 5, "bazam": 2.5 }""" + val parsedJson = + ("booyan" -> "shazam") ~ + ("booyak" -> 5) ~ + ("bazam" -> 2.5) val mockHttpRequest = mock[HTTPRequest] var paramCalcInfo = ParamCalcInfo(Nil, Map.empty, Nil, Full(BodyOrInputStream(new ByteArrayInputStream(testJson.getBytes("UTF-8"))))) @@ -121,21 +125,11 @@ object ReqSpec extends Specification with Mockito { } "with an application/json Content-Type should return the result of parsing the JSON" in new mockJsonReq { - req("application/json").json should_== - Full( - ("booyan" -> "shazam") ~ - ("booyak" -> 5) ~ - ("bazam" -> 2.5) - ) + req("application/json").json should_== Full(parsedJson) } "with a text/json Content-Type should return the result of parsing the JSON" in new mockJsonReq { - req("text/json").json should_== - Full( - ("booyan" -> "shazam") ~ - ("booyak" -> 5) ~ - ("bazam" -> 2.5) - ) + req("text/json").json should_== Full(parsedJson) } } } From 47e8d34ceea3df4697522046075985480fac487e Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Tue, 8 Apr 2014 22:02:41 -0400 Subject: [PATCH 0677/1949] Add bodyAsJson method. bodyAsJson ignores content type and forcibly tries to parse a request body as JSON. Failure is only reported when there is an issue doing the actual JSON parse. --- .../src/main/scala/net/liftweb/http/Req.scala | 34 ++++++++++++------- .../test/scala/net/liftweb/http/ReqSpec.scala | 12 +++++++ 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/Req.scala b/web/webkit/src/main/scala/net/liftweb/http/Req.scala index 74765a1322..a36bdf360d 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Req.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Req.scala @@ -997,22 +997,30 @@ class Req(val path: ParsePath, if (!json_?) { Failure("Cannot parse non-JSON request as JSON; please check content-type.") } else { - try { - import java.io._ + bodyAsJson + } + } - def r = """; *charset=(.*)""".r - def r2 = """[^=]*$""".r + /** + * Forcibly tries to parse the request body as JSON. Does not perform any + * content type checks, unlike the json method. + */ + lazy val bodyAsJson: Box[JsonAST.JValue] = { + try { + import java.io._ - def charSet: String = contentType.flatMap(ct => r.findFirstIn(ct).flatMap(r2.findFirstIn)).getOrElse("UTF-8") + def r = """; *charset=(.*)""".r + def r2 = """[^=]*$""".r + + def charSet: String = contentType.flatMap(ct => r.findFirstIn(ct).flatMap(r2.findFirstIn)).getOrElse("UTF-8") - body.map(b => - JsonParser.parse(new - InputStreamReader(new - ByteArrayInputStream(b), charSet))) - } catch { - case e: LiftFlowOfControlException => throw e - case e: Exception => Failure(e.getMessage, Full(e), Empty) - } + body.map(b => + JsonParser.parse(new + InputStreamReader(new + ByteArrayInputStream(b), charSet))) + } catch { + case e: LiftFlowOfControlException => throw e + case e: Exception => Failure(e.getMessage, Full(e), Empty) } } diff --git a/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala index a407c180eb..be44e499d3 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala @@ -132,6 +132,18 @@ object ReqSpec extends Specification with Mockito { req("text/json").json should_== Full(parsedJson) } } + + "when forcing a request body JSON parse with bodyAsJson" in { + "with an invalid Content-Type should return the result of parsing the JSON" in new mockJsonReq { + req("text/plain").bodyAsJson should_== Full(parsedJson) + } + + "with an application/json Content-Type should return the result of parsing the JSON" in new mockJsonReq { + req("application/json").bodyAsJson should_== Full(parsedJson) + } + + "with a text/json Content-Type should return the result of parsing the JSON" in new mockJsonReq { + req("text/json").bodyAsJson should_== Full(parsedJson) } } } From cd0804cc0563d88962312558f2aa4a43f9eab1f3 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Tue, 8 Apr 2014 22:08:44 -0400 Subject: [PATCH 0678/1949] Abstract mockJsonReq into mockReq and mockJsonReq. This lays the groundwork for an easy mockXmlReq to test XML body types. --- .../test/scala/net/liftweb/http/ReqSpec.scala | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala index be44e499d3..5ba5fd82b5 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala @@ -97,15 +97,11 @@ object ReqSpec extends Specification with Mockito { ieVersions must_== List(6, 7, 8, 9, 10, 11) } - class mockJsonReq extends Scope { - val testJson = """{ "booyan": "shazam", "booyak": 5, "bazam": 2.5 }""" - val parsedJson = - ("booyan" -> "shazam") ~ - ("booyak" -> 5) ~ - ("bazam" -> 2.5) - + trait mockReq extends Scope { val mockHttpRequest = mock[HTTPRequest] - var paramCalcInfo = ParamCalcInfo(Nil, Map.empty, Nil, Full(BodyOrInputStream(new ByteArrayInputStream(testJson.getBytes("UTF-8"))))) + def paramCalcInfo = ParamCalcInfo(Nil, Map.empty, Nil, Full(BodyOrInputStream(new ByteArrayInputStream(bodyBytes)))) + + def bodyBytes: Array[Byte] def req(contentType: String) = { new Req( @@ -119,6 +115,17 @@ object ReqSpec extends Specification with Mockito { } } + trait mockJsonReq extends mockReq { + val testJson = """{ "booyan": "shazam", "booyak": 5, "bazam": 2.5 }""" + val parsedJson = + ("booyan" -> "shazam") ~ + ("booyak" -> 5) ~ + ("bazam" -> 2.5) + + def bodyBytes = { + testJson.getBytes("UTF-8") + } + } "when trying to JSON parse the request body" in { "with an invalid Content-Type should return a Failure" in new mockJsonReq { req("text/plain").json should beAnInstanceOf[Failure] From 41d9ff4551931a334a661029cfb7d62eee017ba0 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Tue, 8 Apr 2014 22:10:44 -0400 Subject: [PATCH 0679/1949] Make Req.xml return a Failure on incorrect content type. --- .../src/main/scala/net/liftweb/http/Req.scala | 20 +++++++++------- .../test/scala/net/liftweb/http/ReqSpec.scala | 24 +++++++++++++++++++ 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/Req.scala b/web/webkit/src/main/scala/net/liftweb/http/Req.scala index a36bdf360d..409ada6d27 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Req.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Req.scala @@ -1044,15 +1044,19 @@ class Req(val path: ParsePath, }) openOr "" - lazy val xml: Box[Elem] = if (!xml_?) Empty - else - try { - import java.io._ - body.map(b => XML.load(new ByteArrayInputStream(b))) - } catch { - case e: LiftFlowOfControlException => throw e - case e: Exception => Failure(e.getMessage, Full(e), Empty) + lazy val xml: Box[Elem] = { + if (!xml_?) { + Failure("Cannot parse non-XML request as XML; please check content-type.") + } else { + try { + import java.io._ + body.map(b => XML.load(new ByteArrayInputStream(b))) + } catch { + case e: LiftFlowOfControlException => throw e + case e: Exception => Failure(e.getMessage, Full(e), Empty) + } } + } /** * The SiteMap Loc associated with this Req diff --git a/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala index 5ba5fd82b5..ec11b42712 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala @@ -126,6 +126,16 @@ object ReqSpec extends Specification with Mockito { testJson.getBytes("UTF-8") } } + + trait mockXmlReq extends mockReq { + val testXml = """Oh yeah""" + val parsedXml = Oh yeah + + def bodyBytes = { + testXml.getBytes("UTF-8") + } + } + "when trying to JSON parse the request body" in { "with an invalid Content-Type should return a Failure" in new mockJsonReq { req("text/plain").json should beAnInstanceOf[Failure] @@ -153,6 +163,20 @@ object ReqSpec extends Specification with Mockito { req("text/json").bodyAsJson should_== Full(parsedJson) } } + + "when trying to XML parse the request body" in { + "with an invalid Content-Type should return a Failure" in new mockXmlReq { + req("text/plain").xml should beAnInstanceOf[Failure] + } + + "with an application/xml Content-Type should return the result of parsing the JSON" in new mockXmlReq { + req("application/xml").xml should_== Full(parsedXml) + } + + "with a text/xml Content-Type should return the result of parsing the JSON" in new mockXmlReq { + req("text/xml").xml should_== Full(parsedXml) + } + } } } From db6b23c2c9846f70f5b51615b6363ff983420f87 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Tue, 8 Apr 2014 22:41:29 -0400 Subject: [PATCH 0680/1949] Add bodyAsXml method. bodyAsXml ignores content type and forcibly tries to parse a request body as XML. Failure is only reported when there is an issue doing the actual XML parse. --- .../src/main/scala/net/liftweb/http/Req.scala | 32 ++++++++++++++----- .../test/scala/net/liftweb/http/ReqSpec.scala | 14 ++++++++ 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/Req.scala b/web/webkit/src/main/scala/net/liftweb/http/Req.scala index 409ada6d27..6766e33829 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Req.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Req.scala @@ -1043,18 +1043,34 @@ class Req(val path: ParsePath, case (sch, port) => sch + "://" + r.serverName + ":" + port + contextPath }) openOr "" - + /** + * The Elem representation of this Req's body, if the body is XML-parsable + * AND the content-type of the request is XML. Returns a Failure if + * the request is not considered a XML request (see xml_?), or if + * there was an error parsing the XML. + * + * If you want to forcibly evaluate the request body as XML, ignoring + * content type, see bodyAsXml. + */ lazy val xml: Box[Elem] = { if (!xml_?) { Failure("Cannot parse non-XML request as XML; please check content-type.") } else { - try { - import java.io._ - body.map(b => XML.load(new ByteArrayInputStream(b))) - } catch { - case e: LiftFlowOfControlException => throw e - case e: Exception => Failure(e.getMessage, Full(e), Empty) - } + bodyAsXml + } + } + + /** + * Forcibly tries to parse the request body as XML. Does not perform any + * content type checks, unlike the xml method. + */ + lazy val bodyAsXml: Box[Elem] = { + try { + import java.io._ + body.map(b => XML.load(new ByteArrayInputStream(b))) + } catch { + case e: LiftFlowOfControlException => throw e + case e: Exception => Failure(e.getMessage, Full(e), Empty) } } diff --git a/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala index ec11b42712..4a9414a1ed 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala @@ -177,6 +177,20 @@ object ReqSpec extends Specification with Mockito { req("text/xml").xml should_== Full(parsedXml) } } + + "when forcing a request body XML parse with bodyAsXml" in { + "with an invalid Content-Type should return the result of parsing the JSON" in new mockXmlReq { + req("text/plain").bodyAsXml should_== Full(parsedXml) + } + + "with an application/json Content-Type should return the result of parsing the JSON" in new mockXmlReq { + req("application/xml").bodyAsXml should_== Full(parsedXml) + } + + "with a text/json Content-Type should return the result of parsing the JSON" in new mockXmlReq { + req("text/xml").bodyAsXml should_== Full(parsedXml) + } + } } } From f39016e70b7b41007b85450cd51a08407ccd064b Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 9 Apr 2014 23:14:37 -0400 Subject: [PATCH 0681/1949] Rework ListOfBoxes and implicit to implicit class extending AnyVal. This avoids an allocation and avoids the implicit conversion method. --- .../main/scala/net/liftweb/common/Box.scala | 71 +++++++++---------- 1 file changed, 35 insertions(+), 36 deletions(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index 4f9cbd699a..a45162b0c8 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -21,41 +21,6 @@ import scala.reflect.Manifest import java.util.{Iterator => JavaIterator, ArrayList => JavaArrayList} -/** - * Helper class to provide an easy way for converting Lists of Boxes[T] into - * a Box of List[T]. -**/ -case class ListOfBoxes[T](theListOfBoxes: List[Box[T]]) { - /** - * Convert a List of Boxes into a single Box containting a List[T], where T is - * the parameterized type of the Boxes. - * - * This method is useful for those cases where you have a lot of operations being - * executed that all return some Box[T]. You want just a List[T] if all of those - * operations succeeded, but you don't want to have Failures disappear if any were - * present in the list. - * - * If all of the Boxes in the List are Full or Empty, we return a Full box containing - * a List of all of the Full Box values that were present. If any of the Boxes contain - * a Failure, a ParamFailure is returned, containing the original List[Box[T]] as the - * param. - * - * It is worth noting that the size of the list in the resulting Box[List[T]] may not be equal - * to the size of the List[Box[T]] that is fed as Empty values will disappear altogether in the - * conversion. - * - * @param failureErrorMessage The string that should be placed in the message for the Failure. - * @return A Full[List[T]] if no Failures were present. ParamFailure[List[Box[T]]] otherwise. - **/ - def toSingleBox(failureErrorMessage: String): Box[List[T]] = { - if (theListOfBoxes.exists(_.isInstanceOf[Failure])) { - Failure(failureErrorMessage) ~> theListOfBoxes - } else { - Full(theListOfBoxes.flatten) - } - } -} - /** * The bridge from Java to Scala Box */ @@ -82,7 +47,41 @@ class BoxJBridge { * It also provides implicit methods to transform Option to Box, Box to Iterable, and Box to Option */ object Box extends BoxTrait { - implicit def listToListOfBoxes[T](boxes: List[Box[T]]) = ListOfBoxes(boxes) + /** + * Helper class to provide an easy way for converting Lists of Boxes[T] into + * a Box of List[T]. + **/ + implicit class ListOfBoxes[T](val theListOfBoxes: List[Box[T]]) extends AnyVal { + /** + * Convert a List of Boxes into a single Box containting a List[T], where T is + * the parameterized type of the Boxes. + * + * This method is useful for those cases where you have a lot of operations being + * executed that all return some Box[T]. You want just a List[T] if all of those + * operations succeeded, but you don't want to have Failures disappear if any were + * present in the list. + * + * If all of the Boxes in the List are Full or Empty, we return a Full box containing + * a List of all of the Full Box values that were present. If any of the Boxes contain + * a Failure, a ParamFailure is returned, containing the original List[Box[T]] as the + * param. + * + * It is worth noting that the size of the list in the resulting Box[List[T]] may not be equal + * to the size of the List[Box[T]] that is fed as Empty values will disappear altogether in the + * conversion. + * + * @param failureErrorMessage The string that should be placed in the message for the Failure. + * @return A Full[List[T]] if no Failures were present. ParamFailure[List[Box[T]]] otherwise. + **/ + def toSingleBox(failureErrorMessage: String): Box[List[T]] = { + if (theListOfBoxes.exists(_.isInstanceOf[Failure])) { + Failure(failureErrorMessage) ~> theListOfBoxes + } else { + Full(theListOfBoxes.flatten) + } + } + } + } /** From 018c329d3b4a18cbcac87cab6e651fa12f9eece2 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Thu, 10 Apr 2014 20:21:40 -0400 Subject: [PATCH 0682/1949] Add LiftRules.supplementalHeaders and deprecate supplimental variant. supplementalHeaders is now a FactoryMaker, whereas supplimentalHeaders was a volatile var containing a generation function, though it had recently been switched to point to the listOfSupplimentalHeaders FactoryMaker. --- .../src/main/scala/net/liftweb/http/LiftRules.scala | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 124c4ea0b5..e49a06a528 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -1560,9 +1560,13 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { /** * Compute the headers to be sent to the browser in addition to anything else that's sent */ - val listOfSupplimentalHeaders: FactoryMaker[List[(String, String)]] = new FactoryMaker(() => List(("X-Lift-Version", liftVersion), ("X-Frame-Options", "SAMEORIGIN"))) {} + val supplementalHeaders: FactoryMaker[List[(String, String)]] = new FactoryMaker(() => List(("X-Lift-Version", liftVersion), ("X-Frame-Options", "SAMEORIGIN"))) {} - @volatile var supplimentalHeaders: HTTPResponse => Unit = s => listOfSupplimentalHeaders.vend.foreach{case (k, v) => s.addHeaders(List(HTTPParam(k, v)))} + @deprecated("Use supplementalHeaders with an 'e'. This misspelled variant will be removed in Lift 3.0.", "2.6") + final val listOfSupplimentalHeaders = supplementalHeaders + + @deprecated("Use the supplementalHeaders (with an 'e') FactoryMaker. This misspelled variant will be removed in Lift 3.0.", "2.6") + @volatile var supplimentalHeaders: HTTPResponse => Unit = s => supplementalHeaders.vend.foreach{case (k, v) => s.addHeaders(List(HTTPParam(k, v)))} @volatile var calcIE6ForResponse: () => Boolean = () => S.request.map(_.isIE6) openOr false From 1b90b0b81605695e0a32acfa58d8465674a6026e Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Thu, 10 Apr 2014 20:54:28 -0400 Subject: [PATCH 0683/1949] Fix manifest and pure value warnings in common. --- core/common/src/main/scala/net/liftweb/common/Box.scala | 2 +- core/common/src/main/scala/net/liftweb/common/LRU.scala | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index 4f9cbd699a..ce7afc8fe7 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -638,7 +638,7 @@ final case class Full[+A](value: A) extends Box[A]{ case _ => Empty } - override def asA[B](implicit m: Manifest[B]): Box[B] = this.isA(m.erasure).asInstanceOf[Box[B]] + override def asA[B](implicit m: Manifest[B]): Box[B] = this.isA(m.runtimeClass).asInstanceOf[Box[B]] override def ===[B >: A](to: B): Boolean = value == to diff --git a/core/common/src/main/scala/net/liftweb/common/LRU.scala b/core/common/src/main/scala/net/liftweb/common/LRU.scala index 11a3b9252d..b4da7bedde 100644 --- a/core/common/src/main/scala/net/liftweb/common/LRU.scala +++ b/core/common/src/main/scala/net/liftweb/common/LRU.scala @@ -34,7 +34,6 @@ private[common] trait LinkedListElem[T1, T2] { what._prev = this _next._prev = what this._next = what - what } private[common] def addAtTail(what: LinkedListElem[T1, T2]) { @@ -42,7 +41,6 @@ private[common] trait LinkedListElem[T1, T2] { what._next = this _prev._next = what this._prev = what - what } } From 80c15bb54fb18882656ea4eb56219dd2b2729b5d Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Thu, 10 Apr 2014 20:54:42 -0400 Subject: [PATCH 0684/1949] Add feature imports as needed in common. --- core/common/src/main/scala/net/liftweb/common/Box.scala | 5 ++++- .../src/main/scala/net/liftweb/common/CombinableBox.scala | 2 ++ .../src/main/scala/net/liftweb/common/Conversions.scala | 1 + .../src/main/scala/net/liftweb/common/FuncJBridge.scala | 1 + 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index ce7afc8fe7..b0fbb3182e 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -17,6 +17,8 @@ package net.liftweb package common +import scala.language.implicitConversions +import scala.language.existentials import scala.reflect.Manifest import java.util.{Iterator => JavaIterator, ArrayList => JavaArrayList} @@ -209,11 +211,12 @@ sealed trait BoxTrait { def isA[A, B](in: A, clz: Class[B]): Box[B] = (Box !! in).isA(clz) + // FIXME Why do we need an existential type here? /** * Create a Full box containing the specified value if in is of * type B; Empty otherwise. */ - def asA[B](in: T forSome {type T})(implicit m: Manifest[B]): Box[B] = + def asA[B](in: T forSome { type T })(implicit m: Manifest[B]): Box[B] = (Box !! in).asA[B] } diff --git a/core/common/src/main/scala/net/liftweb/common/CombinableBox.scala b/core/common/src/main/scala/net/liftweb/common/CombinableBox.scala index 4475ec97fb..a763b2d0fb 100644 --- a/core/common/src/main/scala/net/liftweb/common/CombinableBox.scala +++ b/core/common/src/main/scala/net/liftweb/common/CombinableBox.scala @@ -17,6 +17,8 @@ package net.liftweb package common +import scala.language.implicitConversions + /** *

    * Via an HList containing a Collection of Box[things], either generate an diff --git a/core/common/src/main/scala/net/liftweb/common/Conversions.scala b/core/common/src/main/scala/net/liftweb/common/Conversions.scala index d67c674ed0..2046d4bb48 100644 --- a/core/common/src/main/scala/net/liftweb/common/Conversions.scala +++ b/core/common/src/main/scala/net/liftweb/common/Conversions.scala @@ -17,6 +17,7 @@ package net.liftweb package common +import scala.language.implicitConversions import scala.xml.NodeSeq /* diff --git a/core/common/src/main/scala/net/liftweb/common/FuncJBridge.scala b/core/common/src/main/scala/net/liftweb/common/FuncJBridge.scala index febbdd3e97..23790371ef 100644 --- a/core/common/src/main/scala/net/liftweb/common/FuncJBridge.scala +++ b/core/common/src/main/scala/net/liftweb/common/FuncJBridge.scala @@ -17,6 +17,7 @@ package net.liftweb package common +import scala.language.implicitConversions object FuncJBridge extends FuncJBridge From 153d8943415db1c265dcd1ac9cf6dabf08f678c0 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Thu, 10 Apr 2014 21:16:48 -0400 Subject: [PATCH 0685/1949] Fix lift-markdown feature warnings. --- .../src/main/scala/net/liftweb/markdown/BaseParsers.scala | 5 +++-- .../src/main/scala/net/liftweb/markdown/BlockParsers.scala | 2 ++ .../src/main/scala/net/liftweb/markdown/InlineParsers.scala | 2 ++ .../src/main/scala/net/liftweb/markdown/LineTokenizer.scala | 5 +++-- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/core/markdown/src/main/scala/net/liftweb/markdown/BaseParsers.scala b/core/markdown/src/main/scala/net/liftweb/markdown/BaseParsers.scala index f97278b3d2..58b9bb0dfb 100644 --- a/core/markdown/src/main/scala/net/liftweb/markdown/BaseParsers.scala +++ b/core/markdown/src/main/scala/net/liftweb/markdown/BaseParsers.scala @@ -19,11 +19,12 @@ package net.liftweb.markdown * Christoph Henkelmann http://henkelmann.eu/ */ +import scala.language.postfixOps + import util.parsing.json.Parser import util.parsing.combinator.RegexParsers import collection.SortedMap - /** * Basic parsers for Markdown Source. * Provides general, small parsers that are used by other parsers. @@ -270,4 +271,4 @@ trait BaseParsers extends RegexParsers { case e: NoSuccess => throw new IllegalArgumentException("Could not parse '" + in + "': " + e) } } -} \ No newline at end of file +} diff --git a/core/markdown/src/main/scala/net/liftweb/markdown/BlockParsers.scala b/core/markdown/src/main/scala/net/liftweb/markdown/BlockParsers.scala index d4c14f5138..7f2fb19d6b 100644 --- a/core/markdown/src/main/scala/net/liftweb/markdown/BlockParsers.scala +++ b/core/markdown/src/main/scala/net/liftweb/markdown/BlockParsers.scala @@ -19,6 +19,8 @@ package net.liftweb.markdown * Christoph Henkelmann http://henkelmann.eu/ */ +import scala.language.postfixOps + import collection.immutable.StringOps import collection.mutable.ListBuffer import xml.{Group, Node, Text, NodeSeq, Elem => XmlElem, TopScope, XML} diff --git a/core/markdown/src/main/scala/net/liftweb/markdown/InlineParsers.scala b/core/markdown/src/main/scala/net/liftweb/markdown/InlineParsers.scala index d12762df74..4a0b281549 100644 --- a/core/markdown/src/main/scala/net/liftweb/markdown/InlineParsers.scala +++ b/core/markdown/src/main/scala/net/liftweb/markdown/InlineParsers.scala @@ -19,6 +19,8 @@ package net.liftweb.markdown * Christoph Henkelmann http://henkelmann.eu/ */ +import scala.language.postfixOps + /** * A parser for inline markdown, markdown escapes and XML escapes. * This is used by the result classes of the block parsers to handle diff --git a/core/markdown/src/main/scala/net/liftweb/markdown/LineTokenizer.scala b/core/markdown/src/main/scala/net/liftweb/markdown/LineTokenizer.scala index 9ab46d5444..488067eca8 100644 --- a/core/markdown/src/main/scala/net/liftweb/markdown/LineTokenizer.scala +++ b/core/markdown/src/main/scala/net/liftweb/markdown/LineTokenizer.scala @@ -19,8 +19,9 @@ package net.liftweb.markdown * Christoph Henkelmann http://henkelmann.eu/ */ -import scala.util.parsing.combinator.Parsers import scala.collection.mutable.{HashMap, ArrayBuffer} +import scala.language.postfixOps +import scala.util.parsing.combinator.Parsers import scala.util.parsing.input.{Position, Reader} import scala.xml @@ -245,4 +246,4 @@ class LineTokenizer() extends Parsers { case n:NoSuccess => throw new IllegalStateException("Tokenizing failed. This is a bug. Message was: " + n.msg) } -} \ No newline at end of file +} From 16c400bc91be59322c9c82ddf08eee187fc9f4b3 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Thu, 10 Apr 2014 21:16:59 -0400 Subject: [PATCH 0686/1949] Fix lift-json XML Elem constructor warnings. --- core/json/src/main/scala/net/liftweb/json/Xml.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/json/src/main/scala/net/liftweb/json/Xml.scala b/core/json/src/main/scala/net/liftweb/json/Xml.scala index 720076c855..4251ad5f4a 100644 --- a/core/json/src/main/scala/net/liftweb/json/Xml.scala +++ b/core/json/src/main/scala/net/liftweb/json/Xml.scala @@ -187,7 +187,7 @@ object Xml { } } - private[json] class XmlNode(name: String, children: Seq[Node]) extends Elem(null, name, xml.Null, TopScope, children :_*) + private[json] class XmlNode(name: String, children: Seq[Node]) extends Elem(null, name, xml.Null, TopScope, true, children :_*) - private[json] class XmlElem(name: String, value: String) extends Elem(null, name, xml.Null, TopScope, Text(value)) + private[json] class XmlElem(name: String, value: String) extends Elem(null, name, xml.Null, TopScope, true, Text(value)) } From 46884148e051217c3890d8f7bf42628f8020bc68 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Thu, 10 Apr 2014 21:17:24 -0400 Subject: [PATCH 0687/1949] Fix existential type warning in ScalaSig. We still had code in place to support 2.8 that is no longer needed, so we stop using existential types. --- .../main/scala/net/liftweb/json/ScalaSig.scala | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/core/json/src/main/scala/net/liftweb/json/ScalaSig.scala b/core/json/src/main/scala/net/liftweb/json/ScalaSig.scala index d7adc5dfe5..0208625dd9 100644 --- a/core/json/src/main/scala/net/liftweb/json/ScalaSig.scala +++ b/core/json/src/main/scala/net/liftweb/json/ScalaSig.scala @@ -77,24 +77,12 @@ private[json] object ScalaSigReader { } private def findArgTypeForField(s: MethodSymbol, typeArgIdx: Int): Class[_] = { - // FIXME can be removed when 2.8 no longer needs to be supported. - // 2.8 does not have NullaryMethodType, work around that. - /* val t = s.infoType match { case NullaryMethodType(TypeRefType(_, _, args)) => args(typeArgIdx) } - */ - def resultType = try { - s.infoType.asInstanceOf[{ def resultType: Type }].resultType - } catch { - case e: java.lang.NoSuchMethodException => s.infoType.asInstanceOf[{ def typeRef: Type }].typeRef - } - - val t = resultType match { - case TypeRefType(_, _, args) => args(typeArgIdx) - } - def findPrimitive(t: Type): Symbol = t match { + @scala.annotation.tailrec + def findPrimitive(t: Type): Symbol = t match { case TypeRefType(ThisType(_), symbol, _) => symbol case ref @ TypeRefType(_, _, _) => findPrimitive(ref) case x => Meta.fail("Unexpected type info " + x) From e98bd8248c225fc1c0afdf29e5ea52af401f027c Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Thu, 10 Apr 2014 21:17:50 -0400 Subject: [PATCH 0688/1949] Fix a postfix operator warning in lift-json. --- core/json/src/main/scala/net/liftweb/json/Meta.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/json/src/main/scala/net/liftweb/json/Meta.scala b/core/json/src/main/scala/net/liftweb/json/Meta.scala index f9ae5918a6..066d663386 100644 --- a/core/json/src/main/scala/net/liftweb/json/Meta.scala +++ b/core/json/src/main/scala/net/liftweb/json/Meta.scala @@ -229,7 +229,7 @@ private[json] object Meta { def constructorArgs(t: Type, constructor: JConstructor[_], nameReader: ParameterNameReader, context: Option[Context]): List[(String, Type)] = { def argsInfo(c: JConstructor[_], typeArgs: Map[TypeVariable[_], Type]) = { - val Name = """^((?:[^$]|[$][^0-9]+)+)([$][0-9]+)?$"""r + val Name = """^((?:[^$]|[$][^0-9]+)+)([$][0-9]+)?$""".r def clean(name: String) = name match { case Name(text, junk) => text } From 35d7fad7146a9b8921af098904e716b892671af5 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Thu, 10 Apr 2014 21:18:30 -0400 Subject: [PATCH 0689/1949] Fix inferred existential type warnings in lift-json. There were a few places where the compiler was inferring existential types when we needed to just be explicit about our wildcard types. --- core/json/src/main/scala/net/liftweb/json/Extraction.scala | 2 +- core/json/src/main/scala/net/liftweb/json/Formats.scala | 6 ++++-- core/json/src/main/scala/net/liftweb/json/Meta.scala | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/core/json/src/main/scala/net/liftweb/json/Extraction.scala b/core/json/src/main/scala/net/liftweb/json/Extraction.scala index 56ec08227f..f96e92984b 100644 --- a/core/json/src/main/scala/net/liftweb/json/Extraction.scala +++ b/core/json/src/main/scala/net/liftweb/json/Extraction.scala @@ -36,7 +36,7 @@ object Extraction { * @throws MappingException is thrown if extraction fails */ def extract[A](json: JValue)(implicit formats: Formats, mf: Manifest[A]): A = { - def allTypes(mf: Manifest[_]): List[Class[_]] = mf.erasure :: (mf.typeArguments flatMap allTypes) + def allTypes(mf: Manifest[_]): List[Class[_]] = mf.runtimeClass :: (mf.typeArguments flatMap allTypes) try { val types = allTypes(mf) diff --git a/core/json/src/main/scala/net/liftweb/json/Formats.scala b/core/json/src/main/scala/net/liftweb/json/Formats.scala index 3ab3cc101d..cbdaf7b69a 100644 --- a/core/json/src/main/scala/net/liftweb/json/Formats.scala +++ b/core/json/src/main/scala/net/liftweb/json/Formats.scala @@ -80,7 +80,7 @@ trait Formats { self: Formats => override val parameterNameReader = self.parameterNameReader override val typeHints = self.typeHints override val customSerializers = self.customSerializers - override val fieldSerializers = (mf.erasure, newSerializer) :: self.fieldSerializers + override val fieldSerializers: List[(Class[_], FieldSerializer[_])] = (mf.runtimeClass, newSerializer) :: self.fieldSerializers } private[json] def fieldSerializer(clazz: Class[_]): Option[FieldSerializer[_]] = { @@ -220,7 +220,9 @@ case class ShortTypeHints(hints: List[Class[_]]) extends TypeHints { */ case class FullTypeHints(hints: List[Class[_]]) extends TypeHints { def hintFor(clazz: Class[_]) = clazz.getName - def classFor(hint: String) = Some(Thread.currentThread.getContextClassLoader.loadClass(hint)) + def classFor(hint: String): Option[Class[_]] = { + Some(Thread.currentThread.getContextClassLoader.loadClass(hint)) + } } /** Default date format is UTC time. diff --git a/core/json/src/main/scala/net/liftweb/json/Meta.scala b/core/json/src/main/scala/net/liftweb/json/Meta.scala index 066d663386..cc8ce35a5d 100644 --- a/core/json/src/main/scala/net/liftweb/json/Meta.scala +++ b/core/json/src/main/scala/net/liftweb/json/Meta.scala @@ -93,7 +93,7 @@ private[json] object Meta { (implicit formats: Formats): Mapping = { import Reflection._ - def constructors(t: Type, visited: Set[Type], context: Option[Context]) = { + def constructors(t: Type, visited: Set[Type], context: Option[Context]): List[DeclaredConstructor] = { Reflection.constructors(t, formats.parameterNameReader, context).map { case (c, args) => DeclaredConstructor(c, args.map { case (name, t) => toArg(unmangleName(name), t, visited, Context(name, c.getDeclaringClass, args)) }) From a1c63d6ca80d0bef94943a2134e7cbfa00911626 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Thu, 10 Apr 2014 21:18:51 -0400 Subject: [PATCH 0690/1949] Fix an erasure deprecation warning in lift-json. --- core/json/src/main/scala/net/liftweb/json/Formats.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/json/src/main/scala/net/liftweb/json/Formats.scala b/core/json/src/main/scala/net/liftweb/json/Formats.scala index cbdaf7b69a..93b7a06c7d 100644 --- a/core/json/src/main/scala/net/liftweb/json/Formats.scala +++ b/core/json/src/main/scala/net/liftweb/json/Formats.scala @@ -274,7 +274,7 @@ private[json] class ThreadLocal[A](init: => A) extends java.lang.ThreadLocal[A] class CustomSerializer[A: Manifest]( ser: Formats => (PartialFunction[JValue, A], PartialFunction[Any, JValue])) extends Serializer[A] { - val Class = implicitly[Manifest[A]].erasure + val Class = implicitly[Manifest[A]].runtimeClass def deserialize(implicit format: Formats) = { case (TypeInfo(Class, _), json) => From ef9e6d1f3911d4fd7778425308b1aa67200b9148 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Thu, 10 Apr 2014 21:19:16 -0400 Subject: [PATCH 0691/1949] Add a catch-all to JsonAST's Document match. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Document isn’t a sealed abstract class, so we need a wildcard match at the end or we get a match exhaustiveness warning. --- core/json/src/main/scala/net/liftweb/json/JsonAST.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/core/json/src/main/scala/net/liftweb/json/JsonAST.scala b/core/json/src/main/scala/net/liftweb/json/JsonAST.scala index 9a8d402544..02a96e471c 100644 --- a/core/json/src/main/scala/net/liftweb/json/JsonAST.scala +++ b/core/json/src/main/scala/net/liftweb/json/JsonAST.scala @@ -614,6 +614,7 @@ trait Printer { case DocNest(_, d) :: rs => layout(d :: rs) case DocGroup(d) :: rs => layout(d :: rs) case DocNil :: rs => layout(rs) + case _ :: rs => layout(rs) } layout(List(d)) From c56d7b31683fc2c88ba9bb3664089b902bee7692 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Fri, 11 Apr 2014 09:59:21 -0400 Subject: [PATCH 0692/1949] Feature imports to appease warnings in lift-json. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit One in particular is due to a Scala compiler bug, but it’s been annotated accordingly. --- core/json/src/main/scala/net/liftweb/json/JsonAST.scala | 2 ++ core/json/src/main/scala/net/liftweb/json/Meta.scala | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/core/json/src/main/scala/net/liftweb/json/JsonAST.scala b/core/json/src/main/scala/net/liftweb/json/JsonAST.scala index 02a96e471c..d4806052f9 100644 --- a/core/json/src/main/scala/net/liftweb/json/JsonAST.scala +++ b/core/json/src/main/scala/net/liftweb/json/JsonAST.scala @@ -17,6 +17,8 @@ package net.liftweb package json +import scala.language.implicitConversions + object JsonAST { import scala.text.{Document, DocText} import scala.text.Document._ diff --git a/core/json/src/main/scala/net/liftweb/json/Meta.scala b/core/json/src/main/scala/net/liftweb/json/Meta.scala index cc8ce35a5d..1b69c3ee14 100644 --- a/core/json/src/main/scala/net/liftweb/json/Meta.scala +++ b/core/json/src/main/scala/net/liftweb/json/Meta.scala @@ -17,6 +17,13 @@ package net.liftweb package json +// FIXME Needed to due to https://issues.scala-lang.org/browse/SI-6541, +// which causes existential types to be inferred for the generated +// unapply of a case class with a wildcard parameterized type. +// Ostensibly should be fixed in 2.12, which means we're a ways away +// from being able to remove this, though. +import scala.language.existentials + import java.lang.reflect.{Constructor => JConstructor, Field, Type, ParameterizedType, GenericArrayType} import java.util.Date import java.sql.Timestamp From 26c6e59485cc8c8b5abb172461e851cb6b4cb64d Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Fri, 11 Apr 2014 10:03:10 -0400 Subject: [PATCH 0693/1949] Fix an inferred existential type in lift-json. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Compiler quirkiness… --- core/json/src/main/scala/net/liftweb/json/Formats.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/json/src/main/scala/net/liftweb/json/Formats.scala b/core/json/src/main/scala/net/liftweb/json/Formats.scala index 93b7a06c7d..9951a5e898 100644 --- a/core/json/src/main/scala/net/liftweb/json/Formats.scala +++ b/core/json/src/main/scala/net/liftweb/json/Formats.scala @@ -80,7 +80,10 @@ trait Formats { self: Formats => override val parameterNameReader = self.parameterNameReader override val typeHints = self.typeHints override val customSerializers = self.customSerializers - override val fieldSerializers: List[(Class[_], FieldSerializer[_])] = (mf.runtimeClass, newSerializer) :: self.fieldSerializers + // The type inferencer infers an existential type below if we use + // value :: list instead of list.::(value), and we get a feature + // warning. + override val fieldSerializers: List[(Class[_], FieldSerializer[_])] = self.fieldSerializers.::((mf.runtimeClass: Class[_], newSerializer)) } private[json] def fieldSerializer(clazz: Class[_]): Option[FieldSerializer[_]] = { From d93a95e8d233b03d4fa41d5d2c809cba19fb1db5 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Fri, 11 Apr 2014 10:09:09 -0400 Subject: [PATCH 0694/1949] Implicit conversion->implicit class in lift-json. --- core/json/src/main/scala/net/liftweb/json/Merge.scala | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/core/json/src/main/scala/net/liftweb/json/Merge.scala b/core/json/src/main/scala/net/liftweb/json/Merge.scala index dcbb0ee5bd..43760b9883 100644 --- a/core/json/src/main/scala/net/liftweb/json/Merge.scala +++ b/core/json/src/main/scala/net/liftweb/json/Merge.scala @@ -93,9 +93,7 @@ object Merge { } private[json] trait Mergeable extends MergeDeps { - implicit def j2m[A <: JValue](json: A): MergeSyntax[A] = new MergeSyntax(json) - - class MergeSyntax[A <: JValue](json: A) { + implicit class MergeSyntax[A <: JValue](val json: A) { /** Return merged JSON. * @see net.liftweb.json.Merge#merge */ From e8e6a45a6e3b1309ab7bda6c8d147cfbc5a5f5b7 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Fri, 11 Apr 2014 10:36:46 -0400 Subject: [PATCH 0695/1949] Change fixme to explanation in Box.asA. There was a FIXME about why existential types were being used for Box.asA, but I figured it out, so I filled in the reasoning as a comment. --- .../src/main/scala/net/liftweb/common/Box.scala | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index b0fbb3182e..2d7ad8f704 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -211,7 +211,16 @@ sealed trait BoxTrait { def isA[A, B](in: A, clz: Class[B]): Box[B] = (Box !! in).isA(clz) - // FIXME Why do we need an existential type here? + // NOTE: We use an existential type here so that you can invoke asA with + // just one type parameter. To wit, this lets you do: + // + // Box.asA[Int](myVariableWithDifferentType) + // + // If instead asA was defined as asA[T, B], you would have to do: + // + // Box.asA[DifferentType, Int](myVariableWithDifferentType) + // + // Uglier, and generally not as nice. /** * Create a Full box containing the specified value if in is of * type B; Empty otherwise. From 0c141a8b6491dd547d2dbfd9b0c4ad66699a1bed Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Fri, 11 Apr 2014 21:46:59 -0400 Subject: [PATCH 0696/1949] Version bump to 2.6-M3 for release. --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index d753e6d813..3f666d25b4 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ import Dependencies._ organization in ThisBuild := "net.liftweb" -version in ThisBuild := "2.6-SNAPSHOT" +version in ThisBuild := "2.6-M3" homepage in ThisBuild := Some(url("http://www.liftweb.net")) From 65aeefb6a80721783b7ad74788a36adcfe283ca0 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Fri, 11 Apr 2014 21:48:29 -0400 Subject: [PATCH 0697/1949] Version bump to 2.6-SNAPSHOT for forward development. --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 3f666d25b4..d753e6d813 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ import Dependencies._ organization in ThisBuild := "net.liftweb" -version in ThisBuild := "2.6-M3" +version in ThisBuild := "2.6-SNAPSHOT" homepage in ThisBuild := Some(url("http://www.liftweb.net")) From 18aa419eafc066f5a8e098917e181c7cbde3cb7f Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Fri, 11 Apr 2014 22:49:54 -0400 Subject: [PATCH 0698/1949] Fix a catchall exception handler warning in actor. --- .../src/main/scala/net/liftweb/actor/LiftActor.scala | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/core/actor/src/main/scala/net/liftweb/actor/LiftActor.scala b/core/actor/src/main/scala/net/liftweb/actor/LiftActor.scala index 753c564cf0..7a3a8644e2 100644 --- a/core/actor/src/main/scala/net/liftweb/actor/LiftActor.scala +++ b/core/actor/src/main/scala/net/liftweb/actor/LiftActor.scala @@ -320,9 +320,11 @@ trait SpecializedLiftActor[T] extends SimpleActor[T] { } } } catch { - case e => - if (eh.isDefinedAt(e)) eh(e) - throw e + case exception: Throwable => + if (eh.isDefinedAt(exception)) + eh(exception) + + throw exception } finally { if (clearProcessing) { baseMailbox.synchronized { From ab126e29fd7fcf30d7f1cda2aa8e869ba4a27dd7 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Fri, 11 Apr 2014 23:11:47 -0400 Subject: [PATCH 0699/1949] Fix an implicit conversion feature warning. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Not sure what JsonCommand is for to begin with, but that’s a separate question. --- core/util/src/main/scala/net/liftweb/util/JsonCmd.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/util/src/main/scala/net/liftweb/util/JsonCmd.scala b/core/util/src/main/scala/net/liftweb/util/JsonCmd.scala index c5f5de4d7e..5dd6e8a6e4 100644 --- a/core/util/src/main/scala/net/liftweb/util/JsonCmd.scala +++ b/core/util/src/main/scala/net/liftweb/util/JsonCmd.scala @@ -38,6 +38,8 @@ import net.liftweb.json.JsonAST._ * make some sense of it. */ object JsonCommand { + import scala.language.implicitConversions + implicit def iterableToOption[X](in: Iterable[X]): Option[X] = in.toSeq.headOption def unapply(in: JValue): Option[(String, Option[String], JValue)] = From cf1108e749dcc846a9d00b0be2dce3e9ec72a519 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 12 Apr 2014 00:15:18 -0400 Subject: [PATCH 0700/1949] Import implicitConversions as needed in util. --- core/util/src/main/scala/net/liftweb/util/AnyVar.scala | 2 ++ .../src/main/scala/net/liftweb/util/BindHelpers.scala | 1 + .../src/main/scala/net/liftweb/util/BindPlus.scala | 2 ++ .../src/main/scala/net/liftweb/util/CSSHelpers.scala | 2 ++ .../scala/net/liftweb/util/CombParserHelpers.scala | 1 + core/util/src/main/scala/net/liftweb/util/CssSel.scala | 4 ++++ .../src/main/scala/net/liftweb/util/CssSelector.scala | 2 ++ .../util/src/main/scala/net/liftweb/util/FatLazy.scala | 2 ++ .../src/main/scala/net/liftweb/util/HttpHelpers.scala | 10 +++++++--- .../main/scala/net/liftweb/util/IterableConst.scala | 2 ++ .../src/main/scala/net/liftweb/util/IterableFunc.scala | 4 +++- .../src/main/scala/net/liftweb/util/ListHelpers.scala | 2 ++ core/util/src/main/scala/net/liftweb/util/Mailer.scala | 6 +++++- core/util/src/main/scala/net/liftweb/util/Maker.scala | 5 ++++- .../scala/net/liftweb/util/MonadicConversions.scala | 1 + core/util/src/main/scala/net/liftweb/util/RE.scala | 3 +++ .../main/scala/net/liftweb/util/StringHelpers.scala | 3 +++ .../main/scala/net/liftweb/util/StringPromotable.scala | 2 ++ .../src/main/scala/net/liftweb/util/TimeHelpers.scala | 6 +++++- .../src/main/scala/net/liftweb/util/VCardParser.scala | 2 ++ .../src/main/scala/net/liftweb/util/ValueHolder.scala | 2 ++ core/util/src/main/scala/net/liftweb/util/Wiring.scala | 2 ++ 22 files changed, 59 insertions(+), 7 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/AnyVar.scala b/core/util/src/main/scala/net/liftweb/util/AnyVar.scala index 17b5053216..9ec3879a2f 100644 --- a/core/util/src/main/scala/net/liftweb/util/AnyVar.scala +++ b/core/util/src/main/scala/net/liftweb/util/AnyVar.scala @@ -17,6 +17,8 @@ package net.liftweb package util +import scala.language.implicitConversions + import Helpers._ import common._ diff --git a/core/util/src/main/scala/net/liftweb/util/BindHelpers.scala b/core/util/src/main/scala/net/liftweb/util/BindHelpers.scala index a2e4349bc0..45518df3c0 100644 --- a/core/util/src/main/scala/net/liftweb/util/BindHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/BindHelpers.scala @@ -17,6 +17,7 @@ package net.liftweb package util +import scala.language.implicitConversions import scala.xml._ import common._ diff --git a/core/util/src/main/scala/net/liftweb/util/BindPlus.scala b/core/util/src/main/scala/net/liftweb/util/BindPlus.scala index f0f59b8486..457e9d649e 100644 --- a/core/util/src/main/scala/net/liftweb/util/BindPlus.scala +++ b/core/util/src/main/scala/net/liftweb/util/BindPlus.scala @@ -17,7 +17,9 @@ package net.liftweb package util +import scala.language.implicitConversions import scala.xml.{NodeSeq, PrefixedAttribute, MetaData} + import Helpers.{bind, BindParam, FuncBindParam, TheBindParam} import common._ diff --git a/core/util/src/main/scala/net/liftweb/util/CSSHelpers.scala b/core/util/src/main/scala/net/liftweb/util/CSSHelpers.scala index ff5ba5c00a..a2cca765b7 100644 --- a/core/util/src/main/scala/net/liftweb/util/CSSHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/CSSHelpers.scala @@ -17,6 +17,8 @@ package net.liftweb package util +import scala.language.implicitConversions + import scala.util.parsing.combinator._ import common._ import java.io._ diff --git a/core/util/src/main/scala/net/liftweb/util/CombParserHelpers.scala b/core/util/src/main/scala/net/liftweb/util/CombParserHelpers.scala index 7da2d46390..70216ebf4b 100644 --- a/core/util/src/main/scala/net/liftweb/util/CombParserHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/CombParserHelpers.scala @@ -17,6 +17,7 @@ package net.liftweb package util +import scala.language.implicitConversions import scala.util.parsing.combinator.Parsers import Helpers._ diff --git a/core/util/src/main/scala/net/liftweb/util/CssSel.scala b/core/util/src/main/scala/net/liftweb/util/CssSel.scala index a13f494868..7d23d702e8 100644 --- a/core/util/src/main/scala/net/liftweb/util/CssSel.scala +++ b/core/util/src/main/scala/net/liftweb/util/CssSel.scala @@ -695,6 +695,8 @@ abstract class CssBindImpl(val stringSelector: Box[String], val css: Box[CssSele */ final class CssJBridge { + import scala.language.implicitConversions + /** * promote a String to a ToCssBindPromotor */ @@ -731,6 +733,8 @@ trait CanBind[-T] { } object CanBind { + import scala.language.higherKinds + implicit def stringTransform: CanBind[String] = new CanBind[String] { def apply(str: => String)(ns: NodeSeq): Seq[NodeSeq] = { val s = str diff --git a/core/util/src/main/scala/net/liftweb/util/CssSelector.scala b/core/util/src/main/scala/net/liftweb/util/CssSelector.scala index a7eb25974f..29a5f13940 100644 --- a/core/util/src/main/scala/net/liftweb/util/CssSelector.scala +++ b/core/util/src/main/scala/net/liftweb/util/CssSelector.scala @@ -155,6 +155,8 @@ object CssSelectorParser extends PackratParsers with ImplicitConversions { } } + import scala.language.implicitConversions + private implicit def str2chars(s: String): List[Char] = new scala.collection.immutable.WrappedString(s).toList private def fixAll(all: List[CssSelector], sn: Option[SubNode]): CssSelector = { diff --git a/core/util/src/main/scala/net/liftweb/util/FatLazy.scala b/core/util/src/main/scala/net/liftweb/util/FatLazy.scala index 97a300682c..994e617334 100644 --- a/core/util/src/main/scala/net/liftweb/util/FatLazy.scala +++ b/core/util/src/main/scala/net/liftweb/util/FatLazy.scala @@ -17,6 +17,8 @@ package net.liftweb package util +import scala.language.implicitConversions + import common._ /** diff --git a/core/util/src/main/scala/net/liftweb/util/HttpHelpers.scala b/core/util/src/main/scala/net/liftweb/util/HttpHelpers.scala index ba236e386d..90d0ee1e1f 100644 --- a/core/util/src/main/scala/net/liftweb/util/HttpHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/HttpHelpers.scala @@ -17,12 +17,16 @@ package net.liftweb package util + import java.net.{URLDecoder, URLEncoder} +import java.util.concurrent.atomic.AtomicLong + +import scala.language.implicitConversions +import scala.collection.Map import scala.collection.mutable.ListBuffer -import scala.xml._ -import scala.collection.{Map} import scala.collection.mutable.HashMap -import java.util.concurrent.atomic.AtomicLong +import scala.xml._ + import common._ object HttpHelpers extends ListHelpers with StringHelpers diff --git a/core/util/src/main/scala/net/liftweb/util/IterableConst.scala b/core/util/src/main/scala/net/liftweb/util/IterableConst.scala index 681802280c..ea2400472f 100644 --- a/core/util/src/main/scala/net/liftweb/util/IterableConst.scala +++ b/core/util/src/main/scala/net/liftweb/util/IterableConst.scala @@ -74,6 +74,8 @@ final case class SeqBindableIterableConst(it: Iterable[Bindable]) extends Iterab * e.g. Iterable[NodeSeq], Seq[String], Box[String], and Option[String] */ object IterableConst { + import scala.language.implicitConversions + /** * Converts anything that can be converted into an Iterable[NodeSeq] * into an IterableConst. This includes Seq[NodeSeq] diff --git a/core/util/src/main/scala/net/liftweb/util/IterableFunc.scala b/core/util/src/main/scala/net/liftweb/util/IterableFunc.scala index 9f5b4888aa..e885dba42e 100644 --- a/core/util/src/main/scala/net/liftweb/util/IterableFunc.scala +++ b/core/util/src/main/scala/net/liftweb/util/IterableFunc.scala @@ -1,8 +1,10 @@ package net.liftweb package util +import scala.language.implicitConversions +import scala.xml.{Text, NodeSeq} + import common._ -import xml.{Text, NodeSeq} sealed trait IterableFunc extends Function1[NodeSeq, Seq[NodeSeq]] { def apply(ns: NodeSeq): Seq[NodeSeq] diff --git a/core/util/src/main/scala/net/liftweb/util/ListHelpers.scala b/core/util/src/main/scala/net/liftweb/util/ListHelpers.scala index 80ec6b7ff6..c246d52ccf 100644 --- a/core/util/src/main/scala/net/liftweb/util/ListHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/ListHelpers.scala @@ -17,6 +17,8 @@ package net.liftweb package util +import scala.language.implicitConversions + import common._ object ListHelpers extends ListHelpers diff --git a/core/util/src/main/scala/net/liftweb/util/Mailer.scala b/core/util/src/main/scala/net/liftweb/util/Mailer.scala index 04e974e0df..e17742a199 100644 --- a/core/util/src/main/scala/net/liftweb/util/Mailer.scala +++ b/core/util/src/main/scala/net/liftweb/util/Mailer.scala @@ -21,9 +21,13 @@ import javax.mail._ import javax.mail.internet._ import javax.naming.{Context, InitialContext} import java.util.Properties + +import scala.language.implicitConversions +import scala.xml.{Text, Elem, Node, NodeSeq} + import common._ import actor._ -import xml.{Text, Elem, Node, NodeSeq} + import Mailer._ /** diff --git a/core/util/src/main/scala/net/liftweb/util/Maker.scala b/core/util/src/main/scala/net/liftweb/util/Maker.scala index 6c386c4465..891b3c3211 100644 --- a/core/util/src/main/scala/net/liftweb/util/Maker.scala +++ b/core/util/src/main/scala/net/liftweb/util/Maker.scala @@ -19,9 +19,12 @@ package util import java.util.concurrent.{ConcurrentHashMap => CHash, Callable} import java.lang.ThreadLocal + +import scala.language.implicitConversions import scala.reflect.Manifest +import scala.xml.NodeSeq + import common._ -import xml.NodeSeq /** * A trait that does basic dependency injection. diff --git a/core/util/src/main/scala/net/liftweb/util/MonadicConversions.scala b/core/util/src/main/scala/net/liftweb/util/MonadicConversions.scala index 906f67ba2f..b203412abb 100644 --- a/core/util/src/main/scala/net/liftweb/util/MonadicConversions.scala +++ b/core/util/src/main/scala/net/liftweb/util/MonadicConversions.scala @@ -21,6 +21,7 @@ package util * Holds the implicit conversions from/to MonadicCondition */ object MonadicConversions { + import scala.language.implicitConversions implicit def bool2Monadic(cond: Boolean) = cond match { case true => True diff --git a/core/util/src/main/scala/net/liftweb/util/RE.scala b/core/util/src/main/scala/net/liftweb/util/RE.scala index 7f140b42dc..c9be965959 100644 --- a/core/util/src/main/scala/net/liftweb/util/RE.scala +++ b/core/util/src/main/scala/net/liftweb/util/RE.scala @@ -18,7 +18,10 @@ package net.liftweb package util import java.util.regex.Pattern + import scala.collection.mutable.ListBuffer +import scala.language.implicitConversions + import common._ /** diff --git a/core/util/src/main/scala/net/liftweb/util/StringHelpers.scala b/core/util/src/main/scala/net/liftweb/util/StringHelpers.scala index 1b3715976a..b86721e256 100644 --- a/core/util/src/main/scala/net/liftweb/util/StringHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/StringHelpers.scala @@ -21,7 +21,10 @@ import java.security.SecureRandom import java.util.regex._ import java.lang.Character._ import java.lang.{StringBuilder => GoodSB} + +import scala.language.implicitConversions import scala.xml.NodeSeq + import common._ object StringHelpers extends StringHelpers diff --git a/core/util/src/main/scala/net/liftweb/util/StringPromotable.scala b/core/util/src/main/scala/net/liftweb/util/StringPromotable.scala index 64fa57402e..7b2b24b270 100644 --- a/core/util/src/main/scala/net/liftweb/util/StringPromotable.scala +++ b/core/util/src/main/scala/net/liftweb/util/StringPromotable.scala @@ -1,5 +1,7 @@ package net.liftweb.util +import scala.language.implicitConversions + /** * This trait marks something that can be promoted into a String. * The companion object has helpful conversions from Int, diff --git a/core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala b/core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala index 39fbfdd8ed..64e32e1875 100644 --- a/core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala @@ -19,9 +19,13 @@ package util import java.text.SimpleDateFormat import java.util.{TimeZone, Calendar, Date, Locale} -import common._ + +import scala.language.implicitConversions + import org.joda.time.{DateTime, Duration, Period, PeriodType} +import common._ + /** * The TimeHelpers object extends the TimeHelpers. It can be imported to access all of the trait functions. */ diff --git a/core/util/src/main/scala/net/liftweb/util/VCardParser.scala b/core/util/src/main/scala/net/liftweb/util/VCardParser.scala index 8b3e6123d2..b5293c5add 100644 --- a/core/util/src/main/scala/net/liftweb/util/VCardParser.scala +++ b/core/util/src/main/scala/net/liftweb/util/VCardParser.scala @@ -36,6 +36,8 @@ import scala.util.parsing.combinator._ * */ object VCardParser extends Parsers { + import scala.language.implicitConversions + type Elem = Char implicit def strToInput(in: String): Input = new scala.util.parsing.input.CharArrayReader(in.toCharArray) diff --git a/core/util/src/main/scala/net/liftweb/util/ValueHolder.scala b/core/util/src/main/scala/net/liftweb/util/ValueHolder.scala index 8e084ea442..625a7382a1 100644 --- a/core/util/src/main/scala/net/liftweb/util/ValueHolder.scala +++ b/core/util/src/main/scala/net/liftweb/util/ValueHolder.scala @@ -17,6 +17,8 @@ package net.liftweb package util +import scala.language.implicitConversions + trait ValueHolder { type ValueType diff --git a/core/util/src/main/scala/net/liftweb/util/Wiring.scala b/core/util/src/main/scala/net/liftweb/util/Wiring.scala index 4dce40a67f..c70356f80d 100644 --- a/core/util/src/main/scala/net/liftweb/util/Wiring.scala +++ b/core/util/src/main/scala/net/liftweb/util/Wiring.scala @@ -191,6 +191,8 @@ final case class DynamicCell[T](f: () => T) extends Cell[T] { * The companion object that has a helpful constructor */ object ValueCell { + import scala.language.implicitConversions + def apply[A](value: A): ValueCell[A] = new ValueCell(value) implicit def vcToT[T](in: ValueCell[T]): T = in.get From a9c16b6c4505d6da3be72d5af01dd334eb9b79e6 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 12 Apr 2014 00:16:56 -0400 Subject: [PATCH 0701/1949] Fix minimizeEmpty deprecations in util. Elem has an extra required parameter in its constructor. --- .../scala/net/liftweb/util/BindHelpers.scala | 21 ++++++++++-------- .../main/scala/net/liftweb/util/CssSel.scala | 22 +++++++++---------- .../scala/net/liftweb/util/CssSelector.scala | 6 ++--- .../scala/net/liftweb/util/HeadHelper.scala | 6 ++--- 4 files changed, 29 insertions(+), 26 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/BindHelpers.scala b/core/util/src/main/scala/net/liftweb/util/BindHelpers.scala index 45518df3c0..6b30ea98d4 100644 --- a/core/util/src/main/scala/net/liftweb/util/BindHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/BindHelpers.scala @@ -184,6 +184,7 @@ trait BindHelpers { elem.label, fix(elem.attributes), elem.scope, + elem.minimizeEmpty, elem.child :_*) } case _ => elem % new UnprefixedAttribute("class", cssClass, Null) @@ -859,7 +860,7 @@ trait BindHelpers { else Text("FIX"+"ME failed to bind <"+namespace+":"+node.label+" />") case Group(nodes) => Group(rec_xbind(nodes)) - case s: Elem => Elem(node.prefix, node.label, node.attributes, node.scope, rec_xbind(node.child): _*) + case s: Elem => Elem(node.prefix, node.label, node.attributes, node.scope, s.minimizeEmpty, rec_xbind(node.child): _*) case n => node } } @@ -976,10 +977,12 @@ trait BindHelpers { val fixedLabel = av.substring(nsColon.length) val fake = new Elem(namespace, fixedLabel, fixedAttrs, - e.scope, new Elem(e.namespace, + e.scope, e.minimizeEmpty, + new Elem(e.namespace, e.label, fixedAttrs, e.scope, + e.minimizeEmpty, e.child :_*)) BindHelpers._currentNode.doWith(fake) { @@ -1014,7 +1017,7 @@ trait BindHelpers { } } case Group(nodes) => Group(in_bind(nodes)) - case s: Elem => Elem(s.prefix, s.label, attrBind(s.attributes), if (preserveScope) s.scope else TopScope, + case s: Elem => Elem(s.prefix, s.label, attrBind(s.attributes), if (preserveScope) s.scope else TopScope, s.minimizeEmpty, in_bind(s.child): _*) case n => n } @@ -1118,7 +1121,7 @@ trait BindHelpers { } } case Group(nodes) => Group(bind(vals, nodes)) - case s: Elem => Elem(node.prefix, node.label, node.attributes, node.scope, bind(vals, node.child, false, unusedBindings): _*) + case s: Elem => Elem(node.prefix, node.label, node.attributes, node.scope, true, bind(vals, node.child, s.minimizeEmpty, unusedBindings): _*) case n => node } } @@ -1167,7 +1170,7 @@ trait BindHelpers { case _ => None }.getOrElse(processBind(v.asInstanceOf[Elem].child, atWhat)) - case e: Elem => {Elem(e.prefix, e.label, e.attributes, e.scope, processBind(e.child, atWhat): _*)} + case e: Elem => {Elem(e.prefix, e.label, e.attributes, e.scope, e.minimizeEmpty, processBind(e.child, atWhat): _*)} case _ => {v} } @@ -1351,7 +1354,7 @@ trait BindHelpers { in.label, in.attributes.filter { case up: UnprefixedAttribute => up.key != "id" case _ => true - }, in.scope, in.child :_*) + }, in.scope, in.minimizeEmpty, in.child :_*) } else { ids += id.text in @@ -1389,12 +1392,12 @@ trait BindHelpers { in.label, in.attributes.filter { case up: UnprefixedAttribute => up.key != "id" case _ => true - }, in.scope, in.child.map(ensure) :_*) + }, in.scope, in.minimizeEmpty, in.child.map(ensure) :_*) } else { ids += id.text new Elem(in.prefix, in.label, in.attributes, - in.scope, in.child.map(ensure) :_*) + in.scope, in.minimizeEmpty, in.child.map(ensure) :_*) } } @@ -1402,7 +1405,7 @@ trait BindHelpers { case _ => new Elem(in.prefix, in.label, in.attributes, - in.scope, in.child.map(ensure) :_*) + in.scope, in.minimizeEmpty, in.child.map(ensure) :_*) } case x => x diff --git a/core/util/src/main/scala/net/liftweb/util/CssSel.scala b/core/util/src/main/scala/net/liftweb/util/CssSel.scala index 7d23d702e8..63626eaf3b 100644 --- a/core/util/src/main/scala/net/liftweb/util/CssSel.scala +++ b/core/util/src/main/scala/net/liftweb/util/CssSel.scala @@ -243,7 +243,7 @@ private class SelectorMap(binds: List[CssBind]) extends Function1[NodeSeq, NodeS new Elem(elem.prefix, elem.label, newAttr, - elem.scope, elem.child: _*) + elem.scope, elem.minimizeEmpty, elem.child: _*) } } @@ -279,7 +279,7 @@ private class SelectorMap(binds: List[CssBind]) extends Function1[NodeSeq, NodeS new Elem(elem.prefix, elem.label, newAttr, - elem.scope, elem.child: _*) + elem.scope, elem.minimizeEmpty, elem.child: _*) } } @@ -374,13 +374,13 @@ private class SelectorMap(binds: List[CssBind]) extends Function1[NodeSeq, NodeS case Full(todo: WithKids) => { val calced = bind.calculate(realE.child) calced.length match { - case 0 => new Elem(realE.prefix, realE.label, realE.attributes, realE.scope) + case 0 => new Elem(realE.prefix, realE.label, realE.attributes, realE.scope, realE.minimizeEmpty) case 1 => new Elem(realE.prefix, realE.label, - realE.attributes, realE.scope, + realE.attributes, realE.scope, realE.minimizeEmpty, todo.transform(realE.child, calced.head): _*) case _ if id.isEmpty => calced.map(kids => new Elem(realE.prefix, realE.label, - realE.attributes, realE.scope, + realE.attributes, realE.scope, realE.minimizeEmpty, todo.transform(realE.child, kids): _*)) case _ => { @@ -388,11 +388,11 @@ private class SelectorMap(binds: List[CssBind]) extends Function1[NodeSeq, NodeS calced.toList.zipWithIndex.map { case (kids, 0) => new Elem(realE.prefix, realE.label, - realE.attributes, realE.scope, + realE.attributes, realE.scope, realE.minimizeEmpty, todo.transform(realE.child, kids): _*) case (kids, _) => new Elem(realE.prefix, realE.label, - noId, realE.scope, + noId, realE.scope, realE.minimizeEmpty, todo.transform(realE.child, kids): _*) } } @@ -409,7 +409,7 @@ private class SelectorMap(binds: List[CssBind]) extends Function1[NodeSeq, NodeS case Group(g) => g case e: Elem => new Elem(e.prefix, e.label, mergeAll(e.attributes, false, x == Full(DontMergeAttributes)), - e.scope, e.child: _*) + e.scope, e.minimizeEmpty, e.child: _*) case x => x } } @@ -431,7 +431,7 @@ private class SelectorMap(binds: List[CssBind]) extends Function1[NodeSeq, NodeS id => ids.contains(id) } getOrElse (false) val newIds = targetId filter (_ => keepId) map (i => ids - i) getOrElse (ids) - val newElem = new Elem(e.prefix, e.label, mergeAll(e.attributes, !keepId, x == Full(DontMergeAttributes)), e.scope, e.child: _*) + val newElem = new Elem(e.prefix, e.label, mergeAll(e.attributes, !keepId, x == Full(DontMergeAttributes)), e.scope, e.minimizeEmpty, e.child: _*) (newIds, newElem :: result) } case x => (ids, x :: result) @@ -570,7 +570,7 @@ private class SelectorMap(binds: List[CssBind]) extends Function1[NodeSeq, NodeS } else { lb.toList.filterNot(_.selectThis_?) match { case Nil => new Elem(e.prefix, e.label, - e.attributes, e.scope, run(e.child, onlySel, depth + 1): _*) + e.attributes, e.scope, e.minimizeEmpty, run(e.child, onlySel, depth + 1): _*) case csb => // do attributes first, then the body csb.partition(_.attrSel_?) match { @@ -578,7 +578,7 @@ private class SelectorMap(binds: List[CssBind]) extends Function1[NodeSeq, NodeS case (attrs, Nil) => { val elem = slurp.applyAttributeRules(attrs, e) new Elem(elem.prefix, elem.label, - elem.attributes, elem.scope, run(elem.child, onlySel, depth + 1): _*) + elem.attributes, elem.scope, e.minimizeEmpty, run(elem.child, onlySel, depth + 1): _*) } case (attrs, rules) => { diff --git a/core/util/src/main/scala/net/liftweb/util/CssSelector.scala b/core/util/src/main/scala/net/liftweb/util/CssSelector.scala index 29a5f13940..a74eef9c88 100644 --- a/core/util/src/main/scala/net/liftweb/util/CssSelector.scala +++ b/core/util/src/main/scala/net/liftweb/util/CssSelector.scala @@ -17,9 +17,9 @@ package net.liftweb package util - import scala.util.parsing.combinator.{PackratParsers, Parsers, ImplicitConversions} -import xml.{Elem, NodeSeq} +import scala.xml.{Elem, NodeSeq} + import net.liftweb.common._ sealed trait CssSelector { @@ -92,7 +92,7 @@ final case class SurroundKids() extends SubNode with WithKids { changed = true new Elem(e.prefix, e.label, e.attributes, - e.scope, e.child ++ original :_*) + e.scope, e.minimizeEmpty, e.child ++ original :_*) case x => x } diff --git a/core/util/src/main/scala/net/liftweb/util/HeadHelper.scala b/core/util/src/main/scala/net/liftweb/util/HeadHelper.scala index 3249552859..7eff8ab401 100644 --- a/core/util/src/main/scala/net/liftweb/util/HeadHelper.scala +++ b/core/util/src/main/scala/net/liftweb/util/HeadHelper.scala @@ -85,16 +85,16 @@ object HeadHelper { } else { def xform(in: NodeSeq, inBody: Boolean): NodeSeq = in flatMap { case e: Elem if !inBody && e.label == "body" => - Elem(e.prefix, e.label, e.attributes, e.scope, xform(e.child, true) :_*) + Elem(e.prefix, e.label, e.attributes, e.scope, e.minimizeEmpty, xform(e.child, true) :_*) case e: Elem if inBody && e.label == "head" => NodeSeq.Empty case e: Elem if e.label == "head" => Elem(e.prefix, e.label, e.attributes, - e.scope, removeHtmlDuplicates(e.child ++ headInBody) :_*) + e.scope, e.minimizeEmpty, removeHtmlDuplicates(e.child ++ headInBody) :_*) case e: Elem => - Elem(e.prefix, e.label, e.attributes, e.scope, xform(e.child, inBody) :_*) + Elem(e.prefix, e.label, e.attributes, e.scope, e.minimizeEmpty, xform(e.child, inBody) :_*) case g: Group => xform(g.child, inBody) From 351a28d7d7e17b47cff5b53d706bad327e150077 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 12 Apr 2014 00:17:34 -0400 Subject: [PATCH 0702/1949] Import postfixOps as needed in lift-util. --- core/util/src/main/scala/net/liftweb/util/CSSHelpers.scala | 1 + .../src/main/scala/net/liftweb/util/SoftReferenceCache.scala | 5 +++-- core/util/src/main/scala/net/liftweb/util/VCardParser.scala | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/CSSHelpers.scala b/core/util/src/main/scala/net/liftweb/util/CSSHelpers.scala index a2cca765b7..05c7b606c5 100644 --- a/core/util/src/main/scala/net/liftweb/util/CSSHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/CSSHelpers.scala @@ -17,6 +17,7 @@ package net.liftweb package util +import scala.language.postfixOps import scala.language.implicitConversions import scala.util.parsing.combinator._ diff --git a/core/util/src/main/scala/net/liftweb/util/SoftReferenceCache.scala b/core/util/src/main/scala/net/liftweb/util/SoftReferenceCache.scala index ccba530f72..4db2159575 100644 --- a/core/util/src/main/scala/net/liftweb/util/SoftReferenceCache.scala +++ b/core/util/src/main/scala/net/liftweb/util/SoftReferenceCache.scala @@ -104,11 +104,12 @@ class SoftReferenceCache[K, V](cacheSize: Int) { val writeLock = rwl.writeLock private def lock[T](l: Lock)(block: => T): T = { - l lock; + l.lock + try { block } finally { - l unlock + l.unlock } } diff --git a/core/util/src/main/scala/net/liftweb/util/VCardParser.scala b/core/util/src/main/scala/net/liftweb/util/VCardParser.scala index b5293c5add..4acc97f3e7 100644 --- a/core/util/src/main/scala/net/liftweb/util/VCardParser.scala +++ b/core/util/src/main/scala/net/liftweb/util/VCardParser.scala @@ -18,6 +18,7 @@ package net.liftweb package util import scala.collection.mutable._ +import scala.language.postfixOps import scala.util.parsing.combinator._ /** From 5b057f553efaa0ec85202b6a1f8ee9d779a17757 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 12 Apr 2014 00:18:16 -0400 Subject: [PATCH 0703/1949] Implicit conversions->implicit classes in util. --- .../net/liftweb/util/BasicTypesHelpers.scala | 71 +++++++------------ 1 file changed, 24 insertions(+), 47 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/BasicTypesHelpers.scala b/core/util/src/main/scala/net/liftweb/util/BasicTypesHelpers.scala index f775740c96..668b6214c1 100644 --- a/core/util/src/main/scala/net/liftweb/util/BasicTypesHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/BasicTypesHelpers.scala @@ -49,19 +49,11 @@ object BasicTypesHelpers extends BasicTypesHelpers with StringHelpers with Contr * This trait adds functionality to Scala standard types */ trait BasicTypesHelpers { self: StringHelpers with ControlHelpers => - - /** - * Allows an implicit transform from a Boolean to a Boolean2, allowing expressions such as: - * (1 == 2) ? "a" | "b" (This expression will return "b") - * @param b the predicate to be tested by the ternary operator. - */ - implicit def boolean2(b: => Boolean) = new Boolean2(b) - /** * This decorator class adds a ternary operator to a Boolean value * @param b the predicate to be tested by the ternary operator. */ - class Boolean2(b: => Boolean) { + implicit class Boolean2(b: => Boolean) { /** * Ternary operator. * @return a BooleanSome containing the specified value @@ -177,18 +169,33 @@ trait BasicTypesHelpers { self: StringHelpers with ControlHelpers => } /** - * Implicit transformation from a Boolean expression to an OptionalCons object so - * that an element can be added to a list if the expression is true + * Optional cons that implements the expression: expr ?> value ::: List + * @param expr the predicate to evaluate */ - implicit def toOptiCons(expr: => Boolean): OptionalCons = new OptionalCons(expr) + final implicit class OptionalCons(expr: => Boolean) { + /** + * Return the specified value in a single-element list if the predicate + * evaluates to true. + */ + def ?>[T](f: => T): List[T] = if (expr) List(f) else Nil + } /** - * promote a partial function such that we can invoke the guard method - * to wrap the guarded partial function with a guard + * A helper class that facilitates wrapping of one PartialFunction + * around another */ - implicit def pfToGuardable[A](in: PartialFunction[A, _]): - PartialFunctionWrapper[A] = - new PartialFunctionWrapper[A](in) + final implicit class PartialFunctionWrapper[A](around: PartialFunction[A, _]) { + /** + * Allows you to put a guard around a partial function + * such that the around's isDefinedAt method must return true + * before the other's isDefinedAt method is tested + */ + def guard[B](other: PartialFunction[A, B]): PartialFunction[A,B] = + new PartialFunction[A, B] { + def isDefinedAt(a: A) = around.isDefinedAt(a) && other.isDefinedAt(a) + def apply(a: A): B = other.apply(a) + } + } /** * Convert any object to an "equivalent" Boolean depending on its value @@ -363,33 +370,3 @@ object AsLong { } } -/** - * Optional cons that implements the expression: expr ?> value ::: List - * @param expr the predicate to evaluate - */ -final class OptionalCons(expr: => Boolean) { - /** - * Return the specified value in a single-element list if the predicate - * evaluates to true. - */ - def ?>[T](f: => T): List[T] = if (expr) List(f) else Nil -} - -/** - * The helper class that facilitates wrapping of one PartialFunction - * around another - */ -final class PartialFunctionWrapper[A](around: PartialFunction[A, _]) { - /** - * Allows you to put a guard around a partial function - * such that the around's isDefinedMethod must return true - * before the other's isDefinedAt method is tested - */ - def guard[B](other: PartialFunction[A, B]): PartialFunction[A,B] = - new PartialFunction[A, B] { - def isDefinedAt(a: A) = around.isDefinedAt(a) && other.isDefinedAt(a) - def apply(a: A): B = other.apply(a) - } - -} - From 6c06dc1bbc0fe9dfefead3b1620fc1c623ba2896 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 12 Apr 2014 00:18:56 -0400 Subject: [PATCH 0704/1949] Drop some deprecated implicits to nuke warnings in util. --- .../scala/net/liftweb/util/BindHelpers.scala | 35 ------------------- 1 file changed, 35 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/BindHelpers.scala b/core/util/src/main/scala/net/liftweb/util/BindHelpers.scala index 6b30ea98d4..d8f1fb428b 100644 --- a/core/util/src/main/scala/net/liftweb/util/BindHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/BindHelpers.scala @@ -723,20 +723,6 @@ trait BindHelpers { (("#"+id) #> replacement).apply(in) } - /** - * transforms a Box into a Text node - */ - @deprecated("use -> instead", "2.4") - object BindParamAssoc { - implicit def canStrBoxNodeSeq(in: Box[Any]): Box[NodeSeq] = in.map(_ match { - case null => Text("null") - case v => v.toString match { - case null => NodeSeq.Empty - case str => Text(str) - } - }) - } - /** * takes a NodeSeq and applies all the attributes to all the Elems at the top level of the * NodeSeq. The id attribute is applied to the first-found Elem only @@ -801,7 +787,6 @@ trait BindHelpers { implicit def strToSuperArrowAssoc(in: String): SuperArrowAssoc = new SuperArrowAssoc(in) - /** * This class creates a BindParam from an input value * @@ -823,26 +808,6 @@ trait BindHelpers { def -->(value: Box[NodeSeq]): BindParam = TheBindParam(name, value.openOr(Text("Empty"))) } - /** - * transforms a String to a BindParamAssoc object which can be associated to a BindParam object - * using the --> operator.

    - * Usage: "David" --> "name" - * - * @deprecated use -> instead - */ - @deprecated("use -> instead", "2.4") - implicit def strToBPAssoc(in: String): BindParamAssoc = new BindParamAssoc(in) - - /** - * transforms a Symbol to a SuperArrowAssoc object which can be associated to a BindParam object - * using the -> operator.

    - * Usage: 'David -> "name" - * - * @deprecated use -> instead - */ - @deprecated("use -> instead", "2.4") - implicit def symToSAAssoc(in: Symbol): SuperArrowAssoc = new SuperArrowAssoc(in.name) - /** * Experimental extension to bind which passes in an additional "parameter" from the XHTML to the transform * function, which can be used to format the returned NodeSeq. From b67e05c2bcbd23d600ca8b981f3d589b09313ecc Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 12 Apr 2014 00:19:18 -0400 Subject: [PATCH 0705/1949] Import higherKinds as needed in util. --- core/util/src/main/scala/net/liftweb/util/BindHelpers.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/util/src/main/scala/net/liftweb/util/BindHelpers.scala b/core/util/src/main/scala/net/liftweb/util/BindHelpers.scala index d8f1fb428b..ad8f9fba79 100644 --- a/core/util/src/main/scala/net/liftweb/util/BindHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/BindHelpers.scala @@ -17,8 +17,10 @@ package net.liftweb package util +import scala.language.higherKinds import scala.language.implicitConversions import scala.xml._ + import common._ From 5fa32bad33836f5b13797a15785a0b6899e8faf2 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 12 Apr 2014 00:19:29 -0400 Subject: [PATCH 0706/1949] Import reflectiveCalls as needed in util. --- .../src/main/scala/net/liftweb/util/EnumWithDescription.scala | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/util/src/main/scala/net/liftweb/util/EnumWithDescription.scala b/core/util/src/main/scala/net/liftweb/util/EnumWithDescription.scala index 3bf761531a..6cbe3564fe 100644 --- a/core/util/src/main/scala/net/liftweb/util/EnumWithDescription.scala +++ b/core/util/src/main/scala/net/liftweb/util/EnumWithDescription.scala @@ -17,6 +17,8 @@ package net.liftweb package util + + /* A wrapper arround a Scala Enumeration Value that has a name, description for each object */ @@ -26,6 +28,8 @@ trait ValueWithDescription { } abstract class EnumWithDescription { + import scala.language.reflectiveCalls + type Value = enum.Value with ValueWithDescription private var _values: List[Value] = Nil From 3ea2566238e68dd3d5d8c96994cb98f8873e8c5a Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 12 Apr 2014 00:19:46 -0400 Subject: [PATCH 0707/1949] Fix catch-all exception warnings in util. --- core/util/src/main/scala/net/liftweb/util/ControlHelpers.scala | 2 +- core/util/src/main/scala/net/liftweb/util/IoHelpers.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/ControlHelpers.scala b/core/util/src/main/scala/net/liftweb/util/ControlHelpers.scala index 7408f91b74..a3eaa758ea 100644 --- a/core/util/src/main/scala/net/liftweb/util/ControlHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/ControlHelpers.scala @@ -69,7 +69,7 @@ trait ControlHelpers extends ClassHelpers { Full(f) } catch { case t if handler.isDefinedAt(t) => Full(handler(t)) - case e => Failure(e.getMessage, Full(e), Empty) + case e: Throwable => Failure(e.getMessage, Full(e), Empty) } } diff --git a/core/util/src/main/scala/net/liftweb/util/IoHelpers.scala b/core/util/src/main/scala/net/liftweb/util/IoHelpers.scala index bda7bc4f0f..aa104aacaa 100644 --- a/core/util/src/main/scala/net/liftweb/util/IoHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/IoHelpers.scala @@ -58,7 +58,7 @@ trait IoHelpers { if (res == 0) Full(stdOut) else Failure(stdErr, Empty, Empty) } catch { - case e => Failure(e.getMessage, Full(e), Empty) + case e: Throwable => Failure(e.getMessage, Full(e), Empty) } } From 560424dfbf03c049da77f305d5df4f9bd76df73f Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 12 Apr 2014 00:20:08 -0400 Subject: [PATCH 0708/1949] Fix reference to Manifest.erasure in util. --- core/util/src/main/scala/net/liftweb/util/ClassHelpers.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/util/src/main/scala/net/liftweb/util/ClassHelpers.scala b/core/util/src/main/scala/net/liftweb/util/ClassHelpers.scala index 538d9d333c..12bb1e0a8d 100644 --- a/core/util/src/main/scala/net/liftweb/util/ClassHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/ClassHelpers.scala @@ -71,7 +71,7 @@ trait ClassHelpers { self: ControlHelpers => * @return a Box, either containing the found class or an Empty can. */ def findType[C <: AnyRef](name: String, where: List[String], modifiers: List[String => String])(implicit m: Manifest[C]): Box[Class[C]] = - findClass(name, where, modifiers, m.erasure.asInstanceOf[Class[C]]) + findClass(name, where, modifiers, m.runtimeClass.asInstanceOf[Class[C]]) /** * General method to in find a class according to its name, a list of possible packages and a From 089bd66a657444a2b3c07c50eafff038ec15f94a Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 12 Apr 2014 00:20:30 -0400 Subject: [PATCH 0709/1949] Fix inferred existential type in util. We are now explicit about the wildcard type. --- core/util/src/main/scala/net/liftweb/util/ClassHelpers.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/util/src/main/scala/net/liftweb/util/ClassHelpers.scala b/core/util/src/main/scala/net/liftweb/util/ClassHelpers.scala index 12bb1e0a8d..a7b17dc6bc 100644 --- a/core/util/src/main/scala/net/liftweb/util/ClassHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/ClassHelpers.scala @@ -378,7 +378,7 @@ trait ClassHelpers { self: ControlHelpers => var c: Class[_] = in ret += c while (c.getSuperclass != null) { - val sc = c.getSuperclass + val sc: Class[_] = c.getSuperclass ret += sc c = sc } From 0bce74a0b12aee579885545efbb677d713934f95 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 12 Apr 2014 00:20:42 -0400 Subject: [PATCH 0710/1949] Fix val in for comprehension in util. --- core/util/src/main/scala/net/liftweb/util/ClassHelpers.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/ClassHelpers.scala b/core/util/src/main/scala/net/liftweb/util/ClassHelpers.scala index a7b17dc6bc..44da808a03 100644 --- a/core/util/src/main/scala/net/liftweb/util/ClassHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/ClassHelpers.scala @@ -53,8 +53,8 @@ trait ClassHelpers { self: ControlHelpers => (for ( place <- where.view; mod <- modifiers.view; - val fullName = place + "." + mod(name); - val ignore = List(classOf[ClassNotFoundException], classOf[ClassCastException], classOf[NoClassDefFoundError]); + fullName = place + "." + mod(name); + ignore = List(classOf[ClassNotFoundException], classOf[ClassCastException], classOf[NoClassDefFoundError]); klass <- tryo(ignore)(Class.forName(fullName).asSubclass(targetType).asInstanceOf[Class[C]]) ) yield klass).headOption From babbc114c47cfc333340743102003216f53ccfaa Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 12 Apr 2014 16:05:36 -0400 Subject: [PATCH 0711/1949] Fix pure expression warning in CssSel. --- .../main/scala/net/liftweb/util/CssSel.scala | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/CssSel.scala b/core/util/src/main/scala/net/liftweb/util/CssSel.scala index 63626eaf3b..11da50ce9b 100644 --- a/core/util/src/main/scala/net/liftweb/util/CssSel.scala +++ b/core/util/src/main/scala/net/liftweb/util/CssSel.scala @@ -900,15 +900,16 @@ final case class ToCssBindPromoter(stringSelector: Box[String], css: Box[CssSele */ def #>[T](it: => T)(implicit computer: CanBind[T]): CssSel = { css match { - case Full(EnclosedSelector(a, b)) => null - (ToCssBindPromoter(stringSelector, Full(a))).#>(nsFunc(ns => { - ToCssBindPromoter(stringSelector, Full(b)).#>(it)(computer)(ns) - })) // (CanBind.nodeSeqFuncTransform) - case _ => - new CssBindImpl(stringSelector, css) { - def calculate(in: NodeSeq): Seq[NodeSeq] = computer(it)(in) - } - } + case Full(EnclosedSelector(a, b)) => + ToCssBindPromoter(stringSelector, Full(a)) #> nsFunc({ ns => + ToCssBindPromoter(stringSelector, Full(b)).#>(it)(computer)(ns) + }) // (CanBind.nodeSeqFuncTransform) + + case _ => + new CssBindImpl(stringSelector, css) { + def calculate(in: NodeSeq): Seq[NodeSeq] = computer(it)(in) + } + } } /** From e699276311f5752f508d992c291dd1acdc3b92be Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 12 Apr 2014 16:26:02 -0400 Subject: [PATCH 0712/1949] Build the 2.10 edition of Lift 2.x using Scala 2.10.4 --- build.sbt | 2 +- project/Dependencies.scala | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.sbt b/build.sbt index d753e6d813..8de2acf83e 100644 --- a/build.sbt +++ b/build.sbt @@ -12,7 +12,7 @@ startYear in ThisBuild := Some(2006) organizationName in ThisBuild := "WorldWide Conferencing, LLC" -crossScalaVersions in ThisBuild := Seq("2.10.0", "2.9.2", "2.9.1-1", "2.9.1") +crossScalaVersions in ThisBuild := Seq("2.10.4", "2.9.2", "2.9.1-1", "2.9.1") libraryDependencies in ThisBuild <++= scalaVersion {sv => Seq(specs2(sv), scalacheck) } diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 7f33cb69e8..717080be35 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -24,8 +24,8 @@ object Dependencies { type ModuleMap = String => ModuleID lazy val CVMapping2911 = crossMapped("2.9.1-1" -> "2.9.1") - lazy val CVMapping29 = crossMapped("2.10.0" -> "2.10", "2.9.1-1" -> "2.9.2", "2.9.1" -> "2.9.2") - lazy val CVMappingAll = crossMapped("2.10.0" -> "2.10", "2.9.2" -> "2.9.1", "2.9.1-1" -> "2.9.1") + lazy val CVMapping29 = crossMapped("2.10.4" -> "2.10", "2.9.1-1" -> "2.9.2", "2.9.1" -> "2.9.2") + lazy val CVMappingAll = crossMapped("2.10.4" -> "2.10", "2.9.2" -> "2.9.1", "2.9.1-1" -> "2.9.1") lazy val slf4jVersion = "1.7.2" From bf64e814e422c9f0de8fbdf4e91045fbd5ef8bbe Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 12 Apr 2014 17:26:18 -0400 Subject: [PATCH 0713/1949] Bump us to sbt 0.13.1. --- build.sbt | 2 ++ project/build.properties | 2 +- project/plugins.sbt | 4 ++-- project/project/Plugin.scala | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/build.sbt b/build.sbt index d753e6d813..8ab0ea528d 100644 --- a/build.sbt +++ b/build.sbt @@ -12,6 +12,8 @@ startYear in ThisBuild := Some(2006) organizationName in ThisBuild := "WorldWide Conferencing, LLC" +scalaVersion in ThisBuild := "2.10.0" + crossScalaVersions in ThisBuild := Seq("2.10.0", "2.9.2", "2.9.1-1", "2.9.1") libraryDependencies in ThisBuild <++= scalaVersion {sv => Seq(specs2(sv), scalacheck) } diff --git a/project/build.properties b/project/build.properties index a2a2e1da53..0458ca613b 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1,2 +1,2 @@ # Deprecate using build.properties, use -Dsbt.version=... in launcher arg instead -sbt.version=0.12.4 +sbt.version=0.13.1 diff --git a/project/plugins.sbt b/project/plugins.sbt index 91927a54fb..7820b60ad5 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,7 +1,7 @@ DefaultOptions.addPluginResolvers -addSbtPlugin("in.drajit.sbt" % "sbt-yui-compressor" % "0.2.1") +addSbtPlugin("in.drajit.sbt" % "sbt-yui-compressor" % "0.2.2-SNAPSHOT") resolvers += "sbt-idea-repo" at "http://mpeltonen.github.com/maven/" -addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.1.0") \ No newline at end of file +addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.6.0") diff --git a/project/project/Plugin.scala b/project/project/Plugin.scala index f5b6dc0fd2..b8fe0d9835 100644 --- a/project/project/Plugin.scala +++ b/project/project/Plugin.scala @@ -2,5 +2,5 @@ import sbt._ object PluginDef extends Build { lazy val root = Project("plugins", file(".")) dependsOn(buildPlugin) - lazy val buildPlugin = uri("git://github.com/lift/sbt-lift-build.git#c78f617f62") + lazy val buildPlugin = uri("git://github.com/lift/sbt-lift-build.git#724fb133beac77bbd06d3fb8ea086a1c88ee2a7d") } From 289107f679fa7ff5ab98713c3cca170d008639fa Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 12 Apr 2014 17:26:42 -0400 Subject: [PATCH 0714/1949] Liftsh is now smart enough to download the sbt launcher for you. --- liftsh | 13 ++++++++++++- liftsh.cmd | 7 ++++++- project/sbt-launch-0.12.1.jar | Bin 1103618 -> 0 bytes 3 files changed, 18 insertions(+), 2 deletions(-) delete mode 100644 project/sbt-launch-0.12.1.jar diff --git a/liftsh b/liftsh index 51782b9017..0f2e539129 100755 --- a/liftsh +++ b/liftsh @@ -1,5 +1,16 @@ #!/bin/sh +# Make sure to change the name of the launcher jar and the source when bumping sbt version +# so that the existence test below fails and we download the new jar. +SBT_LAUNCHER_PATH="project/sbt-launch-0.13.1.jar" +SBT_LAUNCHER_SOURCE="http://repo.typesafe.com/typesafe/ivy-releases/org.scala-sbt/sbt-launch/0.13.1/sbt-launch.jar" + +# Download the sbt launcher on-the-fly if it's not already in the repository. +if test ! -f $SBT_LAUNCHER_PATH; then + echo "Downloading sbt launcher..." + curl -o ${SBT_LAUNCHER_PATH} ${SBT_LAUNCHER_SOURCE} +fi + # Load custom liftsh config if test -f ~/.liftsh.config; then . ~/.liftsh.config @@ -17,4 +28,4 @@ DEFAULT_OPTS="" cd `dirname $0` # Call with INTERNAL_OPTS followed by LIFTSH_OPTS (or DEFAULT_OPTS). java always takes the last option when duplicate. -exec java ${INTERNAL_OPTS} ${LIFTSH_OPTS:-${DEFAULT_OPTS}} -jar project/sbt-launch-0.12.1.jar "$@" +exec java ${INTERNAL_OPTS} ${LIFTSH_OPTS:-${DEFAULT_OPTS}} -jar ${SBT_LAUNCHER_PATH} "$@" diff --git a/liftsh.cmd b/liftsh.cmd index f80a55296f..12e0eeeb12 100644 --- a/liftsh.cmd +++ b/liftsh.cmd @@ -1,5 +1,10 @@ @echo off +set SBT_LAUNCHER_PATH="project\sbt-launch-0.13.1.jar" +set SBT_LAUNCHER_SOURCE="http://repo.typesafe.com/typesafe/ivy-releases/org.scala-sbt/sbt-launch/0.13.1/sbt-launch.jar" + +if not exist %SBT_LAUNCHER_PATH% powershell -Command "(New-Object Net.WebClient).DownloadFile('%SBT_LAUNCHER_SOURCE%', '%SBT_LAUNCHER_PATH%')" + @REM Internal options, always specified set INTERNAL_OPTS=-Dfile.encoding=UTF-8 -Xmx768m -noverify -XX:ReservedCodeCacheSize=256m -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC -XX:MaxPermSize=512m @@ -17,4 +22,4 @@ if "%LIFTSH_OPTS%"=="" ( ) @REM Call with INTERNAL_OPTS followed by LIFTSH_OPTS (or DEFAULT_OPTS). java always takes the last option when duplicate. -java %INTERNAL_OPTS% %LIFTSH_OPTS% -jar "%~dp0\project\sbt-launch-0.12.1.jar" %* +java %INTERNAL_OPTS% %LIFTSH_OPTS% -jar "%~dp0\%SBT_LAUNCHER_PATH%" %* diff --git a/project/sbt-launch-0.12.1.jar b/project/sbt-launch-0.12.1.jar deleted file mode 100644 index 06ad8d880592ad776fdfc38c573359261314e6c3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1103618 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

    + } def createButton = S.?("Create") @@ -532,18 +518,17 @@ trait Crudify { * The core template for viewing. Does not include any * page wrapping. */ - def _viewTemplate = - - - - - - + def _viewTemplate = { +
    +
    + + + - -
    -
    - + +
    + } + def showAllMenuName = S.?("List", displayName) /** @@ -559,35 +544,38 @@ trait Crudify { * The core template for showing record. Does not include any * page wrapping */ - def _showAllTemplate = - - - - - - - - - - - - + def _showAllTemplate = { +
    +
       
    + - - - - + + + + + - - - - - - - - -
    {S.?("View")}{S.?("Edit")}{S.?("Delete")}   
    {previousWord}{nextWord}
    -
    + + + + + + + {S ? "View"} + {S ? "Edit"} + {S ? "Delete"} + + + + + + {previousWord} + {nextWord} + + + +
    + } def nextWord = S.?("Next") def previousWord = S.?("Previous") @@ -646,75 +634,77 @@ trait Crudify { /** * Override this method to customize how header items are treated */ - protected def doCrudAllHeaderItems(in: NodeSeq): NodeSeq = - fieldsForList.flatMap(f => - bind("crud", in, "name" -> f.displayHtml)) + protected def doCrudAllHeaderItems: (NodeSeq)=>NodeSeq = { + "^ *" #> fieldsForList.map(_.displayHtml) + } /** * Override this method to customize how a crudAll line is generated */ - protected def doCrudAllRowItem(c: TheCrudType)(in: NodeSeq): NodeSeq = - for { - pointer <- fieldsForList - field <- computeFieldFromPointer(c, pointer).toList - node <- bind("crud", in, "value" -> field.asHtml) - } yield node + protected def doCrudAllRowItem(c: TheCrudType): (NodeSeq)=>NodeSeq = { + "^" #> { + for { + pointer <- fieldsForList + field <- computeFieldFromPointer(c, pointer).toList + } yield { + ".value *" #> field.asHtml + } + } + } /** * Override this method to determine how all the rows on a crud * page are displayed */ - protected def doCrudAllRows(list: List[TheCrudType])(in: NodeSeq): NodeSeq = - list.take(rowsPerPage).flatMap{ - c => - bind("crud", in , "row_item" -> doCrudAllRowItem(c) _, - FuncAttrBindParam("edit_href", { ignore : NodeSeq => - Text(editPathString+"/"+(obscurePrimaryKey(c))) },"href"), - - FuncAttrBindParam("view_href", { ignore : NodeSeq => - Text(viewPathString+"/"+ - (obscurePrimaryKey(c)))},"href"), - - FuncAttrBindParam("delete_href", { ignore : NodeSeq => - Text(deletePathString+"/"+ - (obscurePrimaryKey(c)))},"href") - )} + protected def doCrudAllRows(list: List[TheCrudType]): (NodeSeq)=>NodeSeq = { + "^" #> list.take(rowsPerPage).map { rowItem => + ".row-item" #> doCrudAllRowItem(rowItem) & + ".view [href]" #> (s"$viewPathString/${obscurePrimaryKey(rowItem)}") & + ".edit [href]" #> (s"$editPathString/${obscurePrimaryKey(rowItem)}") & + ".delete [href]" #> (s"$deletePathString/${obscurePrimaryKey(rowItem)}") + } + } /** * Override this method to change how the previous link is * generated */ - protected def crudAllPrev(first: Long)(in: NodeSeq) = - if (first < rowsPerPage)   - else NodeSeq = { + if (first < rowsPerPage) { + ClearNodes + } else { + "^ <*>" #> + " #> + + } + } /** * Override this method if you want to change the behavior * of displaying records via the crud.all snippet */ - protected def doCrudAll(in: NodeSeq): NodeSeq = { + protected def doCrudAll: (NodeSeq)=>NodeSeq = { val first = S.param("first").map(toLong) openOr 0L val list = findForList(first, rowsPerPage) - - - - - bind("crud", in, "header_item" -> doCrudAllHeaderItems _, - "row" -> doCrudAllRows(list) _, - "prev" -> crudAllPrev(first) _, - "next" -> crudAllNext(first, list) _) - + + ".header-item" #> doCrudAllHeaderItems & + ".row" #> doCrudAllRows(list) & + ".previous" #> crudAllPrev(first) & + ".next" #> crudAllNext(first, list) } @@ -770,13 +760,14 @@ trait Crudify { val snipName = S.currentSnippet def loop(html:NodeSeq): NodeSeq = { - def error(field: BaseField): NodeSeq = + def error(field: BaseField): NodeSeq = { field.uniqueFieldId match { - case fid @ Full(id) => S.getNotices.filter(_._3 == fid).flatMap(err => - List(Text(" "), {err._2}) ) + case fid @ Full(id) => S.getNotices.filter(_._3 == fid).flatMap(err => + List(Text(" "), {err._2}) ) - case _ => NodeSeq.Empty - } + case _ => NodeSeq.Empty + } + } def doFields(html: NodeSeq): NodeSeq = for { @@ -784,11 +775,13 @@ trait Crudify { field <- computeFieldFromPointer(item, pointer).toList if field.show_? form <- field.toForm.toList - node <- bind("crud", html, - "name" -> (wrapNameInRequired(field.displayHtml, - field.required_?) ++ - error(field)), - "form" -> form) + bindNode = + ".name *" #> { + wrapNameInRequired(field.displayHtml, field.required_?) ++ + error(field) + } & + ".form *" #> form + node <- bindNode(html) } yield node def doSubmit() = item.validate match { @@ -802,10 +795,11 @@ trait Crudify { snipName.foreach(S.mapSnippet(_, loop)) } - bind("crud", html, - "field" -> doFields _, - "submit" -> - ((text: NodeSeq) => SHtml.submit(text.text, doSubmit _))) + val bind = + ".field" #> doFields _ & + "type=submit" #> SHtml.onSubmitUnit(doSubmit _) + + bind(html) } loop(in) From a5fa15c69ed48286b65c05ab495f2762411bcedb Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 26 May 2014 11:16:09 -0400 Subject: [PATCH 0921/1949] Fix an issue with mapper's use of new paginator. Before paginator.paginate needed a _ to get a NodeSeq=>NodeSeq; that is no longer necessary. --- .../src/main/scala/net/liftweb/mapper/view/Paginator.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/persistence/mapper/src/main/scala/net/liftweb/mapper/view/Paginator.scala b/persistence/mapper/src/main/scala/net/liftweb/mapper/view/Paginator.scala index 755b3dd9b7..73fbd0ed03 100644 --- a/persistence/mapper/src/main/scala/net/liftweb/mapper/view/Paginator.scala +++ b/persistence/mapper/src/main/scala/net/liftweb/mapper/view/Paginator.scala @@ -32,7 +32,7 @@ import net.liftweb.mapper.{Mapper, MetaMapper, MappedField, * @author nafg and Timothy Perrett */ trait PaginatedModelSnippet[T <: Mapper[T]] extends ModelSnippet[T] { - abstract override def dispatch: DispatchIt = super.dispatch orElse Map("paginate" -> paginator.paginate _ ) + abstract override def dispatch: DispatchIt = super.dispatch orElse Map("paginate" -> paginator.paginate) /** * The paginator to delegate to */ From b15949999f5e6b0e19b040e19af3f851d3273c19 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 26 May 2014 11:17:08 -0400 Subject: [PATCH 0922/1949] Remove FormProcessor. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There doesn’t seem to be any documentation describing how to truly use it, and it was introduced as an “experimental” feature. If folks ask for it back, we can restore it after inquiring as to how they’re using it. --- .../liftweb/mapper/view/FormProcessor.scala | 70 ------------------- 1 file changed, 70 deletions(-) delete mode 100644 persistence/mapper/src/main/scala/net/liftweb/mapper/view/FormProcessor.scala diff --git a/persistence/mapper/src/main/scala/net/liftweb/mapper/view/FormProcessor.scala b/persistence/mapper/src/main/scala/net/liftweb/mapper/view/FormProcessor.scala deleted file mode 100644 index b1f1a49ce5..0000000000 --- a/persistence/mapper/src/main/scala/net/liftweb/mapper/view/FormProcessor.scala +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2009-2011 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 mapper -package view - -import scala.xml.{Group, Elem, NodeSeq} -import scala.collection.mutable.{Map => mMap} - -import net.liftweb.http.SHtml -import net.liftweb.util.{Helpers} -import net.liftweb.common.{Full, Empty} - - -/** - * This class can be used to act on all submitted values in one block of code. - * This enables one to use try/catch when setting values etc. - * @author nafg - */ -abstract class FormProcessor(prefix: String) { - - def text(attrs: SHtml.ElemAttr*)(init: String, action: String=>Unit) = SHtml.text(init, action, attrs:_*) - def text: (String,String=>Unit)=>NodeSeq = text()_ - def checkbox(attrs: SHtml.ElemAttr*)(init: Boolean, action: Boolean=>Unit) = SHtml.checkbox(init, action, (attrs: Seq[SHtml.ElemAttr]):_*) - def checkbox: (Boolean, Boolean=>Unit)=>NodeSeq = checkbox()_ - - val stringValues: mMap[String, String] = mMap.empty[String, String] - val strings = mMap.empty[String, (String,String=>Unit)=>NodeSeq] - - val booleanValues = mMap.empty[String, Boolean] - val booleans = mMap.empty[String, (Boolean,Boolean=>Unit)=>NodeSeq] - - - - - def bind(xhtml: NodeSeq) = { - def transform(node: NodeSeq): NodeSeq = { - put - node match { - case Elem(`prefix`, label, _, _, _*) if strings.keys.toIterator contains label => - strings(label)(stringValues(label), stringValues(label) = _) - case Elem(`prefix`, label, _, _, _*) if booleans.keys.toIterator contains label => - booleans(label)(booleanValues(label), booleanValues(label) = _) - case other => other - } - } - Helpers.bind(prefix, Full(transform _), Empty, xhtml) ++ - Seq(SHtml.hidden(()=>get)) - - } - - - def put: Unit - def get: Unit -} - From 07f27b0d5d9967a77f009709aa85d51bf7f8cf6b Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 26 May 2014 11:42:08 -0400 Subject: [PATCH 0923/1949] Replace ModelView BindParams etc with CssSels. --- .../net/liftweb/mapper/view/ModelView.scala | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/persistence/mapper/src/main/scala/net/liftweb/mapper/view/ModelView.scala b/persistence/mapper/src/main/scala/net/liftweb/mapper/view/ModelView.scala index 14d67f4a7e..7782cc0a25 100644 --- a/persistence/mapper/src/main/scala/net/liftweb/mapper/view/ModelView.scala +++ b/persistence/mapper/src/main/scala/net/liftweb/mapper/view/ModelView.scala @@ -67,17 +67,17 @@ trait ModelSnippet[T <: Mapper[T]] extends StatefulSnippet { def dispatch: DispatchIt = { case "list" => list _ case "edit" => edit _ - case "newOrEdit" => view.newOrEdit _ + case "newOrEdit" => view.newOrEdit } /** - * An "edit" BindParam + * A ".edit" CssSel */ - def editAction(e: T) = TheBindParam("edit", link("edit", ()=>load(e), Text(?("Edit")))) + def editAction(e: T) = ".edit" #> link("edit", ()=>load(e), Text(?("Edit"))) /** - * A "remove" BindParam + * A ".remove" CssSel */ - def removeAction(e: T) = TheBindParam("remove", link("list", ()=>e.delete_!, Text(?("Remove")))) + def removeAction(e: T) = ".remove" #> link("list", ()=>e.delete_!, Text(?("Remove"))) } @@ -110,10 +110,12 @@ class ModelView[T <: Mapper[T]](var entity: T, val snippet: ModelSnippet[T]) { * existing entity is being edited or a new one is being * created. */ - def newOrEdit(xhtml: NodeSeq) = - chooseTemplate("if", - if(entity.saved_?) "edit" else "new", - xhtml) + def newOrEdit = { + if (entity.saved_?) + ".edit ^^" #> "ignored" + else + ".new ^^" #> "ignored" + } /** * This method checks whether the entity @@ -155,23 +157,23 @@ class ModelView[T <: Mapper[T]](var entity: T, val snippet: ModelSnippet[T]) { /** - * Returns a BindParam that contains a link to load and edit this entity + * Returns a CssSel that binds a link to ".edit" to load and edit this entity */ - lazy val editAction = TheBindParam("edit", snippet.link("edit", ()=>load, Text(?("Edit")))) + lazy val editAction = ".edit" #> snippet.link("edit", ()=>load, Text(?("Edit"))) /** - * Returns a BindParam that contains a link to delete this entity + * Returns a CssSel that binds a link to ".remove" that contains a link to delete this entity */ - lazy val removeAction = TheBindParam("remove", snippet.link("list", ()=>remove, Text(?("Remove")))) + lazy val removeAction = ".remove" #> snippet.link("list", ()=>remove, Text(?("Remove"))) /** - * Returns a BindParam that binds "name" to the field named "name." + * Returns a CssSel that binds the contents of an element with class "." + * to the field named `name`. * If the field has a Full toForm implementation then that is used; * otherwise its asHtml is called. */ def edit(name: String) = { entity.fieldByName(name).map { (field: net.liftweb.mapper.MappedField[_,_]) => - TheBindParam(name, field.toForm.openOr(field.asHtml)) + s".$name *" #> field.toForm.openOr(field.asHtml) }.openOrThrowException("If nobody has complained about this giving a NPE, I'll assume it is safe") - } } From 7485f3e5bb9bded9cd9055dcf43893d561d9fb29 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 26 May 2014 23:18:42 -0400 Subject: [PATCH 0924/1949] Remove bind from TableEditor/Util in mapper.view. --- .../net/liftweb/mapper/view/TableEditor.scala | 94 ++++++++++--------- .../scala/net/liftweb/mapper/view/Util.scala | 57 ++++++----- 2 files changed, 82 insertions(+), 69 deletions(-) diff --git a/persistence/mapper/src/main/scala/net/liftweb/mapper/view/TableEditor.scala b/persistence/mapper/src/main/scala/net/liftweb/mapper/view/TableEditor.scala index 7c92a8d51b..8be6e27149 100644 --- a/persistence/mapper/src/main/scala/net/liftweb/mapper/view/TableEditor.scala +++ b/persistence/mapper/src/main/scala/net/liftweb/mapper/view/TableEditor.scala @@ -21,8 +21,7 @@ package view import xml.{NodeSeq, Text} import common.{Box, Full, Empty} -import util.BindPlus._ -import util.{Helpers, BindHelpers} +import util.Helpers import Helpers._ import http.{SHtml, S, DispatchSnippet, js} import S.? @@ -210,7 +209,7 @@ package snippet { def dispatch = { case "edit" => val o = getInstance.openOrThrowException("if we don't have the table attr, we want the dev to know about it.") - o.edit _ + o.edit } } } @@ -248,7 +247,7 @@ trait ItemsListEditor[T<:Mapper[T]] { def customBind(item: T): NodeSeq=>NodeSeq = (ns: NodeSeq) => ns - def edit(xhtml: NodeSeq): NodeSeq = { + def edit: (NodeSeq)=>NodeSeq = { def unsavedScript = ({Script(Run(""" var safeToContinue = false window.onbeforeunload = function(evt) {{ // thanks Tim! @@ -269,47 +268,50 @@ trait ItemsListEditor[T<:Mapper[T]] { } else { unsavedScript } - optScript ++ xhtml.bind("header", - "fields" -> eachField[T]( - items.metaMapper, - (f: MappedField[_,T]) => Seq( - "name" -> SHtml.link(S.uri, sortFn(f), Text(capify(f.displayName))) - ), - fieldFilter - ) - ).bind("table", - "title" -> title, - "insertBtn" -> SHtml.submit(?("Insert"), onInsert _, noPrompt), - "items" -> {ns:NodeSeq => - items.items.flatMap {i => - bind("item", - customBind(i)(ns), - "fields" -> eachField( - i, - (f: MappedField[_,T]) => Seq("form" -> f.toForm), - fieldFilter - ), - "removeBtn" -> SHtml.submit(?("Remove"), ()=>onRemove(i), noPrompt), - "msg" -> ((i.validate match { - case Nil => - if(!i.saved_?) Text(?("New")) else if(i.dirty_?) Text(?("Unsaved")) else NodeSeq.Empty - case errors => (
      {errors.flatMap(e =>
    • {e.msg}
    • )}
    ) - })) - ) - } ++ items.removed.flatMap { i => - bind("item", customBind(i)(ns), - "fields" -> eachField( - i, - {f: MappedField[_,T] => Seq("form" -> {f.asHtml})}, - fieldFilter - ), - "removeBtn" -> NodeSeq.Empty, - "msg" -> Text(?("Deleted")) - ) - }: NodeSeq - }, - "saveBtn" -> SHtml.submit(?("Save"), onSubmit _, noPrompt) - ) - } + val bindRemovedItems = + items.removed.map { item => + "^" #> customBind(item) andThen + ".fields" #> eachField(item, { f: MappedField[_, T] => ".form" #> {f.asHtml} }) & + ".removeBtn" #> SHtml.submit(?("Remove"), ()=>onRemove(item), noPrompt) & + ".msg" #> Text(?("Deleted")) + } + + val bindRegularItems = + items.items.map { item => + "^" #> customBind(item) andThen + ".fields" #> eachField(item, { f: MappedField[_, T] => ".form" #> f.toForm }) & + ".removeBtn" #> SHtml.submit(?("Remove"), ()=>onRemove(item), noPrompt) & + ".msg" #> { + item.validate match { + case Nil => + if (! item.saved_?) + Text(?("New")) + else if (item.dirty_?) + Text(?("Unsaved")) + else + NodeSeq.Empty + case errors => +
      {errors.flatMap(e =>
    • {e.msg}
    • )}
    + } + } + } + + "^ >*" #> optScript andThen + ".fields *" #> { + eachField[T]( + items.metaMapper, + { f: MappedField[_, T] => + ".name" #> SHtml.link(S.uri, sortFn(f), Text(capify(f.displayName))) + }, + fieldFilter + ) + } & + ".table" #> { + ".title *" #> title & + ".insertBtn" #> SHtml.submit(?("Insert"), onInsert _, noPrompt) & + ".item" #> (bindRegularItems ++ bindRemovedItems) & + ".saveBtn" #> SHtml.submit(?("Save"), onSubmit _, noPrompt) + } + } } diff --git a/persistence/mapper/src/main/scala/net/liftweb/mapper/view/Util.scala b/persistence/mapper/src/main/scala/net/liftweb/mapper/view/Util.scala index f90565d2da..c12462e2a3 100644 --- a/persistence/mapper/src/main/scala/net/liftweb/mapper/view/Util.scala +++ b/persistence/mapper/src/main/scala/net/liftweb/mapper/view/Util.scala @@ -23,9 +23,8 @@ import net.liftweb.mapper.{Mapper, } import net.liftweb.common.{Full, Box} -import net.liftweb.util.{Helpers, BindHelpers} - -import Helpers._ +import net.liftweb.util._ + import Helpers._ import scala.xml.{NodeSeq, Elem} @@ -73,33 +72,45 @@ object Util { */ def eachField[T<:net.liftweb.mapper.Mapper[T]]( mapper: T, - fn:MappedField[_,T]=>Seq[BindParam], + fn:MappedField[_,T]=>CssSel, filter: MappedField[_,T]=>Boolean ): NodeSeq=>NodeSeq = { - (ns: NodeSeq) => BindHelpers.attr("fields") match { - case Some(fields) => - NodeSeq.fromSeq( - fields.text.split("\\s+").flatMap {f => - val field = mapper.fieldByName(f.toString) - field match { - case Full(f) if filter(f) => - bind("field", ns, fn(f) : _*) - case _ => - NodeSeq.Empty - } - } - ) - case None => - NodeSeq.fromSeq( - mapper.formFields.filter(filter).flatMap { case f: MappedField[_,T] => - bind("field",ns, fn(f): _*) + def fieldBindIfWanted(fieldName: String) = { + mapper.fieldByName(fieldName).filter(filter) match { + case Full(field) => + Some(fn(field)) + case _ => + None + } + } + + "^" #> { ns: NodeSeq => + val fieldsAttribute = (ns \ "@fields") + + val bind: Seq[CssSel] = + if (fieldsAttribute.nonEmpty) { + for { + fieldName <- fieldsAttribute.text.split("\\s+") + // the following hackery is brought to you by the Scala compiler not + // properly typing MapperField[_, T] in the context of the for + // comprehension + fieldBind <- fieldBindIfWanted(fieldName) + } yield { + ".field" #> fieldBind + } + } else { + mapper.formFields.filter(filter).map { + case field: MappedField[_, T] => + ".field" #> fn(field) } - ) + } + + bind.map(_(ns)) } } def eachField[T<:net.liftweb.mapper.Mapper[T]]( mapper: T, - fn:MappedField[_,T]=>Seq[BindParam] + fn:MappedField[_,T]=>CssSel ): NodeSeq=>NodeSeq = eachField(mapper, fn, (f:MappedField[_,T])=>true) From 44ae85fa1652f24ebffdce37e4c8b843cfcdf864 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 26 May 2014 23:36:41 -0400 Subject: [PATCH 0925/1949] Fix NamedCometPerTabSpec. This was broken because comet actors used to extend BindHelpers, which pulled in CSS selector implicits. We have CometActor mix in CssBindImplicits now so that these are in scope inside comets. --- web/webkit/src/main/scala/net/liftweb/http/CometActor.scala | 2 +- .../test/scala/net/liftweb/http/NamedCometPerTabSpec.scala | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala b/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala index 5d6993f5ab..600cc3e502 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala @@ -447,7 +447,7 @@ abstract class CometActorJWithCometListener extends CometActorJ with CometListen /** * Takes care of the plumbing for building Comet-based Web Apps */ -trait CometActor extends LiftActor with LiftCometActor { +trait CometActor extends LiftActor with LiftCometActor with CssBindImplicits { private val logger = Logger(classOf[CometActor]) val uniqueId = Helpers.nextFuncName private var spanId = uniqueId diff --git a/web/webkit/src/test/scala/net/liftweb/http/NamedCometPerTabSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/NamedCometPerTabSpec.scala index 47dd58ae47..f403fbd97a 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/NamedCometPerTabSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/NamedCometPerTabSpec.scala @@ -17,11 +17,12 @@ package net.liftweb package http -import js.JsCmds -import xml._ +import scala.xml._ + import org.specs2.mutable.Specification import common._ +import js.JsCmds /** * System under specification for NamedComet* files. From dd5da3a5b2901890680661af6a6486c76feeb521 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 26 May 2014 23:51:59 -0400 Subject: [PATCH 0926/1949] Fix compilation of LiftJavaScriptSpec. This broke when we changed the lift JS stuff to use a single LiftRules FactoryMaker to determine both whether it should be generated and which settings should be used. The specs are still failing due to mismatches with the latest JS stuff. --- .../scala/net/liftweb/http/js/LiftJavaScriptSpec.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/web/webkit/src/test/scala/net/liftweb/http/js/LiftJavaScriptSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/js/LiftJavaScriptSpec.scala index 916ae2b0f6..f8fd5125f4 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/js/LiftJavaScriptSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/js/LiftJavaScriptSpec.scala @@ -66,11 +66,11 @@ object LiftJavaScriptSpec extends Specification { } "create init command" in { S.initIfUninitted(session) { - val init = LiftJavaScript.initCmd(LiftRules.javascriptSettings.vend().apply(session)) - init.toJsCmd must_== - """var lift_settings = {"ajaxPath": "ajax_request", "ajaxRetryCount": 4, "ajaxPostTimeout": 5000, "enableGc": true, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometPath": "comet_request", "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "srvr1", "logError": function(msg) {lift.logError(msg);}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}; + val init = LiftRules.javaScriptSettings.vend().map(_.apply(session)).map(LiftJavaScript.initCmd(_).toJsCmd) + init must_== + Full("""var lift_settings = {"ajaxPath": "ajax_request", "ajaxRetryCount": 4, "ajaxPostTimeout": 5000, "enableGc": true, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometPath": "comet_request", "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "srvr1", "logError": function(msg) {lift.logError(msg);}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}; |window.lift.extend(lift_settings,window.liftJQuery); - |window.lift.init(lift_settings);""".stripMargin + |window.lift.init(lift_settings);""".stripMargin) } } "create init command with custom setting" in { From 8a53ed83877fca59ca1af3b6b73d9ef12ee936ac Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Tue, 27 May 2014 00:04:40 -0400 Subject: [PATCH 0927/1949] Fix LiftJavaScript specs. Some stuff was bringing LiftJavaScript into alignment with what ths specs expected, and some bringing the specs into alignment with what LiftJavaScript now does. --- .../scala/net/liftweb/http/js/LiftJavaScript.scala | 8 ++++---- .../net/liftweb/http/js/LiftJavaScriptSpec.scala | 14 +++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/js/LiftJavaScript.scala b/web/webkit/src/main/scala/net/liftweb/http/js/LiftJavaScript.scala index 3dd49eec93..27cc263a96 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/js/LiftJavaScript.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/js/LiftJavaScript.scala @@ -73,13 +73,13 @@ object LiftJavaScript { def initCmd(settings: JsObj): JsCmd = { val extendCmd = LiftRules.jsArtifacts match { - case jsa: JQueryArtifacts => Call("window.lift.extend", JsVar("window", "lift_settings"), JsVar("window", "liftJQuery")) - case _ => Call("window.lift.extend", JsVar("window", "lift_settings"), JsVar("window", "liftVanilla")) + case jsa: JQueryArtifacts => Call("window.lift.extend", JsVar("lift_settings"), JsVar("window", "liftJQuery")) + case _ => Call("window.lift.extend", JsVar("lift_settings"), JsVar("window", "liftVanilla")) } - SetExp(JsVar("window", "lift_settings"), settings) & + JsCrVar("lift_settings", settings) & extendCmd & - Call("window.lift.init", JsVar("window", "lift_settings")) + Call("window.lift.init", JsVar("lift_settings")) /*val extendCmd = LiftRules.jsArtifacts match { case jsa: JQueryArtifacts => Call("window.lift.extend", settings, JsVar("window.liftJQuery")) diff --git a/web/webkit/src/test/scala/net/liftweb/http/js/LiftJavaScriptSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/js/LiftJavaScriptSpec.scala index f8fd5125f4..b615a8b122 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/js/LiftJavaScriptSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/js/LiftJavaScriptSpec.scala @@ -40,46 +40,46 @@ object LiftJavaScriptSpec extends Specification { "create default settings" in { S.initIfUninitted(session) { val settings = LiftJavaScript.settings - settings.toJsCmd must_== """{"ajaxPath": "ajax_request", "ajaxRetryCount": 3, "ajaxPostTimeout": 5000, "enableGc": true, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometPath": "comet_request", "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "", "logError": function(msg) {}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}""" + settings.toJsCmd must_== """{"liftPath": "lift", "ajaxRetryCount": 3, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "", "logError": function(msg) {}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}""" } } "create custom static settings" in { S.initIfUninitted(session) { LiftRules.ajaxRetryCount = Full(4) val settings = LiftJavaScript.settings - settings.toJsCmd must_== """{"ajaxPath": "ajax_request", "ajaxRetryCount": 4, "ajaxPostTimeout": 5000, "enableGc": true, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometPath": "comet_request", "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "", "logError": function(msg) {}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}""" + settings.toJsCmd must_== """{"liftPath": "lift", "ajaxRetryCount": 4, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "", "logError": function(msg) {}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}""" } } "create custom dynamic settings" in { S.initIfUninitted(session) { LiftRules.cometServer = () => "srvr1" val settings = LiftJavaScript.settings - settings.toJsCmd must_== """{"ajaxPath": "ajax_request", "ajaxRetryCount": 4, "ajaxPostTimeout": 5000, "enableGc": true, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometPath": "comet_request", "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "srvr1", "logError": function(msg) {}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}""" + settings.toJsCmd must_== """{"liftPath": "lift", "ajaxRetryCount": 4, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "srvr1", "logError": function(msg) {}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}""" } } "create custom function settings" in { S.initIfUninitted(session) { LiftRules.jsLogFunc = Full(v => JE.Call("lift.logError", v)) val settings = LiftJavaScript.settings - settings.toJsCmd must_== """{"ajaxPath": "ajax_request", "ajaxRetryCount": 4, "ajaxPostTimeout": 5000, "enableGc": true, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometPath": "comet_request", "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "srvr1", "logError": function(msg) {lift.logError(msg);}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}""" + settings.toJsCmd must_== """{"liftPath": "lift", "ajaxRetryCount": 4, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "srvr1", "logError": function(msg) {lift.logError(msg);}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}""" } } "create init command" in { S.initIfUninitted(session) { val init = LiftRules.javaScriptSettings.vend().map(_.apply(session)).map(LiftJavaScript.initCmd(_).toJsCmd) init must_== - Full("""var lift_settings = {"ajaxPath": "ajax_request", "ajaxRetryCount": 4, "ajaxPostTimeout": 5000, "enableGc": true, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometPath": "comet_request", "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "srvr1", "logError": function(msg) {lift.logError(msg);}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}; + Full("""var lift_settings = {"liftPath": "lift", "ajaxRetryCount": 4, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "srvr1", "logError": function(msg) {lift.logError(msg);}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}; |window.lift.extend(lift_settings,window.liftJQuery); |window.lift.init(lift_settings);""".stripMargin) } } "create init command with custom setting" in { S.initIfUninitted(session) { - val settings = JsObj("ajaxPath" -> "ajax_req", "mysetting" -> 99).extend(LiftJavaScript.settings) + val settings = LiftJavaScript.settings.extend(JsObj("liftPath" -> "liftyStuff", "mysetting" -> 99)) val init = LiftJavaScript.initCmd(settings) println(init.toJsCmd) init.toJsCmd must_== - """var lift_settings = {"ajaxPath": "ajax_request", "mysetting": 99, "ajaxRetryCount": 4, "ajaxPostTimeout": 5000, "enableGc": true, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometPath": "comet_request", "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "srvr1", "logError": function(msg) {lift.logError(msg);}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}; + """var lift_settings = {"liftPath": "liftyStuff", "ajaxRetryCount": 4, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "srvr1", "logError": function(msg) {lift.logError(msg);}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}, "mysetting": 99}; |window.lift.extend(lift_settings,window.liftJQuery); |window.lift.init(lift_settings);""".stripMargin } From 1f1f216aa578460fd057db327c48d0247a199a40 Mon Sep 17 00:00:00 2001 From: Mike Limansky Date: Tue, 22 Jan 2013 02:17:26 +0100 Subject: [PATCH 0928/1949] Fix: mail attachments should be placed in multipart/mixed MIME part. --- contributors.md | 6 +++ .../main/scala/net/liftweb/util/Mailer.scala | 49 ++++++++++++------- 2 files changed, 38 insertions(+), 17 deletions(-) diff --git a/contributors.md b/contributors.md index bf743bd7f3..1b530b3b7f 100644 --- a/contributors.md +++ b/contributors.md @@ -209,3 +209,9 @@ Robert Freytag ### Email: ### robertfreytag+lift at gmail .. com + +### Name: ### +Mikhail Limansky + +### Email: ### +mike.limansky at gmail dot com \ No newline at end of file diff --git a/core/util/src/main/scala/net/liftweb/util/Mailer.scala b/core/util/src/main/scala/net/liftweb/util/Mailer.scala index 04e974e0df..a127fb80b8 100644 --- a/core/util/src/main/scala/net/liftweb/util/Mailer.scala +++ b/core/util/src/main/scala/net/liftweb/util/Mailer.scala @@ -290,30 +290,45 @@ trait Mailer extends SimpleInjector { case XHTMLPlusImages(html, img@_*) => val html_mp = new MimeMultipart("related") val bp2 = new MimeBodyPart + val (attachs, images) = img.partition(_.attachment) bp2.setContent(encodeHtmlBodyPart(html), "text/html; charset=" + charSet) html_mp.addBodyPart(bp2) - img.foreach { - i => - val rel_bpi = new MimeBodyPart - rel_bpi.setFileName(i.name) - rel_bpi.setContentID(i.name) - rel_bpi.setDisposition(if (!i.attachment) "inline" else "attachment") - rel_bpi.setDataHandler(new javax.activation.DataHandler(new javax.activation.DataSource { - def getContentType = i.mimeType - - def getInputStream = new java.io.ByteArrayInputStream(i.bytes) - - def getName = i.name - - def getOutputStream = throw new java.io.IOException("Unable to write to item") - })) - html_mp.addBodyPart(rel_bpi) + images.foreach { + i => html_mp.addBodyPart(buildAttachment(i)) + } + if (attachs.isEmpty) { + bp.setContent(html_mp) + } else { + val mixed_mp = new MimeMultipart("mixed") + val html_p = new MimeBodyPart + html_p.setContent(html_mp) + mixed_mp.addBodyPart(html_p) + attachs.foreach { + i => mixed_mp.addBodyPart(buildAttachment(i)) + } + bp.setContent(mixed_mp) } - bp.setContent(html_mp) } bp } + private def buildAttachment(holder: PlusImageHolder) = { + val part = new MimeBodyPart + part.setFileName(holder.name) + part.setContentID(holder.name) + part.setDisposition(if (holder.attachment) Part.ATTACHMENT else Part.INLINE) + part.setDataHandler(new javax.activation.DataHandler(new javax.activation.DataSource { + def getContentType = holder.mimeType + + def getInputStream = new java.io.ByteArrayInputStream(holder.bytes) + + def getName = holder.name + + def getOutputStream = throw new java.io.IOException("Unable to write to item") + })) + part + } + /** * Asynchronously send an email. From 1036743ae03e0b9e65cf20b830e7955de1d983ba Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Tue, 27 May 2014 23:18:49 -0400 Subject: [PATCH 0929/1949] Start pulling the yui compressor plugin from git directly. --- project/plugins.sbt | 2 -- project/project/Plugin.scala | 3 ++- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 7820b60ad5..436c7c0a40 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,7 +1,5 @@ DefaultOptions.addPluginResolvers -addSbtPlugin("in.drajit.sbt" % "sbt-yui-compressor" % "0.2.2-SNAPSHOT") - resolvers += "sbt-idea-repo" at "http://mpeltonen.github.com/maven/" addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.6.0") diff --git a/project/project/Plugin.scala b/project/project/Plugin.scala index b8fe0d9835..ecd25ad4f3 100644 --- a/project/project/Plugin.scala +++ b/project/project/Plugin.scala @@ -1,6 +1,7 @@ import sbt._ object PluginDef extends Build { - lazy val root = Project("plugins", file(".")) dependsOn(buildPlugin) + lazy val root = Project("plugins", file(".")) dependsOn(buildPlugin, yuiCompressorPlugin) lazy val buildPlugin = uri("git://github.com/lift/sbt-lift-build.git#724fb133beac77bbd06d3fb8ea086a1c88ee2a7d") + lazy val yuiCompressorPlugin = uri("git://github.com/indrajitr/sbt-yui-compressor.git#89304ec0c988183d1f1a889e665e0269fe513031") } From 45537ea3d68fcb4346f6d923198b69ce2d2a68fa Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Tue, 27 May 2014 23:20:03 -0400 Subject: [PATCH 0930/1949] Somehow 2.10.0 ended up as the default version? Fixed. --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 16c114bc77..8490ae2f53 100644 --- a/build.sbt +++ b/build.sbt @@ -12,7 +12,7 @@ startYear in ThisBuild := Some(2006) organizationName in ThisBuild := "WorldWide Conferencing, LLC" -scalaVersion in ThisBuild := "2.10.0" +scalaVersion in ThisBuild := "2.10.4" crossScalaVersions in ThisBuild := Seq("2.10.4", "2.9.2", "2.9.1-1", "2.9.1") From 51fd39b6fa2a94d5a3300970424e3ed6236a7cf8 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Wed, 28 May 2014 10:47:30 -0400 Subject: [PATCH 0931/1949] Bump sbt to version 0.13.5. --- liftsh | 4 ++-- liftsh.cmd | 4 ++-- project/build.properties | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/liftsh b/liftsh index 0f2e539129..65555bb13d 100755 --- a/liftsh +++ b/liftsh @@ -2,8 +2,8 @@ # Make sure to change the name of the launcher jar and the source when bumping sbt version # so that the existence test below fails and we download the new jar. -SBT_LAUNCHER_PATH="project/sbt-launch-0.13.1.jar" -SBT_LAUNCHER_SOURCE="http://repo.typesafe.com/typesafe/ivy-releases/org.scala-sbt/sbt-launch/0.13.1/sbt-launch.jar" +SBT_LAUNCHER_PATH="project/sbt-launch-0.13.5.jar" +SBT_LAUNCHER_SOURCE="http://repo.typesafe.com/typesafe/ivy-releases/org.scala-sbt/sbt-launch/0.13.5/sbt-launch.jar" # Download the sbt launcher on-the-fly if it's not already in the repository. if test ! -f $SBT_LAUNCHER_PATH; then diff --git a/liftsh.cmd b/liftsh.cmd index 12e0eeeb12..c294d5005b 100644 --- a/liftsh.cmd +++ b/liftsh.cmd @@ -1,7 +1,7 @@ @echo off -set SBT_LAUNCHER_PATH="project\sbt-launch-0.13.1.jar" -set SBT_LAUNCHER_SOURCE="http://repo.typesafe.com/typesafe/ivy-releases/org.scala-sbt/sbt-launch/0.13.1/sbt-launch.jar" +set SBT_LAUNCHER_PATH="project\sbt-launch-0.13.5.jar" +set SBT_LAUNCHER_SOURCE="http://repo.typesafe.com/typesafe/ivy-releases/org.scala-sbt/sbt-launch/0.13.5/sbt-launch.jar" if not exist %SBT_LAUNCHER_PATH% powershell -Command "(New-Object Net.WebClient).DownloadFile('%SBT_LAUNCHER_SOURCE%', '%SBT_LAUNCHER_PATH%')" diff --git a/project/build.properties b/project/build.properties index 0458ca613b..4bedffccc8 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1,2 +1,2 @@ # Deprecate using build.properties, use -Dsbt.version=... in launcher arg instead -sbt.version=0.13.1 +sbt.version=0.13.5 From 4bfde86edcfde8b2e3fc6c721ee6c6a1e2ac1b64 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Wed, 28 May 2014 11:00:13 -0400 Subject: [PATCH 0932/1949] Some formatting improvements in Mailer.buildMailBody --- .../main/scala/net/liftweb/util/Mailer.scala | 78 +++++++++++-------- 1 file changed, 45 insertions(+), 33 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/Mailer.scala b/core/util/src/main/scala/net/liftweb/util/Mailer.scala index a127fb80b8..8241b848dd 100644 --- a/core/util/src/main/scala/net/liftweb/util/Mailer.scala +++ b/core/util/src/main/scala/net/liftweb/util/Mailer.scala @@ -282,50 +282,62 @@ trait Mailer extends SimpleInjector { */ protected def buildMailBody(tab: MailBodyType): BodyPart = { val bp = new MimeBodyPart - tab match { - case PlainMailBodyType(txt) => bp.setText(txt, "UTF-8") - case PlainPlusBodyType(txt, charset) => bp.setText(txt, charset) - case XHTMLMailBodyType(html) => bp.setContent(encodeHtmlBodyPart(html), "text/html; charset=" + charSet) - - case XHTMLPlusImages(html, img@_*) => - val html_mp = new MimeMultipart("related") - val bp2 = new MimeBodyPart - val (attachs, images) = img.partition(_.attachment) - bp2.setContent(encodeHtmlBodyPart(html), "text/html; charset=" + charSet) - html_mp.addBodyPart(bp2) - images.foreach { - i => html_mp.addBodyPart(buildAttachment(i)) - } - if (attachs.isEmpty) { - bp.setContent(html_mp) - } else { - val mixed_mp = new MimeMultipart("mixed") - val html_p = new MimeBodyPart - html_p.setContent(html_mp) - mixed_mp.addBodyPart(html_p) - attachs.foreach { - i => mixed_mp.addBodyPart(buildAttachment(i)) - } - bp.setContent(mixed_mp) - } + + tab match { + case PlainMailBodyType(txt) => + bp.setText(txt, "UTF-8") + + case PlainPlusBodyType(txt, charset) => + bp.setText(txt, charset) + + case XHTMLMailBodyType(html) => + bp.setContent(encodeHtmlBodyPart(html), "text/html; charset=" + charSet) + + case XHTMLPlusImages(html, img@_*) => + val html_mp = new MimeMultipart("related") + val bp2 = new MimeBodyPart + val (attachments, images) = img.partition(_.attachment) + + bp2.setContent(encodeHtmlBodyPart(html), "text/html; charset=" + charSet) + html_mp.addBodyPart(bp2) + + images.foreach { image => + html_mp.addBodyPart(buildAttachment(image)) + } + + if (attachments.isEmpty) { + bp.setContent(html_mp) + } else { + val mixed_mp = new MimeMultipart("mixed") + val html_p = new MimeBodyPart + + html_p.setContent(html_mp) + mixed_mp.addBodyPart(html_p) + + attachments.foreach { attachment => + mixed_mp.addBodyPart(buildAttachment(attachment)) } + + bp.setContent(mixed_mp) + } + } + bp } private def buildAttachment(holder: PlusImageHolder) = { val part = new MimeBodyPart + part.setFileName(holder.name) part.setContentID(holder.name) part.setDisposition(if (holder.attachment) Part.ATTACHMENT else Part.INLINE) part.setDataHandler(new javax.activation.DataHandler(new javax.activation.DataSource { - def getContentType = holder.mimeType - - def getInputStream = new java.io.ByteArrayInputStream(holder.bytes) - - def getName = holder.name + def getContentType = holder.mimeType + def getInputStream = new java.io.ByteArrayInputStream(holder.bytes) + def getName = holder.name + def getOutputStream = throw new java.io.IOException("Unable to write to item") + })) - def getOutputStream = throw new java.io.IOException("Unable to write to item") - })) part } From e5a16edcbf13935a3414b6638f59ced4824038fb Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Wed, 28 May 2014 11:08:03 -0400 Subject: [PATCH 0933/1949] Improve variable names a bit to improve readability. --- .../main/scala/net/liftweb/util/Mailer.scala | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/Mailer.scala b/core/util/src/main/scala/net/liftweb/util/Mailer.scala index 8241b848dd..2000b6d017 100644 --- a/core/util/src/main/scala/net/liftweb/util/Mailer.scala +++ b/core/util/src/main/scala/net/liftweb/util/Mailer.scala @@ -294,31 +294,31 @@ trait Mailer extends SimpleInjector { bp.setContent(encodeHtmlBodyPart(html), "text/html; charset=" + charSet) case XHTMLPlusImages(html, img@_*) => - val html_mp = new MimeMultipart("related") - val bp2 = new MimeBodyPart val (attachments, images) = img.partition(_.attachment) + val relatedMultipart = new MimeMultipart("related") - bp2.setContent(encodeHtmlBodyPart(html), "text/html; charset=" + charSet) - html_mp.addBodyPart(bp2) + val htmlBodyPart = new MimeBodyPart + htmlBodyPart.setContent(encodeHtmlBodyPart(html), "text/html; charset=" + charSet) + relatedMultipart.addBodyPart(htmlBodyPart) images.foreach { image => - html_mp.addBodyPart(buildAttachment(image)) + relatedMultipart.addBodyPart(buildAttachment(image)) } if (attachments.isEmpty) { - bp.setContent(html_mp) + bp.setContent(relatedMultipart) } else { - val mixed_mp = new MimeMultipart("mixed") - val html_p = new MimeBodyPart + val mixedMultipart = new MimeMultipart("mixed") - html_p.setContent(html_mp) - mixed_mp.addBodyPart(html_p) + val relatedMultipartBodypart = new MimeBodyPart + relatedMultipartBodypart.setContent(relatedMultipart) + mixedMultipart.addBodyPart(relatedMultipartBodypart) attachments.foreach { attachment => - mixed_mp.addBodyPart(buildAttachment(attachment)) + mixedMultipart.addBodyPart(buildAttachment(attachment)) } - bp.setContent(mixed_mp) + bp.setContent(mixedMultipart) } } From 21e0dfadc12137bcfcd95617121b0f2138a782e3 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Wed, 28 May 2014 11:09:37 -0400 Subject: [PATCH 0934/1949] Add a comment documenting behavior of Exchange w/ mixed. We added this code because some Exchange servers don't play nice without a mixed multipart. This appears to be tied to some versions of the Exchange server, not neccicarily Outlook or other associated clients. --- core/util/src/main/scala/net/liftweb/util/Mailer.scala | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/util/src/main/scala/net/liftweb/util/Mailer.scala b/core/util/src/main/scala/net/liftweb/util/Mailer.scala index 2000b6d017..b019e1de7b 100644 --- a/core/util/src/main/scala/net/liftweb/util/Mailer.scala +++ b/core/util/src/main/scala/net/liftweb/util/Mailer.scala @@ -308,6 +308,10 @@ trait Mailer extends SimpleInjector { if (attachments.isEmpty) { bp.setContent(relatedMultipart) } else { + // Some old versions of Exchange server will not behave correclty without + // a mixed multipart wrapping file attachments. This appears to be linked to + // specific versions of Exchange and Outlook. See the discussion at + // https://github.com/lift/framework/pull/1569 for more details. val mixedMultipart = new MimeMultipart("mixed") val relatedMultipartBodypart = new MimeBodyPart From 52435235767110d546ec17000026518d8fc19898 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Wed, 28 May 2014 11:45:44 -0400 Subject: [PATCH 0935/1949] Add a spec to cover the new multipart/mixed behavior. --- .../scala/net/liftweb/util/MailerSpec.scala | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/core/util/src/test/scala/net/liftweb/util/MailerSpec.scala b/core/util/src/test/scala/net/liftweb/util/MailerSpec.scala index 3566767cfe..a19a8f3f70 100644 --- a/core/util/src/test/scala/net/liftweb/util/MailerSpec.scala +++ b/core/util/src/test/scala/net/liftweb/util/MailerSpec.scala @@ -23,7 +23,9 @@ import org.specs2.mutable.Specification import common._ -import Mailer._ +import Mailer.{From, To, Subject, PlainMailBodyType, XHTMLMailBodyType, XHTMLPlusImages, PlusImageHolder} + +import scala.io.Source /** * Systems under specification for Lift Mailer. @@ -105,5 +107,39 @@ object MailerSpec extends Specification { case x => failure("The complex message has content type of " + x.getClass.getName) } } + + "deliver emails with attachments as mixed multipart" in { + val attachmentBytes = Source.fromInputStream( + getClass.getClassLoader.getResourceAsStream("net/liftweb/util/Html5ParserSpec.page1.html") + ).map(_.toByte).toArray + val msg = doNewMessage { + sendMail( + From("sender@nowhere.com"), + Subject("This is a mixed email"), + To("recipient@nowhere.com"), + XHTMLPlusImages( + Here is some rich text , + PlusImageHolder("awesome.pdf", "text/html", attachmentBytes, true) + ) + ) + } + + println(msg.getContentType) + msg.getContent match { + case mp: MimeMultipart => + mp.getContentType.substring(0, 21) must_== "multipart/alternative" + + mp.getBodyPart(0).getContent match { + case mp2: MimeMultipart => + mp2.getContentType.substring(0, 15) must_== "multipart/mixed" + + case somethingElse => + failure("The message's multipart's first body part wasn't a MimeMultipart") + } + + case somethingElse => + failure("The complex message has content type of " + somethingElse.getClass.getName) + } + } } } From ff8d3c62f2ee126455e6a36f0b2029154edc0d7e Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Wed, 28 May 2014 11:47:07 -0400 Subject: [PATCH 0936/1949] Remove a println. --- core/util/src/test/scala/net/liftweb/util/MailerSpec.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/core/util/src/test/scala/net/liftweb/util/MailerSpec.scala b/core/util/src/test/scala/net/liftweb/util/MailerSpec.scala index a19a8f3f70..17cbd7bb80 100644 --- a/core/util/src/test/scala/net/liftweb/util/MailerSpec.scala +++ b/core/util/src/test/scala/net/liftweb/util/MailerSpec.scala @@ -124,7 +124,6 @@ object MailerSpec extends Specification { ) } - println(msg.getContentType) msg.getContent match { case mp: MimeMultipart => mp.getContentType.substring(0, 21) must_== "multipart/alternative" From 2e7c77290a148c76ac178d099295d2ba040decb6 Mon Sep 17 00:00:00 2001 From: Tim Nelson Date: Wed, 28 May 2014 15:21:05 -0500 Subject: [PATCH 0937/1949] Use StringHelpers.charSplit in MongoRules. --- .../mongodb/src/main/scala/net/liftweb/mongodb/MongoRules.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/MongoRules.scala b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/MongoRules.scala index 0ffe6c83f0..b574974421 100644 --- a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/MongoRules.scala +++ b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/MongoRules.scala @@ -19,7 +19,7 @@ import util.Helpers._ object MongoRules extends SimpleInjector { private def defaultCollectionNameFunc(conn: ConnectionIdentifier, name: String): String = { - name.split("\\.").toList.last.toLowerCase+"s" + charSplit(name, '.').last.toLowerCase+"s" } /** From 5d753d054a062ae1d693c76071ded2483729daa5 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Fri, 30 May 2014 11:02:06 -0400 Subject: [PATCH 0938/1949] Add withFilter implementation to BaseResponse. This is so it plays nice with for comprehensions. --- .../src/main/scala/net/liftweb/http/testing/TestFramework.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/web/testkit/src/main/scala/net/liftweb/http/testing/TestFramework.scala b/web/testkit/src/main/scala/net/liftweb/http/testing/TestFramework.scala index fbf38b125a..edc4b3b9bc 100644 --- a/web/testkit/src/main/scala/net/liftweb/http/testing/TestFramework.scala +++ b/web/testkit/src/main/scala/net/liftweb/http/testing/TestFramework.scala @@ -925,6 +925,8 @@ abstract class BaseResponse(override val baseUrl: String, f(st) st } + + def withFilter(f: FuncType => Unit): FuncType = this.filter(f) } class CompleteFailure(val serverName: String, val exception: Box[Throwable]) extends TestResponse { From 652d88291bbdb0270c9112449d83c35d593a2c30 Mon Sep 17 00:00:00 2001 From: Tim Nelson Date: Sun, 1 Jun 2014 09:56:12 -0500 Subject: [PATCH 0939/1949] Added mongodb.JsonExtractors for DBObject <-> JValue conversions. --- .../mongodb/record/field/DateField.scala | 4 +- .../mongodb/record/field/MongoListField.scala | 23 ++-- .../mongodb/record/field/ObjectIdField.scala | 6 +- .../mongodb/record/field/PatternField.scala | 4 +- .../mongodb/record/field/UUIDField.scala | 12 +- .../mongodb/record/MongoFieldSpec.scala | 28 ++--- .../scala/net/liftweb/mongodb/BsonDSL.scala | 12 +- .../net/liftweb/mongodb/JObjectParser.scala | 44 +++---- .../net/liftweb/mongodb/JsonExtractors.scala | 107 ++++++++++++++++++ .../main/scala/net/liftweb/mongodb/Meta.scala | 36 +++--- .../net/liftweb/mongodb/Serializers.scala | 36 +++--- .../net/liftweb/mongodb/BsonDSLSpec.scala | 20 ++++ 12 files changed, 220 insertions(+), 112 deletions(-) create mode 100644 persistence/mongodb/src/main/scala/net/liftweb/mongodb/JsonExtractors.scala diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/DateField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/DateField.scala index 34d01c5de6..144db8a787 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/DateField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/DateField.scala @@ -1,5 +1,5 @@ /* -* Copyright 2010-2013 WorldWide Conferencing, LLC +* Copyright 2010-2014 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. @@ -76,7 +76,7 @@ trait DateTypedField extends TypedField[Date] { case jv => JsRaw(Printer.compact(render(jv))) } - def asJValue: JValue = valueBox.map(v => mongodb.Meta.dateAsJValue(v, formats)) openOr (JNothing: JValue) + def asJValue: JValue = valueBox.map(v => JsonDate(v)(formats)) openOr (JNothing: JValue) } class DateField[OwnerType <: BsonRecord[OwnerType]](rec: OwnerType) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoListField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoListField.scala index d27741a6dd..4f7465be64 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoListField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoListField.scala @@ -19,9 +19,6 @@ package mongodb package record package field -import java.util.{Date, UUID} -import java.util.regex.Pattern - import scala.collection.JavaConversions._ import scala.xml.NodeSeq @@ -66,6 +63,8 @@ class MongoListField[OwnerType <: BsonRecord[OwnerType], ListType: Manifest](rec def defaultValue = List[ListType]() + implicit def formats = owner.meta.formats + def setFromAny(in: Any): Box[MyType] = { in match { case dbo: DBObject => setFromDBObject(dbo) @@ -83,18 +82,14 @@ class MongoListField[OwnerType <: BsonRecord[OwnerType], ListType: Manifest](rec def setFromJValue(jvalue: JValue): Box[MyType] = jvalue match { case JNothing|JNull if optional_? => setBox(Empty) - case JArray(arr) => setBox(Full((arr.map { jv => (jv match { - case JObject(JField("$oid", JString(s)) :: Nil) if (ObjectId.isValid(s)) => - new ObjectId(s) - case JObject(JField("$regex", JString(s)) :: JField("$flags", JInt(f)) :: Nil) => - Pattern.compile(s, f.intValue) - case JObject(JField("$dt", JString(s)) :: Nil) => - val dt = owner.meta.formats.dateFormat.parse(s).getOrElse(throw new MongoException("Can't parse "+ s + " to Date")) - if (owner.meta.formats.customSerializers.exists { _.isInstanceOf[DateTimeSerializer] }) new DateTime(dt) - else dt - case JObject(JField("$uuid", JString(s)) :: Nil) => UUID.fromString(s) + case JArray(array) => setBox(Full((array.map { + case JsonObjectId(objectId) => objectId + case JsonRegex(regex) => regex + case JsonUUID(uuid) => uuid + case JsonDateTime(dt) if (mf.toString == "org.joda.time.DateTime") => dt + case JsonDate(date) => date case other => other.values - }).asInstanceOf[ListType]}))) + }).asInstanceOf[MyType])) case other => setBox(FieldHelpers.expectedA("JArray", other)) } diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/ObjectIdField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/ObjectIdField.scala index 0337a88ec9..4d1c3fcf51 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/ObjectIdField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/ObjectIdField.scala @@ -1,5 +1,5 @@ /* - * Copyright 2010-2011 WorldWide Conferencing, LLC + * Copyright 2010-2014 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. @@ -90,8 +90,8 @@ class ObjectIdField[OwnerType <: BsonRecord[OwnerType]](rec: OwnerType) case jv => JsRaw(Printer.compact(render(jv))) } - def asJValue: JValue = valueBox.map(v => Meta.objectIdAsJValue(v, owner.meta.formats)) openOr (JNothing: JValue) + def asJValue: JValue = valueBox.map(v => JsonObjectId.asJValue(v, owner.meta.formats)) openOr (JNothing: JValue) - def createdAt: Date = new Date(this.get.getTime) + def createdAt: Date = this.get.getDate } diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/PatternField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/PatternField.scala index d356769c81..a4a7fd2da9 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/PatternField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/PatternField.scala @@ -1,5 +1,5 @@ /* -* Copyright 2010-2011 WorldWide Conferencing, LLC +* Copyright 2010-2014 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. @@ -70,6 +70,6 @@ class PatternField[OwnerType <: BsonRecord[OwnerType]](rec: OwnerType) case jv => Str(Printer.compact(render(jv))) } - def asJValue: JValue = valueBox.map(v => Meta.patternAsJValue(v)) openOr (JNothing: JValue) + def asJValue: JValue = valueBox.map(v => JsonRegex(v)) openOr (JNothing: JValue) } diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/UUIDField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/UUIDField.scala index bd296fd01d..845f573bb0 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/UUIDField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/UUIDField.scala @@ -1,5 +1,5 @@ /* -* Copyright 2010-2011 WorldWide Conferencing, LLC +* Copyright 2010-2014 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. @@ -11,10 +11,10 @@ * limitations under the License. */ -package net.liftweb -package mongodb -package record -package field +package net.liftweb +package mongodb +package record +package field import java.util.UUID @@ -82,7 +82,7 @@ class UUIDField[OwnerType <: BsonRecord[OwnerType]](rec: OwnerType) case jv => JsRaw(Printer.compact(render(jv))) } - def asJValue: JValue = valueBox.map(v => Meta.uuidAsJValue(v)) openOr (JNothing: JValue) + def asJValue: JValue = valueBox.map(v => JsonUUID(v)) openOr (JNothing: JValue) } diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala index 800c54394e..aca9f7507a 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala @@ -15,7 +15,8 @@ */ package net.liftweb -package mongodb.record +package mongodb +package record import java.util.{Calendar, Date, UUID} import java.util.regex.Pattern @@ -29,7 +30,6 @@ import org.joda.time.DateTime import common._ import json._ -import mongodb.{Meta => DBOMeta} import mongodb.BsonDSL._ import util.Helpers.randomString import http.{LiftSession, S} @@ -264,7 +264,7 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundExample Full() ) rec.mandatoryObjectIdField(oid) - new Date(oid.getTime) mustEqual rec.mandatoryObjectIdField.createdAt + oid.getDate mustEqual rec.mandatoryObjectIdField.createdAt } "PatternField" should { @@ -389,8 +389,8 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundExample rec.patternListField, JsArray(Str(ptrn1.toString), Str(ptrn2.toString)), JArray(List( - DBOMeta.patternAsJValue(ptrn1), - DBOMeta.patternAsJValue(ptrn2) + JsonRegex(ptrn1), + JsonRegex(ptrn2) )), Empty, false @@ -415,9 +415,9 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundExample rec.dateListField, JsArray(Str(dt1.toString), Str(dt2.toString), Str(dt3.toString)), JArray(List( - DBOMeta.dateAsJValue(dt1, MongoListTestRecord.formats), - DBOMeta.dateAsJValue(dt2, MongoListTestRecord.formats), - DBOMeta.dateAsJValue(dt3, MongoListTestRecord.formats) + JsonDate(dt1)(MongoListTestRecord.formats), + JsonDate(dt2)(MongoListTestRecord.formats), + JsonDate(dt3)(MongoListTestRecord.formats) )), Empty ) @@ -441,9 +441,9 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundExample rec.uuidListField, JsArray(Str(uuid1.toString), Str(uuid2.toString), Str(uuid3.toString)), JArray(List( - DBOMeta.uuidAsJValue(uuid1), - DBOMeta.uuidAsJValue(uuid2), - DBOMeta.uuidAsJValue(uuid3) + JsonUUID(uuid1), + JsonUUID(uuid2), + JsonUUID(uuid3) )), Empty ) @@ -467,9 +467,9 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundExample rec.dateTimeListField, JsArray(Str(dt1.toString), Str(dt2.toString), Str(dt3.toString)), JArray(List( - DBOMeta.dateAsJValue(dt1.toDate, MongoListTestRecord.formats), - DBOMeta.dateAsJValue(dt2.toDate, MongoListTestRecord.formats), - DBOMeta.dateAsJValue(dt3.toDate, MongoListTestRecord.formats) + JsonDate(dt1.toDate)(MongoListTestRecord.formats), + JsonDate(dt2.toDate)(MongoListTestRecord.formats), + JsonDate(dt3.toDate)(MongoListTestRecord.formats) )), Empty ) diff --git a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/BsonDSL.scala b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/BsonDSL.scala index b25f5f7eb5..5b4881679c 100644 --- a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/BsonDSL.scala +++ b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/BsonDSL.scala @@ -24,11 +24,13 @@ import java.util.{Date, UUID} import java.util.regex.Pattern import org.bson.types.ObjectId +import org.joda.time.DateTime object BsonDSL extends JsonDSL { - implicit def objectid2jvalue(oid: ObjectId): JValue = Meta.objectIdAsJValue(oid) - implicit def pattern2jvalue(p: Pattern): JValue = Meta.patternAsJValue(p) - implicit def regex2jvalue(r: Regex): JValue = Meta.patternAsJValue(r.pattern) - implicit def uuid2jvalue(u: UUID): JValue = Meta.uuidAsJValue(u) - implicit def date2jvalue(d: Date)(implicit formats: Formats): JValue = Meta.dateAsJValue(d, formats) + implicit def objectid2jvalue(oid: ObjectId): JValue = JsonObjectId(oid) + implicit def pattern2jvalue(p: Pattern): JValue = JsonRegex(p) + implicit def regex2jvalue(r: Regex): JValue = JsonRegex(r.pattern) + implicit def uuid2jvalue(u: UUID): JValue = JsonUUID(u) + implicit def date2jvalue(d: Date)(implicit formats: Formats): JValue = JsonDate(d) + implicit def datetime2jvalue(d: DateTime)(implicit formats: Formats): JValue = JsonDateTime(d) } diff --git a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/JObjectParser.scala b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/JObjectParser.scala index 18263fcf7f..c44e0fb26e 100644 --- a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/JObjectParser.scala +++ b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/JObjectParser.scala @@ -76,50 +76,42 @@ object JObjectParser extends SimpleInjector { object Parser { def parse(jo: JObject, formats: Formats): DBObject = { - parseObject(jo.obj, formats) + parseObject(jo.obj)(formats) } - private def parseArray(arr: List[JValue], formats: Formats): BasicDBList = { + private def parseArray(arr: List[JValue])(implicit formats: Formats): BasicDBList = { val dbl = new BasicDBList trimArr(arr).foreach { a => a match { - case JObject(JField("$oid", JString(s)) :: Nil) if (ObjectId.isValid(s)) => - dbl.add(new ObjectId(s)) - case JObject(JField("$regex", JString(s)) :: JField("$flags", JInt(f)) :: Nil) => - dbl.add(Pattern.compile(s, f.intValue)) - case JObject(JField("$dt", JString(s)) :: Nil) => - formats.dateFormat.parse(s) foreach { d => dbl.add(d) } - case JObject(JField("$uuid", JString(s)) :: Nil) => - dbl.add(UUID.fromString(s)) - case JArray(arr) => dbl.add(parseArray(arr, formats)) - case JObject(jo) => dbl.add(parseObject(jo, formats)) - case jv: JValue => dbl.add(renderValue(jv, formats)) + case JsonObjectId(objectId) => dbl.add(objectId) + case JsonRegex(regex) => dbl.add(regex) + case JsonUUID(uuid) => dbl.add(uuid) + case JsonDate(date) => dbl.add(date) + case JArray(arr) => dbl.add(parseArray(arr)) + case JObject(jo) => dbl.add(parseObject(jo)) + case jv: JValue => dbl.add(renderValue(jv)) } } dbl } - private def parseObject(obj: List[JField], formats: Formats): BasicDBObject = { + private def parseObject(obj: List[JField])(implicit formats: Formats): BasicDBObject = { val dbo = new BasicDBObject trimObj(obj).foreach { jf => jf.value match { - case JObject(JField("$oid", JString(s)) :: Nil) if (ObjectId.isValid(s)) => - dbo.put(jf.name, new ObjectId(s)) - case JObject(JField("$regex", JString(s)) :: JField("$flags", JInt(f)) :: Nil) => - dbo.put(jf.name, Pattern.compile(s, f.intValue)) - case JObject(JField("$dt", JString(s)) :: Nil) => - formats.dateFormat.parse(s) foreach { d => dbo.put(jf.name, d) } - case JObject(JField("$uuid", JString(s)) :: Nil) => - dbo.put(jf.name, UUID.fromString(s)) - case JArray(arr) => dbo.put(jf.name, parseArray(arr, formats)) - case JObject(jo) => dbo.put(jf.name, parseObject(jo, formats)) - case jv: JValue => dbo.put(jf.name, renderValue(jv, formats)) + case JsonObjectId(objectId) => dbo.put(jf.name, objectId) + case JsonRegex(regex) => dbo.put(jf.name, regex) + case JsonUUID(uuid) => dbo.put(jf.name, uuid) + case JsonDate(date) => dbo.put(jf.name, date) + case JArray(arr) => dbo.put(jf.name, parseArray(arr)) + case JObject(jo) => dbo.put(jf.name, parseObject(jo)) + case jv: JValue => dbo.put(jf.name, renderValue(jv)) } } dbo } - private def renderValue(jv: JValue, formats: Formats): Object = jv match { + private def renderValue(jv: JValue)(implicit formats: Formats): Object = jv match { case JBool(b) => java.lang.Boolean.valueOf(b) case JInt(n) => renderInteger(n) case JDouble(n) => new java.lang.Double(n) diff --git a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/JsonExtractors.scala b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/JsonExtractors.scala new file mode 100644 index 0000000000..3a4a7c299d --- /dev/null +++ b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/JsonExtractors.scala @@ -0,0 +1,107 @@ +/** + * Copyright 2014 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 mongodb + +import json._ +import util.Helpers.tryo +import JsonDSL._ + +import java.util.{Date, UUID} +import java.util.regex.Pattern + +import org.bson.types.ObjectId +import org.joda.time.DateTime + +object JsonObjectId { + def unapply(json: JValue): Option[ObjectId] = { + json match { + case JObject(JField("$oid", JString(objectIdString)) :: Nil) if ObjectId.isValid(objectIdString) => + Some(new ObjectId(objectIdString)) + case _ => + None + } + } + + def apply(objectId: ObjectId): JValue = ("$oid" -> objectId.toString) + + def asJValue(objectId: ObjectId, formats: Formats): JValue = + if (isObjectIdSerializerUsed(formats)) + apply(objectId) + else + JString(objectId.toString) + + /** + * Check to see if the ObjectIdSerializer is being used. + */ + private def isObjectIdSerializerUsed(formats: Formats): Boolean = + formats.customSerializers.exists(_.getClass == objectIdSerializerClass) + + private val objectIdSerializerClass = classOf[net.liftweb.mongodb.ObjectIdSerializer] +} + +object JsonRegex { + def unapply(json: JValue): Option[Pattern] = { + json match { + case JObject(JField("$regex", JString(regex)) :: JField("$flags", JInt(f)) :: Nil) => + Some(Pattern.compile(regex, f.intValue)) + case _ => + None + } + } + + def apply(p: Pattern): JValue = ("$regex" -> p.pattern) ~ ("$flags" -> p.flags) +} + +object JsonUUID { + def unapply(json: JValue): Option[UUID] = { + json match { + case JObject(JField("$uuid", JString(s)) :: Nil) => + tryo(UUID.fromString(s)) + case _ => + None + } + } + + def apply(uuid: UUID): JValue = ("$uuid" -> uuid.toString) +} + +object JsonDate { + def unapply(json: JValue)(implicit formats: Formats): Option[Date] = { + json match { + case JObject(JField("$dt", JString(s)) :: Nil) => + formats.dateFormat.parse(s) + case _ => + None + } + } + + def apply(dt: Date)(implicit formats: Formats): JValue = ("$dt" -> formats.dateFormat.format(dt)) +} + +object JsonDateTime { + def unapply(json: JValue)(implicit formats: Formats): Option[DateTime] = { + json match { + case JObject(JField("$dt", JString(s)) :: Nil) => + formats.dateFormat.parse(s).map(dt => new DateTime(dt)) + case _ => + None + } + } + + def apply(dt: DateTime)(implicit formats: Formats): JValue = ("$dt" -> formats.dateFormat.format(dt.toDate)) +} diff --git a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/Meta.scala b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/Meta.scala index 98e3f08963..d005c79519 100644 --- a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/Meta.scala +++ b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/Meta.scala @@ -81,9 +81,9 @@ private[mongodb] object Meta { def datetype_?(clazz: Class[_]) = datetypes contains clazz def datetype2jvalue(a: Any)(implicit formats: Formats) = a match { - case x: Calendar => dateAsJValue(x.getTime, formats) - case x: Date => dateAsJValue(x, formats) - case x: DateTime => dateAsJValue(x.toDate, formats) + case x: Calendar => JsonDate(x.getTime)(formats) + case x: Date => JsonDate(x)(formats) + case x: DateTime => JsonDateTime(x)(formats) } def datetype2dbovalue(a: Any) = a match { @@ -104,31 +104,27 @@ private[mongodb] object Meta { * Definitive place for JValue conversion of mongo types */ def mongotype2jvalue(a: Any)(implicit formats: Formats) = a match { - case x: ObjectId => objectIdAsJValue(x, formats) - case x: Pattern => patternAsJValue(x) - case x: UUID => uuidAsJValue(x) + case x: ObjectId => JsonObjectId.asJValue(x, formats) + case x: Pattern => JsonRegex(x) + case x: UUID => JsonUUID(x) case x: DBRef => sys.error("DBRefs are not supported.") case _ => sys.error("not a mongotype " + a.asInstanceOf[AnyRef].getClass) } } + @deprecated("use JsonDate.apply", "2.6") def dateAsJValue(d: Date, formats: Formats): JValue = ("$dt" -> formats.dateFormat.format(d)) - def objectIdAsJValue(oid: ObjectId): JValue = ("$oid" -> oid.toString) - def patternAsJValue(p: Pattern): JValue = ("$regex" -> p.pattern) ~ ("$flags" -> p.flags) - def uuidAsJValue(u: UUID): JValue = ("$uuid" -> u.toString) - + @deprecated("use JsonObjectId.apply", "2.6") + def objectIdAsJValue(oid: ObjectId): JValue = JsonObjectId(oid) + @deprecated("use JsonRegex.apply", "2.6") + def patternAsJValue(p: Pattern): JValue = JsonRegex(p) + @deprecated("use JsonUUID.apply", "2.6") + def uuidAsJValue(u: UUID): JValue = JsonUUID(u) + + @deprecated("use JsonObjectId.asJValue", "2.6") def objectIdAsJValue(oid: ObjectId, formats: Formats): JValue = - if (isObjectIdSerializerUsed(formats)) - objectIdAsJValue(oid) - else - JString(oid.toString) + JsonObjectId.asJValue(oid, formats) - /* - * Check to see if the ObjectIdSerializer is being used. - */ - private def isObjectIdSerializerUsed(formats: Formats): Boolean = - formats.customSerializers.exists(_.getClass == objectIdSerializerClass) - private val objectIdSerializerClass = classOf[net.liftweb.mongodb.ObjectIdSerializer] } diff --git a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/Serializers.scala b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/Serializers.scala index 37b102c234..7c1ae6d822 100644 --- a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/Serializers.scala +++ b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/Serializers.scala @@ -1,5 +1,5 @@ /* -* Copyright 2010-2011 WorldWide Conferencing, LLC +* Copyright 2010-2014 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. @@ -11,15 +11,15 @@ * limitations under the License. */ -package net.liftweb -package mongodb +package net.liftweb +package mongodb import json.{Formats, MappingException, Serializer, TypeInfo} import json.JsonAST._ import java.util.{Date, UUID} -import java.util.regex.{Pattern, PatternSyntaxException} +import java.util.regex.Pattern import org.bson.types.ObjectId @@ -36,14 +36,13 @@ class ObjectIdSerializer extends Serializer[ObjectId] { def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), ObjectId] = { case (TypeInfo(ObjectIdClass, _), json) => json match { - case JObject(JField("$oid", JString(s)) :: Nil) if (ObjectId.isValid(s)) => - new ObjectId(s) + case JsonObjectId(objectId) => objectId case x => throw new MappingException("Can't convert " + x + " to ObjectId") } } def serialize(implicit formats: Formats): PartialFunction[Any, JValue] = { - case x: ObjectId => Meta.objectIdAsJValue(x) + case x: ObjectId => JsonObjectId(x) } } @@ -59,14 +58,13 @@ class PatternSerializer extends Serializer[Pattern] { def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), Pattern] = { case (TypeInfo(PatternClass, _), json) => json match { - case JObject(JField("$regex", JString(s)) :: JField("$flags", JInt(f)) :: Nil) => - Pattern.compile(s, f.intValue) + case JsonRegex(regex) => regex case x => throw new MappingException("Can't convert " + x + " to Pattern") } } def serialize(implicit formats: Formats): PartialFunction[Any, JValue] = { - case x: Pattern => Meta.patternAsJValue(x) + case x: Pattern => JsonRegex(x) } } @@ -81,14 +79,13 @@ class DateSerializer extends Serializer[Date] { def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), Date] = { case (TypeInfo(DateClass, _), json) => json match { - case JObject(JField("$dt", JString(s)) :: Nil) => - format.dateFormat.parse(s).getOrElse(throw new MappingException("Can't parse "+ s + " to Date")) + case JsonDate(dt) => dt case x => throw new MappingException("Can't convert " + x + " to Date") } } def serialize(implicit format: Formats): PartialFunction[Any, JValue] = { - case x: Date => Meta.dateAsJValue(x, format) + case x: Date => JsonDate(x) } } @@ -103,14 +100,13 @@ class DateTimeSerializer extends Serializer[DateTime] { def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), DateTime] = { case (TypeInfo(DateTimeClass, _), json) => json match { - case JObject(JField("$dt", JString(s)) :: Nil) => - new DateTime(format.dateFormat.parse(s).getOrElse(throw new MappingException("Can't parse "+ s + " to DateTime"))) + case JsonDateTime(dt) => dt case x => throw new MappingException("Can't convert " + x + " to Date") } } - def serialize(implicit format: Formats): PartialFunction[Any, JValue] = { - case x: DateTime => Meta.dateAsJValue(x.toDate, format) + def serialize(implicit format: Formats): PartialFunction[Any, JValue] = { + case x: DateTime => JsonDateTime(x) } } @@ -125,13 +121,13 @@ class UUIDSerializer extends Serializer[UUID] { def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), UUID] = { case (TypeInfo(UUIDClass, _), json) => json match { - case JObject(JField("$uuid", JString(s)) :: Nil) => UUID.fromString(s) - case x => throw new MappingException("Can't convert " + x + " to Date") + case JsonUUID(uuid) => uuid + case x => throw new MappingException("Can't convert " + x + " to UUID") } } def serialize(implicit format: Formats): PartialFunction[Any, JValue] = { - case x: UUID => Meta.uuidAsJValue(x) + case x: UUID => JsonUUID(x) } } diff --git a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/BsonDSLSpec.scala b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/BsonDSLSpec.scala index d2230093c4..b2191cbf06 100644 --- a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/BsonDSLSpec.scala +++ b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/BsonDSLSpec.scala @@ -27,6 +27,7 @@ import java.util.{Date, UUID} import java.util.regex.Pattern import org.bson.types.ObjectId +import org.joda.time.DateTime import org.specs2.mutable.Specification import com.mongodb.{BasicDBList, DBObject} @@ -122,5 +123,24 @@ object BsonDSLSpec extends Specification { dateList2 must_== dateList } + + "Convert DateTime properly" in { + implicit val formats = DefaultFormats.lossless + val dt: DateTime = new DateTime + val qry: JObject = ("now" -> dt) + val dbo: DBObject = JObjectParser.parse(qry) + + new DateTime(dbo.get("now")) must_== dt + } + + "Convert List[DateTime] properly" in { + implicit val formats = DefaultFormats.lossless + val dateList = new DateTime :: new DateTime :: new DateTime :: Nil + val qry: JObject = ("dts" -> dateList) + val dbo: DBObject = JObjectParser.parse(qry) + val dateList2: List[DateTime] = dbo.get("dts").asInstanceOf[BasicDBList].toList.map(_.asInstanceOf[Date]).map(d => new DateTime(d)) + + dateList2 must_== dateList + } } } From 0ab99da262af38f46bb5ae4fc6a0dcd5939e9cc9 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Tue, 3 Jun 2014 12:42:12 -0400 Subject: [PATCH 0940/1949] Separate target directory for pre-2.11 project. sbt 0.13.5 barfs if this isn't the case, and it's probably a good idea anyway. --- project/Build.scala | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index 09439b54ff..319e49f3c2 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -35,11 +35,9 @@ object BuildDef extends Build { liftProject("lift-framework-pre-211", file(".")) .aggregate(liftProjects ++ pre_211_project : _*) .settings(aggregatedSetting(sources in(Compile, doc)), - aggregatedSetting(dependencyClasspath in(Compile, doc)), - publishArtifact := false) - - - + aggregatedSetting(dependencyClasspath in(Compile, doc)), + publishArtifact := false, + target <<= baseDirectory / "target-pre-211") // Core Projects // ------------- From 5470ddd133a13bed36a43693ce3450f059349e5d Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Fri, 6 Jun 2014 21:18:05 -0400 Subject: [PATCH 0941/1949] Fix dependencies for 2.11.1. --- project/Build.scala | 4 ++-- project/Dependencies.scala | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index 319e49f3c2..fc91ee1e3a 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -51,7 +51,7 @@ object BuildDef extends Build { .settings(description := "Common Libraties and Utilities", libraryDependencies ++= Seq(slf4j_api, logback, slf4j_log4j12), libraryDependencies <++= scalaVersion { - case "2.11.0" => Seq(scala_xml, scala_parser) + case "2.11.0" | "2.11.1" => Seq(scala_xml, scala_parser) case _ => Seq() } ) @@ -68,7 +68,7 @@ object BuildDef extends Build { parallelExecution in Test := false, libraryDependencies <++= scalaVersion { sv => Seq(scalatest(sv), junit) }, libraryDependencies <++= scalaVersion { - case "2.11.0" => Seq(scala_xml, scala_parser) + case "2.11.0" | "2.11.1" => Seq(scala_xml, scala_parser) case _ => Seq() } ) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index bc74961716..f712a916cf 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -25,16 +25,16 @@ object Dependencies { lazy val CVMapping2911 = crossMapped("2.9.1-1" -> "2.9.1") lazy val CVMapping29 = crossMapped("2.10.4" -> "2.10", "2.10.0" -> "2.10", "2.9.1-1" -> "2.9.2", "2.9.1" -> "2.9.2") - lazy val CVMappingAll = crossMapped("2.11.0" -> "2.11", "2.10.4" -> "2.10", "2.10.0" -> "2.10", "2.9.2" -> "2.9.1", "2.9.1-1" -> "2.9.1") - lazy val CVMappingScalaz = crossMapped("2.11.0" -> "2.11", "2.10.4" -> "2.10", "2.10.0" -> "2.10", "2.9.1-1" -> "2.9.2", "2.9.1" -> "2.9.2") + lazy val CVMappingAll = crossMapped("2.11.1" -> "2.11", "2.11.0" -> "2.11", "2.10.4" -> "2.10", "2.10.0" -> "2.10", "2.9.2" -> "2.9.1", "2.9.1-1" -> "2.9.1") + lazy val CVMappingScalaz = crossMapped("2.11.1" ->"2.11", "2.11.0" -> "2.11", "2.10.4" -> "2.10", "2.10.0" -> "2.10", "2.9.1-1" -> "2.9.2", "2.9.1" -> "2.9.2") lazy val slf4jVersion = "1.7.2" lazy val scalazGroup = defaultOrMapped("org.scalaz") lazy val scalazVersion = defaultOrMapped("6.0.4") - lazy val scalaz7Version = defaultOrMapped("7.0.0", "2.11.0" -> "7.0.6") - lazy val specs2Version = defaultOrMapped("1.12.3", "2.11.0" -> "2.3.11") - lazy val scalatestVersion = defaultOrMapped("1.9.1", "2.11.0" -> "2.1.3") + lazy val scalaz7Version = defaultOrMapped("7.0.0", "2.11.0" -> "7.0.6", "2.11.1" -> "7.0.6") + lazy val specs2Version = defaultOrMapped("1.12.3", "2.11.0" -> "2.3.11", "2.11.1" -> "2.3.11") + lazy val scalatestVersion = defaultOrMapped("1.9.1", "2.11.0" -> "2.1.3", "2.11.1" -> "2.1.3") // Compile scope: // Scope available in all classpath, transitive by default. From 955730856ccdcfe7ea7e043eb97952e11ba9d3fe Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Fri, 6 Jun 2014 21:18:34 -0400 Subject: [PATCH 0942/1949] Fix lift-mongodb-record test compilation in 2.11. There was a particularly strange ambiguous overload issue, whose fix is equally odd, but works. --- .../mongodb/record/MongoFieldSpec.scala | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala index d2374f32e7..2de7ff7e90 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala @@ -254,19 +254,23 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundExample } "ObjectIdField" should { - val rec = MongoFieldTypeTestRecord.createRecord - val oid = ObjectId.get - val oid2 = ObjectId.get - passBasicTests(oid, oid2, rec.mandatoryObjectIdField, Full(rec.legacyOptionalObjectIdField), false) - passConversionTests( - oid, - rec.mandatoryObjectIdField, - JsObj(("$oid", oid.toString)), - JObject(List(JField("$oid", JString(oid.toString)))), - Full() - ) - rec.mandatoryObjectIdField(oid) - oid.getDate mustEqual rec.mandatoryObjectIdField.createdAt + // The extra `in` here is required for compilation, or we get a strange ambiguous overload warning. + "work and provide the appropriate date" in { + val rec = MongoFieldTypeTestRecord.createRecord + val oid = ObjectId.get + val oid2 = ObjectId.get + passBasicTests(oid, oid2, rec.mandatoryObjectIdField, Full(rec.legacyOptionalObjectIdField), false) + passConversionTests( + oid, + rec.mandatoryObjectIdField, + JsObj(("$oid", oid.toString)), + JObject(List(JField("$oid", JString(oid.toString)))), + Full() + ) + rec.mandatoryObjectIdField(oid) + + oid.getDate must_== rec.mandatoryObjectIdField.createdAt + } } "PatternField" should { From f3d85add040ef7eb3371de6b677e9fc2b1ed5685 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Fri, 6 Jun 2014 21:31:55 -0400 Subject: [PATCH 0943/1949] Fix lift-util test compilation in 2.11. Between writing the 2.11 fixes and merging to master, a PR landed that added a spec to MailerSpec; this spec was not written to have a MatchResult as its return type, so things blew up. --- .../src/test/scala/net/liftweb/util/MailerSpec.scala | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/core/util/src/test/scala/net/liftweb/util/MailerSpec.scala b/core/util/src/test/scala/net/liftweb/util/MailerSpec.scala index fb69410082..2421ef2d3d 100644 --- a/core/util/src/test/scala/net/liftweb/util/MailerSpec.scala +++ b/core/util/src/test/scala/net/liftweb/util/MailerSpec.scala @@ -115,20 +115,14 @@ object MailerSpec extends Specification { ) } - msg.getContent match { + msg.getContent must beLike { case mp: MimeMultipart => mp.getContentType.substring(0, 21) must_== "multipart/alternative" - mp.getBodyPart(0).getContent match { + mp.getBodyPart(0).getContent must beLike { case mp2: MimeMultipart => mp2.getContentType.substring(0, 15) must_== "multipart/mixed" - - case somethingElse => - failure("The message's multipart's first body part wasn't a MimeMultipart") } - - case somethingElse => - failure("The complex message has content type of " + somethingElse.getClass.getName) } } } From a6d092344ba313a09e5e8c8a2cfed05e53314937 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Fri, 6 Jun 2014 22:00:33 -0400 Subject: [PATCH 0944/1949] ResponseShortcutException redirects in comet. Comets now properly handle *only* ResponseShortcutExceptions that do redirection. Specs added accordingly. --- .../scala/net/liftweb/http/CometActor.scala | 15 +++ .../net/liftweb/http/CometActorSpec.scala | 96 +++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 web/webkit/src/test/scala/net/liftweb/http/CometActorSpec.scala diff --git a/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala b/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala index 22b81012f2..97424b7be4 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala @@ -792,6 +792,21 @@ trait CometActor extends LiftActor with LiftCometActor with BindHelpers { protected implicit def nodeSeqFuncToBoxNodeSeq(f: NodeSeq => NodeSeq): Box[NodeSeq] = Full(f(defaultHtml)) + /** + * By default, `CometActor` handles `RedirectShortcutException`, which is + * used to handle many types of redirects in Lift. If you override this + * `PartialFunction` to do your own exception handling and want redirects + * from e.g. `S.redirectTo` to continue working correctly, make sure you + * chain back to this implementation. + */ + override def exceptionHandler : PartialFunction[Throwable, Unit] = { + case ResponseShortcutException(_, Full(redirectUri), _) => + partialUpdate(RedirectTo(redirectUri)) + + case other if super.exceptionHandler.isDefinedAt(other) => + super.exceptionHandler(other) + } + /** * Handle messages sent to this Actor before the */ diff --git a/web/webkit/src/test/scala/net/liftweb/http/CometActorSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/CometActorSpec.scala new file mode 100644 index 0000000000..6645975bfa --- /dev/null +++ b/web/webkit/src/test/scala/net/liftweb/http/CometActorSpec.scala @@ -0,0 +1,96 @@ +/* + * Copyright 2010-2014 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 scala.xml.NodeSeq + +import org.specs2.mutable.Specification + +import actor.LAScheduler +import common._ +import js.JsCmds._ + +object CometActorSpec extends Specification { + private case object TestMessage + + private val testSession = new LiftSession("Test Session", "", Empty) + + private class SpecCometActor extends CometActor { + var receivedMessages = List[Any]() + + def render = NodeSeq.Empty + override def theSession = testSession + + override def !(msg: Any) = { + receivedMessages ::= msg + + LAScheduler.onSameThread = true + + super.!(msg) + + LAScheduler.onSameThread = false + } + } + + "A CometActor" should { + class RedirectingComet extends SpecCometActor { + override def lowPriority = { + case TestMessage => + S.redirectTo("place") + } + } + + "redirect the user when a ResponseShortcutException with redirect occurs" in { + val comet = new RedirectingComet + + comet ! TestMessage + + comet.receivedMessages.exists { + case PartialUpdateMsg(update) if update() == RedirectTo("place") => + true + case _ => + false + } must beTrue + } + + class FunctionRedirectingComet extends SpecCometActor { + override def lowPriority = { + case TestMessage => + S.redirectTo("place", () => "do stuff") + } + } + + "redirect the user with a function when a ResponseShortcutException with redirect+function occurs" in { + val comet = new FunctionRedirectingComet + + comet ! TestMessage + + val matchingMessage = + comet.receivedMessages.collect { + case PartialUpdateMsg(update) => + update() + } + + matchingMessage must beLike { + case List(RedirectTo(redirectUri)) => + redirectUri must startWith("place") + redirectUri must beMatching("^[^?]+\\?F[^=]+=_$".r) + } + } + } +} From 82c56aecf0b5b4c05d6be2808f4b5acdff9430bc Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Fri, 6 Jun 2014 22:19:37 -0400 Subject: [PATCH 0945/1949] bodyAsXml/bodyAsJson gain "forced" prefix. We do this to more clearly indicate that you are bypassing built-in safeguards against incorrect Content-Types when you use these methods. --- .../src/main/scala/net/liftweb/http/Req.scala | 12 ++++++------ .../test/scala/net/liftweb/http/ReqSpec.scala | 16 ++++++++-------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/Req.scala b/web/webkit/src/main/scala/net/liftweb/http/Req.scala index 6766e33829..a77d1e53ab 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Req.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Req.scala @@ -991,13 +991,13 @@ class Req(val path: ParsePath, * there was an error parsing the JSON. * * If you want to forcibly evaluate the request body as JSON, ignoring - * content type, see bodyAsJson. + * content type, see `forcedBodyAsJson`. */ lazy val json: Box[JsonAST.JValue] = { if (!json_?) { Failure("Cannot parse non-JSON request as JSON; please check content-type.") } else { - bodyAsJson + forcedBodyAsJson } } @@ -1005,7 +1005,7 @@ class Req(val path: ParsePath, * Forcibly tries to parse the request body as JSON. Does not perform any * content type checks, unlike the json method. */ - lazy val bodyAsJson: Box[JsonAST.JValue] = { + lazy val forcedBodyAsJson: Box[JsonAST.JValue] = { try { import java.io._ @@ -1050,13 +1050,13 @@ class Req(val path: ParsePath, * there was an error parsing the XML. * * If you want to forcibly evaluate the request body as XML, ignoring - * content type, see bodyAsXml. + * content type, see `forcedBodyAsXml`. */ lazy val xml: Box[Elem] = { if (!xml_?) { Failure("Cannot parse non-XML request as XML; please check content-type.") } else { - bodyAsXml + forcedBodyAsXml } } @@ -1064,7 +1064,7 @@ class Req(val path: ParsePath, * Forcibly tries to parse the request body as XML. Does not perform any * content type checks, unlike the xml method. */ - lazy val bodyAsXml: Box[Elem] = { + lazy val forcedBodyAsXml: Box[Elem] = { try { import java.io._ body.map(b => XML.load(new ByteArrayInputStream(b))) diff --git a/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala index e37c78e14a..c8390409df 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala @@ -156,17 +156,17 @@ object ReqSpec extends Specification with XmlMatchers with Mockito { } } - "when forcing a request body JSON parse with bodyAsJson" in { + "when forcing a request body JSON parse with forcedBodyAsJson" in { "with an invalid Content-Type should return the result of parsing the JSON" in new mockJsonReq { - req("text/plain").bodyAsJson should_== Full(parsedJson) + req("text/plain").forcedBodyAsJson should_== Full(parsedJson) } "with an application/json Content-Type should return the result of parsing the JSON" in new mockJsonReq { - req("application/json").bodyAsJson should_== Full(parsedJson) + req("application/json").forcedBodyAsJson should_== Full(parsedJson) } "with a text/json Content-Type should return the result of parsing the JSON" in new mockJsonReq { - req("text/json").bodyAsJson should_== Full(parsedJson) + req("text/json").forcedBodyAsJson should_== Full(parsedJson) } } @@ -184,17 +184,17 @@ object ReqSpec extends Specification with XmlMatchers with Mockito { } } - "when forcing a request body XML parse with bodyAsXml" in { + "when forcing a request body XML parse with forcedBodyAsXml" in { "with an invalid Content-Type should return the result of parsing the JSON" in new mockXmlReq { - req("text/plain").bodyAsXml should_== Full(parsedXml) + req("text/plain").forcedBodyAsXml should_== Full(parsedXml) } "with an application/json Content-Type should return the result of parsing the JSON" in new mockXmlReq { - req("application/xml").bodyAsXml should_== Full(parsedXml) + req("application/xml").forcedBodyAsXml should_== Full(parsedXml) } "with a text/json Content-Type should return the result of parsing the JSON" in new mockXmlReq { - req("text/xml").bodyAsXml should_== Full(parsedXml) + req("text/xml").forcedBodyAsXml should_== Full(parsedXml) } } } From cddea9fd755d9f49528223d234eeb0ada08d807d Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Fri, 6 Jun 2014 22:20:23 -0400 Subject: [PATCH 0946/1949] Specs for parse failures in XML/JSON bodies. These specs clarify that we expect Failures when the content types are either correct or ignored but the contents of the request bodies are not valid XML or JSON bodies, depending on which method is being called. --- .../test/scala/net/liftweb/http/ReqSpec.scala | 36 ++++++++++++++----- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala index c8390409df..32000fe55c 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala @@ -19,6 +19,8 @@ package http import java.io.ByteArrayInputStream +import scala.xml.XML + import org.specs2.matcher.XmlMatchers import org.mockito.Mockito._ @@ -29,6 +31,8 @@ import org.specs2.specification.Scope import common._ import json.JsonDSL._ +import json.JsonParser +import util.Helpers.tryo import provider._ @@ -121,21 +125,18 @@ object ReqSpec extends Specification with XmlMatchers with Mockito { } } - trait mockJsonReq extends mockReq { - val testJson = """{ "booyan": "shazam", "booyak": 5, "bazam": 2.5 }""" - val parsedJson = - ("booyan" -> "shazam") ~ - ("booyak" -> 5) ~ - ("bazam" -> 2.5) + class mockJsonReq(jsonString: String = """{ "booyan": "shazam", "booyak": 5, "bazam": 2.5 }""") extends mockReq { + val testJson = jsonString + val parsedJson = tryo(JsonParser.parse(jsonString)) openOr json.JsonAST.JNothing def bodyBytes = { testJson.getBytes("UTF-8") } } - trait mockXmlReq extends mockReq { - val testXml = """Oh yeah""" - val parsedXml = Oh yeah + class mockXmlReq(xmlString: String = """Oh yeah""") extends mockReq { + val testXml = xmlString + val parsedXml = tryo(XML.loadString(xmlString)) openOr "totally failed" def bodyBytes = { testXml.getBytes("UTF-8") @@ -154,6 +155,10 @@ object ReqSpec extends Specification with XmlMatchers with Mockito { "with a text/json Content-Type should return the result of parsing the JSON" in new mockJsonReq { req("text/json").json should_== Full(parsedJson) } + + "with invalid JSON and a text/json Content-Type should return a Failure" in new mockJsonReq("epic fail") { + req("text/json").json should beAnInstanceOf[Failure] + } } "when forcing a request body JSON parse with forcedBodyAsJson" in { @@ -168,6 +173,10 @@ object ReqSpec extends Specification with XmlMatchers with Mockito { "with a text/json Content-Type should return the result of parsing the JSON" in new mockJsonReq { req("text/json").forcedBodyAsJson should_== Full(parsedJson) } + + "with invalid JSON should return a Failure" in new mockJsonReq("epic fail") { + req("text/json").json should beAnInstanceOf[Failure] + } } "when trying to XML parse the request body" in { @@ -182,6 +191,10 @@ object ReqSpec extends Specification with XmlMatchers with Mockito { "with a text/xml Content-Type should return the result of parsing the JSON" in new mockXmlReq { req("text/xml").xml should_== Full(parsedXml) } + + "with invalid XML and a text/xml Content-Type should return a Failure" in new mockXmlReq("epic fail") { + req("text/xml").forcedBodyAsXml should beAnInstanceOf[Failure] + } } "when forcing a request body XML parse with forcedBodyAsXml" in { @@ -196,6 +209,11 @@ object ReqSpec extends Specification with XmlMatchers with Mockito { "with a text/json Content-Type should return the result of parsing the JSON" in new mockXmlReq { req("text/xml").forcedBodyAsXml should_== Full(parsedXml) } + + "with invalid XML should return a Failure" in new mockXmlReq("epic fail") { + req("text/palin").forcedBodyAsXml should beAnInstanceOf[Failure] + req("text/xml").forcedBodyAsXml should beAnInstanceOf[Failure] + } } } } From 31ea0b0d24ab921111c5fb4bb66afa7ca2a5244b Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Tue, 20 May 2014 16:21:35 -0400 Subject: [PATCH 0947/1949] Pull in fix for #1513 from 57963c4. Failed to notice this when I was pulling in the updated JS. --- web/webkit/src/main/resources/toserve/lift.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/webkit/src/main/resources/toserve/lift.js b/web/webkit/src/main/resources/toserve/lift.js index b7b5e3620b..332297262f 100644 --- a/web/webkit/src/main/resources/toserve/lift.js +++ b/web/webkit/src/main/resources/toserve/lift.js @@ -441,13 +441,13 @@ } }; - if (evt.done) { + if (evt.done != null) { doneMsg(); } - else if (evt.success) { + else if (evt.success != null) { successMsg(evt.success); } - else if (evt.failure) { + else if (evt.failure != null) { failMsg(evt.failure); } }; From a513ab85a5fe8517d8a3173d841b9dc5d49abcac Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 8 Jun 2014 23:40:17 -0400 Subject: [PATCH 0948/1949] Make JField a proper case class again. Notably it does NOT extend JValue, but it is not simply a tuple either. Also sundry compilation fixes to make everything work well and proper. --- .../scala/net/liftweb/json/scalaz/Base.scala | 2 +- .../scala/net/liftweb/json/scalaz/Base.scala | 2 +- .../main/scala/net/liftweb/json/Diff.scala | 6 +- .../scala/net/liftweb/json/Extraction.scala | 14 ++--- .../main/scala/net/liftweb/json/JsonAST.scala | 57 +++++++++---------- .../scala/net/liftweb/json/JsonParser.scala | 12 ++-- .../main/scala/net/liftweb/json/Merge.scala | 6 +- .../src/main/scala/net/liftweb/json/Xml.scala | 6 +- .../scala/net/liftweb/json/Examples.scala | 4 +- .../scala/net/liftweb/json/JsonAstSpec.scala | 2 +- .../scala/net/liftweb/json/XmlExamples.scala | 6 +- .../scala/net/liftweb/mapper/MetaMapper.scala | 9 +-- .../net/liftweb/record/RecordHelpers.scala | 1 - .../net/liftweb/http/rest/RestHelper.scala | 8 --- 14 files changed, 62 insertions(+), 73 deletions(-) diff --git a/core/json-scalaz/src/main/scala/net/liftweb/json/scalaz/Base.scala b/core/json-scalaz/src/main/scala/net/liftweb/json/scalaz/Base.scala index 784f8cbe1f..a8019d325b 100644 --- a/core/json-scalaz/src/main/scala/net/liftweb/json/scalaz/Base.scala +++ b/core/json-scalaz/src/main/scala/net/liftweb/json/scalaz/Base.scala @@ -111,6 +111,6 @@ trait Base { this: Types => } } implicit def mapJSONW[A: JSONW]: JSONW[Map[String, A]] = new JSONW[Map[String, A]] { - def write(values: Map[String, A]) = JObject(values.map { case (k, v) => JField(k, toJSON(v)) }(breakOut)) + def write(values: Map[String, A]) = JObject(values.map { case (k, v) => JField(k, toJSON(v)) }(breakOut): _*) } } diff --git a/core/json-scalaz7/src/main/scala/net/liftweb/json/scalaz/Base.scala b/core/json-scalaz7/src/main/scala/net/liftweb/json/scalaz/Base.scala index d252ae58f1..5441c92c64 100644 --- a/core/json-scalaz7/src/main/scala/net/liftweb/json/scalaz/Base.scala +++ b/core/json-scalaz7/src/main/scala/net/liftweb/json/scalaz/Base.scala @@ -115,6 +115,6 @@ trait Base { this: Types => } } implicit def mapJSONW[A: JSONW]: JSONW[Map[String, A]] = new JSONW[Map[String, A]] { - def write(values: Map[String, A]) = JObject(values.map { case (k, v) => JField(k, toJSON(v)) }(breakOut)) + def write(values: Map[String, A]) = JObject(values.map { case (k, v) => JField(k, toJSON(v)) }(breakOut): _*) } } diff --git a/core/json/src/main/scala/net/liftweb/json/Diff.scala b/core/json/src/main/scala/net/liftweb/json/Diff.scala index 0284826338..91ebabcf88 100644 --- a/core/json/src/main/scala/net/liftweb/json/Diff.scala +++ b/core/json/src/main/scala/net/liftweb/json/Diff.scala @@ -34,7 +34,7 @@ case class Diff(changed: JValue, added: JValue, deleted: JValue) { private[json] def toField(name: String): Diff = { def applyTo(x: JValue) = x match { case JNothing => JNothing - case _ => JObject((name, x)) + case _ => JObject(JField(name, x)) } Diff(applyTo(changed), applyTo(added), applyTo(deleted)) } @@ -66,9 +66,9 @@ object Diff { private def diffFields(vs1: List[JField], vs2: List[JField]) = { def diffRec(xleft: List[JField], yleft: List[JField]): Diff = xleft match { case Nil => Diff(JNothing, if (yleft.isEmpty) JNothing else JObject(yleft), JNothing) - case x :: xs => yleft find (_._1 == x._1) match { + case x :: xs => yleft find (_.name == x.name) match { case Some(y) => - val Diff(c1, a1, d1) = diff(x._2, y._2).toField(y._1) + val Diff(c1, a1, d1) = diff(x.value, y.value).toField(y.name) val Diff(c2, a2, d2) = diffRec(xs, yleft filterNot (_ == y)) Diff(c1 merge c2, a1 merge a2, d1 merge d2) case None => diff --git a/core/json/src/main/scala/net/liftweb/json/Extraction.scala b/core/json/src/main/scala/net/liftweb/json/Extraction.scala index ac0782e65b..6c93ca94ac 100644 --- a/core/json/src/main/scala/net/liftweb/json/Extraction.scala +++ b/core/json/src/main/scala/net/liftweb/json/Extraction.scala @@ -101,7 +101,7 @@ object Extraction { .getOrElse(JField(n, JNothing)) } } getOrElse Nil - val uniqueFields = fields filterNot (f => args.find(_._1 == f._1).isDefined) + val uniqueFields = fields filterNot (f => args.find(_.name == f.name).isDefined) mkObject(x.getClass, uniqueFields ++ args) } } @@ -120,7 +120,7 @@ object Extraction { case JDouble(num) => Map(path -> num.toString) case JInt(num) => Map(path -> num.toString) case JBool(value) => Map(path -> value.toString) - case JObject(obj) => obj.foldLeft(Map[String, String]()) { case (map, (name, value)) => + case JObject(obj) => obj.foldLeft(Map[String, String]()) { case (map, JField(name, value)) => map ++ flatten0(path + "." + escapePath(name), value) } case JArray(arr) => arr.length match { @@ -209,7 +209,7 @@ object Extraction { if (constructor.choices.size == 1) constructor.choices.head // optimized common case else { val argNames = json match { - case JObject(fs) => fs.map(_._1) + case JObject(fs) => fs.map(_.name) case x => Nil } constructor.bestMatching(argNames) @@ -267,7 +267,7 @@ object Extraction { } def mkWithTypeHint(typeHint: String, fields: List[JField], typeInfo: TypeInfo) = { - val obj = JObject(fields filterNot (_._1 == formats.typeHintFieldName)) + val obj = JObject(fields filterNot (_.name == formats.typeHintFieldName)) val deserializer = formats.typeHints.deserialize if (!deserializer.isDefinedAt(typeHint, obj)) { val concreteClass = formats.typeHints.classFor(typeHint) getOrElse fail("Do not know how to deserialize '" + typeHint + "'") @@ -290,9 +290,9 @@ object Extraction { def unapply(fs: List[JField]): Option[(String, List[JField])] = if (formats.typeHints == NoTypeHints) None else { - val grouped = fs groupBy (_._1 == formats.typeHintFieldName) + val grouped = fs groupBy (_.name == formats.typeHintFieldName) if (grouped.isDefinedAt(true)) - Some((grouped(true).head._2.values.toString, grouped.get(false).getOrElse(Nil))) + Some((grouped(true).head.value.values.toString, grouped.get(false).getOrElse(Nil))) else None } } @@ -324,7 +324,7 @@ object Extraction { else if (classOf[Seq[_]].isAssignableFrom(c)) newCollection(root, m, a => List(a: _*)) else fail("Expected collection but got " + m + " for class " + c) case Dict(m) => root match { - case JObject(xs) => Map(xs.map(x => (x._1, build(x._2, m))): _*) + case JObject(xs) => Map(xs.map(x => (x.name, build(x.value, m))): _*) case x => fail("Expected object but got " + x) } } diff --git a/core/json/src/main/scala/net/liftweb/json/JsonAST.scala b/core/json/src/main/scala/net/liftweb/json/JsonAST.scala index d9509d9e1a..6f1001757e 100644 --- a/core/json/src/main/scala/net/liftweb/json/JsonAST.scala +++ b/core/json/src/main/scala/net/liftweb/json/JsonAST.scala @@ -55,19 +55,19 @@ object JsonAST { } private def findDirectByName(xs: List[JValue], name: String): List[JValue] = xs.flatMap { - case JObject(l) => l.filter { - case (n, _) if n == name => true - case _ => false - } map (_._2) + case JObject(l) => + l.collect { + case JField(n, value) if n == name => value + } case JArray(l) => findDirectByName(l, name) case _ => Nil } private def findDirect(xs: List[JValue], p: JValue => Boolean): List[JValue] = xs.flatMap { - case JObject(l) => l.filter { - case (n, x) if p(x) => true - case _ => false - } map (_._2) + case JObject(l) => + l.collect { + case JField(n, x) if p(x) => x + } case JArray(l) => findDirect(l, p) case x if p(x) => x :: Nil case _ => Nil @@ -82,9 +82,9 @@ object JsonAST { def \\(nameToFind: String): JValue = { def find(json: JValue): List[JField] = json match { case JObject(l) => l.foldLeft(List[JField]()) { - case (a, (name, value)) => + case (a, JField(name, value)) => if (name == nameToFind) { - a ::: List((name, value)) ::: find(value) + a ::: List(JField(name, value)) ::: find(value) } else { a ::: find(value) } @@ -93,7 +93,7 @@ object JsonAST { case _ => Nil } find(this) match { - case (_, x) :: Nil => x + case JField(_, x) :: Nil => x case xs => JObject(xs) } } @@ -146,7 +146,7 @@ object JsonAST { *
  • @@ -2644,7 +2644,7 @@ for {
     
       @deprecated("Use AFuncHolder.listStrToAF")
       def toLFunc(in: List[String] => Any): AFuncHolder = LFuncHolder(in, Empty)
    - 
    +
       @deprecated("Use AFuncHolder.unitToAF")
       def toNFunc(in: () => Any): AFuncHolder = NFuncHolder(in, Empty)
     
    @@ -2708,17 +2708,17 @@ for {
               case Full(ret) =>
                 ret.fixSessionTime()
               ret
    -          
    +
               case _ =>
                 val ret = LiftSession(httpRequest.session, req.contextPath)
               ret.fixSessionTime()
    -          SessionMaster.addSession(ret, 
    +          SessionMaster.addSession(ret,
                                        req,
    -                                   httpRequest.userAgent, 
    +                                   httpRequest.userAgent,
                                        SessionMaster.getIpFromReq(req))
               ret
             }
    -        
    +
             init(req, ses) {
               doRender(ses)
             }
    @@ -2761,14 +2761,14 @@ for {
       /**
        * Returns all the HTTP parameters having 'n' name
        */
    -  def params(n: String): List[String] = 
    +  def params(n: String): List[String] =
         paramsForComet.get.get(n) getOrElse
         request.flatMap(_.params.get(n)).openOr(Nil)
     
       /**
        * Returns the HTTP parameter having 'n' name
        */
    -  def param(n: String): Box[String] = 
    +  def param(n: String): Box[String] =
         paramsForComet.get.get(n).flatMap(_.headOption) orElse
         request.flatMap(r => Box(r.param(n)))
     
    diff --git a/web/webkit/src/main/scala/net/liftweb/http/js/JsCommands.scala b/web/webkit/src/main/scala/net/liftweb/http/js/JsCommands.scala
    index 92a7a0499b..a398e28de1 100755
    --- a/web/webkit/src/main/scala/net/liftweb/http/js/JsCommands.scala
    +++ b/web/webkit/src/main/scala/net/liftweb/http/js/JsCommands.scala
    @@ -13,9 +13,9 @@
      * See the License for the specific language governing permissions and
      * limitations under the License.
      */
    -package net.liftweb 
    -package http 
    -package js 
    +package net.liftweb
    +package http
    +package js
     
     import scala.xml.{NodeSeq, Group, Unparsed, Elem}
     import net.liftweb.util.Helpers._
    @@ -87,12 +87,12 @@ trait JsObj extends JsExp {
         other match {
           case jsObj: JsObj => {
             import scala.annotation.tailrec
    -        
    +
             @tailrec def test(me: Map[String, JsExp], them: List[(String, JsExp)]): Boolean = {
               them match {
                 case Nil => me.isEmpty
                 case _ if me.isEmpty => false
    -            case (k, v) :: xs => 
    +            case (k, v) :: xs =>
                   me.get(k) match {
                     case None => false
                     case Some(mv) if mv != v => false
    @@ -100,10 +100,10 @@ trait JsObj extends JsExp {
                   }
               }
             }
    -        
    +
             test(Map(props :_*), jsObj.props)
           }
    -      
    +
           case x => super.equals(x)
         }
       }
    @@ -185,12 +185,12 @@ trait JsExp extends HtmlFixer with ToJsCmd {
     
     
       def ~>(right: Box[JsMember]): JsExp = right.dmap(this)(r => ~>(r))
    -  
    +
       /**
        * This exists for backward compatibility reasons for JQueryLeft and JQueryRight
        * which are now deprecated. Use ~> whenever possible as this will be removed soon.
        */
    -  @deprecated
    +  @deprecated("Use `~>` instead")
       def >>(right: JsMember): JsExp = ~>(right)
     
     
    @@ -228,7 +228,7 @@ object JE {
         def apply(d: Double): Num = new Num(d)
         def apply(f: Float): Num = new Num(f)
       }
    -  
    +
       case class Num(n: Number) extends JsExp {
         def toJsCmd = n.toString
       }
    @@ -558,7 +558,7 @@ trait HtmlFixer {
       @deprecated("Use fixHtmlAndJs or fixHtmlFunc")
       protected def fixHtml(uid: String, content: NodeSeq): String = {
         val w = new java.io.StringWriter
    -    
    +
         S.htmlProperties.
         htmlWriter(Group(S.session.
                          map(s =>
    @@ -576,7 +576,7 @@ trait HtmlFixer {
        * then evaluations to Expression.  For use when converting
        * a JsExp that contains HTML.
        */
    -  def fixHtmlFunc(uid: String, content: NodeSeq)(f: String => String) = 
    +  def fixHtmlFunc(uid: String, content: NodeSeq)(f: String => String) =
         fixHtmlAndJs(uid, content) match {
           case (str, Nil) => f(str)
           case (str, cmds) => "((function() {"+cmds.reduceLeft{_ & _}.toJsCmd+" return "+f(str)+";})())"
    @@ -589,7 +589,7 @@ trait HtmlFixer {
        * For use when converting
        * a JsCmd that contains HTML.
        */
    -  def fixHtmlCmdFunc(uid: String, content: NodeSeq)(f: String => String) = 
    +  def fixHtmlCmdFunc(uid: String, content: NodeSeq)(f: String => String) =
         fixHtmlAndJs(uid, content) match {
           case (str, Nil) => f(str)
           case (str, cmds) => f(str)+"; "+cmds.reduceLeft(_ & _).toJsCmd
    @@ -604,7 +604,7 @@ trait HtmlFixer {
         import Helpers._
     
         val w = new java.io.StringWriter
    -    
    +
         val xhtml = S.session.
         map(s =>
           s.fixHtml(s.processSurroundAndInclude("JS SetHTML id: "
    @@ -614,7 +614,7 @@ trait HtmlFixer {
     
         import scala.collection.mutable.ListBuffer
         val lb = new ListBuffer[JsCmd]
    -    
    +
         val revised = ("script" #> ((ns: NodeSeq) => {
           ns match {
             case FindScript(e) => {
    @@ -726,7 +726,7 @@ object JsCmds {
     
       /**
        * Sets the focus on the element denominated by the id
    -   */ 
    +   */
       case class Focus(id: String) extends JsCmd {
         def toJsCmd = "if (document.getElementById(" + id.encJs + ")) {document.getElementById(" + id.encJs + ").focus();};"
       }
    @@ -781,7 +781,7 @@ object JsCmds {
     
       /**
        * Assigns the value of 'right' to the members of the element
    -   * having this 'id', chained by 'then' sequences 
    +   * having this 'id', chained by 'then' sequences
        */
       case class SetElemById(id: String, right: JsExp, then: String*) extends JsCmd {
         def toJsCmd = "if (document.getElementById(" + id.encJs + ")) {document.getElementById(" + id.encJs + ")" + (
    @@ -854,7 +854,7 @@ object JsCmds {
          */
         def apply(where: String, func: () => Unit): RedirectTo =
         S.session match {
    -      case Full(liftSession) => 
    +      case Full(liftSession) =>
             new RedirectTo(liftSession.attachRedirectFunc(where, Full(func)))
           case _ => new RedirectTo(where)
         }
    diff --git a/web/webkit/src/test/scala/net/liftweb/http/SnippetSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/SnippetSpec.scala
    index 223ee83e6e..fd7bf1753d 100644
    --- a/web/webkit/src/test/scala/net/liftweb/http/SnippetSpec.scala
    +++ b/web/webkit/src/test/scala/net/liftweb/http/SnippetSpec.scala
    @@ -32,9 +32,9 @@ object SnippetSpec extends Specification("SnippetSpec Specification") {
                         System.nanoTime, System.nanoTime, false,
                         () => ParamCalcInfo(Nil, Map.empty, Nil, Empty), Map())
     
    -  "LiftSession" should {
    +  "Templates" should {
         "Correctly process lift:content_id" in {
    -      val ret = LiftSession.checkForContentId(
    +      val ret = Templates.checkForContentId(
                                          
                                          
                                          
    @@ -45,7 +45,7 @@ object SnippetSpec extends Specification("SnippetSpec Specification") { } "Correctly process body class" in { - val ret = LiftSession.checkForContentId( + val ret = Templates.checkForContentId(
    mooose dog @@ -58,7 +58,7 @@ object SnippetSpec extends Specification("SnippetSpec Specification") { } "Correctly process l:content_id" in { - val ret = LiftSession.checkForContentId( + val ret = Templates.checkForContentId(
    @@ -75,12 +75,12 @@ object SnippetSpec extends Specification("SnippetSpec Specification") {
    - - val ret = LiftSession.checkForContentId(xml) + + val ret = Templates.checkForContentId(xml) ret must_== xml } - + "Snippet invocation works " in { val res =
    @@ -186,9 +186,10 @@ object SnippetSpec extends Specification("SnippetSpec Specification") { val ret = S.statelessInit(Req.nil) { S.mapSnippetsWith("foo" -> testAttrs _) { + val clStr = "l:foo?bing=bong&fuzz=faz+snark&noodle=FatPoodle" for { s <- S.session - } yield s.processSurroundAndInclude("test",
    ) + } yield s.processSurroundAndInclude("test",
    ) } } @@ -236,8 +237,6 @@ object SnippetSpec extends Specification("SnippetSpec Specification") { } "Snippet invocation fails class='l:bar'" in { - val res =
    - val ret = S.statelessInit(Req.nil) { S.mapSnippetsWith("foo" -> ((a: NodeSeq) => a)) { @@ -274,7 +273,7 @@ object SnippetSpec extends Specification("SnippetSpec Specification") { S.mapSnippetsWith("foo" -> ChangeVar.foo _) { for { s <- S.session - } yield s.processSurroundAndInclude("test", + } yield s.processSurroundAndInclude("test", {res}) } } @@ -291,7 +290,7 @@ object SnippetSpec extends Specification("SnippetSpec Specification") { S.mapSnippetsWith("foo" -> ChangeVar.foo _) { for { s <- S.session - } yield s.processSurroundAndInclude("test", + } yield s.processSurroundAndInclude("test", {res}) } } @@ -307,7 +306,7 @@ object SnippetSpec extends Specification("SnippetSpec Specification") { S.mapSnippetsWith("foo" -> Funky.foo _) { for { s <- S.session - } yield s.processSurroundAndInclude("test", + } yield s.processSurroundAndInclude("test", {res}) } } @@ -324,7 +323,7 @@ object SnippetSpec extends Specification("SnippetSpec Specification") { S.mapSnippetsWith("foo" -> Funky.foo _) { for { s <- S.session - } yield s.processSurroundAndInclude("test", + } yield s.processSurroundAndInclude("test", {res}) } } @@ -361,7 +360,7 @@ object SnippetSpec extends Specification("SnippetSpec Specification") { val ret = S.init(makeReq, session) { for { s <- S.session - } yield s.processSurroundAndInclude("test", + } yield s.processSurroundAndInclude("test", Moo) } @@ -376,7 +375,7 @@ object SnippetSpec extends Specification("SnippetSpec Specification") { val ret = S.init(makeReq, session) { for { s <- S.session - } yield s.processSurroundAndInclude("test", + } yield s.processSurroundAndInclude("test", ) } @@ -387,14 +386,13 @@ object SnippetSpec extends Specification("SnippetSpec Specification") { "Eager Eval works" in { - val res =
    dog
    val session = new LiftSession("", "hello", Empty) S.init(makeReq, session) { S.mapSnippetsWith("foo" -> ChangeVar.foo _) { for { s <- S.session - } yield s.processSurroundAndInclude("test", + } yield s.processSurroundAndInclude("test",
    ab
    ) } myInfo.is must_== "ab" From b8983ffbf9566d109717fed6bd5f85148b38b6cd Mon Sep 17 00:00:00 2001 From: Diego Medina Date: Sat, 24 Mar 2012 00:04:44 -0400 Subject: [PATCH 0039/1949] fixed #1241 - Deprecate net.liftweb.builtin.snippet.A ** Changed SHtml.a not to use the builtin A object, but to use fmapFunc instead --- .../main/scala/net/liftweb/builtin/snippet/A.scala | 12 ++++++++++++ .../src/main/scala/net/liftweb/http/LiftRules.scala | 2 +- .../src/main/scala/net/liftweb/http/SHtml.scala | 5 ++--- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/A.scala b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/A.scala index 6cc50b5dfb..f0611d9ad2 100644 --- a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/A.scala +++ b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/A.scala @@ -23,12 +23,24 @@ import net.liftweb.http._ import net.liftweb.http.js._ import net.liftweb.util._ +@deprecated("Use any of the ajax methods in SHtml instead.") object A extends DispatchSnippet { def dispatch : DispatchIt = { case _ => render _ } + /** + * Usage: + * + *
     JsCmd, body: NodeSeq, attrs: ElemAttr*): Elem = {
    +   *     val key = formFuncName
    +   *     addFunctionMap(key, ((a: List[String]) => func()))
    +   *     attrs.foldLeft(<lift:a key={key}>{body}</lift:a>)(_ % _)
    +   *   }
    +   *   
    + */ def render(kids: NodeSeq) : NodeSeq = Elem(null, "a", addAjaxHREF(), TopScope, kids :_*) private def addAjaxHREF(): MetaData = { diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index a09d84b4d3..fa14f97e3f 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -1685,7 +1685,7 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { Map("CSS" -> CSS, "Msgs" -> Msgs, "Msg" -> Msg, "Menu" -> Menu, "css" -> CSS, "msgs" -> Msgs, "msg" -> Msg, "menu" -> Menu, - "a" -> A, "children" -> Children, + "children" -> Children, "comet" -> Comet, "form" -> Form, "ignore" -> Ignore, "loc" -> Loc, "surround" -> Surround, "test_cond" -> TestCond, diff --git a/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala b/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala index 05cf3dfc0b..b0ee0ba3a7 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala @@ -523,9 +523,8 @@ trait SHtml { * @param attrs - the anchor node attributes */ def a(func: () => JsCmd, body: NodeSeq, attrs: ElemAttr*): Elem = { - val key = formFuncName - addFunctionMap(key, ((a: List[String]) => func())) - attrs.foldLeft({body})(_ % _) + attrs.foldLeft(fmapFunc((func))(name => + {body}))(_ % _) } /** From a7879579146d43a1fdb36d93663c66f706a9859e Mon Sep 17 00:00:00 2001 From: Peter Brant Date: Tue, 3 Apr 2012 21:26:44 +0200 Subject: [PATCH 0040/1949] Add back code deleted in 380ccf0 --- .../src/main/scala/net/liftweb/http/LiftServlet.scala | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala index 2922478a5f..3f2c9738b2 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala @@ -329,6 +329,7 @@ class LiftServlet extends Loggable { LiftSession.onBeginServicing.foreach(_(liftSession, req)) val ret: (Boolean, Box[LiftResponse]) = try { + try { // run the continuation in the new session // if there is a continuation continuation match { @@ -353,7 +354,12 @@ class LiftServlet extends Loggable { (true, Full(liftSession.checkRedirect(req.createNotFound(f)))) } } + } catch { + case ite: java.lang.reflect.InvocationTargetException if (ite.getCause.isInstanceOf[ResponseShortcutException]) => + (true, Full(liftSession.handleRedirect(ite.getCause.asInstanceOf[ResponseShortcutException], req))) + case rd: net.liftweb.http.ResponseShortcutException => (true, Full(liftSession.handleRedirect(rd, req))) + } } finally { if (S.functionMap.size > 0) { liftSession.updateFunctionMap(S.functionMap, S.renderVersion, millis) From 4cc150c45bc0fec893c9f3c2511bc2545e8704c3 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 7 Apr 2012 12:04:14 -0400 Subject: [PATCH 0041/1949] Add ajax_? to Req to tell whether the req was tagged as ajax. Identification is done using the X-Requested-With header. --- web/webkit/src/main/scala/net/liftweb/http/Req.scala | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/web/webkit/src/main/scala/net/liftweb/http/Req.scala b/web/webkit/src/main/scala/net/liftweb/http/Req.scala index e6532cb019..e6954b19cc 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Req.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Req.scala @@ -846,6 +846,18 @@ class Req(val path: ParsePath, } } + /** + * Returns true if the X-Requested-With header is set to XMLHttpRequest. + * + * Most ajax frameworks, including jQuery and Prototype, set this header + * when doing any ajax request. + */ + def ajax_? = + request.headers.toList.exists { header => + (header.name equalsIgnoreCase "x-requested-with") && + (header.value equalsIgnoreCase "xmlhttprequest") + } + /** * Make the servlet session go away */ From fca7cdf8030ee4056f0fe6409fbd93edc8d77d54 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 7 Apr 2012 12:17:53 -0400 Subject: [PATCH 0042/1949] Handle potential multiple values for X-Requested-With. If any are XMLHttpRequest, we return true for ajax_?. --- web/webkit/src/main/scala/net/liftweb/http/Req.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/Req.scala b/web/webkit/src/main/scala/net/liftweb/http/Req.scala index e6954b19cc..50e7a9156d 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Req.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Req.scala @@ -855,7 +855,7 @@ class Req(val path: ParsePath, def ajax_? = request.headers.toList.exists { header => (header.name equalsIgnoreCase "x-requested-with") && - (header.value equalsIgnoreCase "xmlhttprequest") + (header.values.exists(_ equalsIgnoreCase "xmlhttprequest")) } /** From fcae069acfc8196f899845147e626abe171a9f8e Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Mon, 9 Apr 2012 23:58:48 -0400 Subject: [PATCH 0043/1949] * More API documentation. ** Mostly net.liftweb.http.js.* --- .../net/liftweb/http/jquery/JqSHtml.scala | 6 - .../net/liftweb/http/js/JSArtifacts.scala | 7 +- .../net/liftweb/http/js/JsCommands.scala | 4 +- .../http/js/extcore/ExtCoreArtifacts.scala | 49 +++++++- .../http/js/jquery/JQueryArtifacts.scala | 44 +++++++ .../net/liftweb/http/js/jquery/JqJsCmds.scala | 114 +++++++++++++++++- .../liftweb/http/js/yui/YUIArtifacts.scala | 45 ++++++- 7 files changed, 252 insertions(+), 17 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/jquery/JqSHtml.scala b/web/webkit/src/main/scala/net/liftweb/http/jquery/JqSHtml.scala index f38dcffe7c..342a2e8010 100755 --- a/web/webkit/src/main/scala/net/liftweb/http/jquery/JqSHtml.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/jquery/JqSHtml.scala @@ -18,16 +18,10 @@ package net.liftweb package http package jquery -import net.liftweb.http.S._ -import net.liftweb.http.SHtml._ -import net.liftweb.common._ -import net.liftweb.util._ import net.liftweb.util.Helpers._ import net.liftweb.http.js._ import net.liftweb.http.js.jquery._ -import JE._ import JqJsCmds._ -import scala.xml._ /** * This contains Html artifacts that are heavily relying on JQuery diff --git a/web/webkit/src/main/scala/net/liftweb/http/js/JSArtifacts.scala b/web/webkit/src/main/scala/net/liftweb/http/js/JSArtifacts.scala index 752c822b67..f330890420 100755 --- a/web/webkit/src/main/scala/net/liftweb/http/js/JSArtifacts.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/js/JSArtifacts.scala @@ -19,8 +19,7 @@ package http package js import net.liftweb.common.{Box, Full, Empty} -import net.liftweb.http.NoticeType -import scala.xml.{Elem, NodeSeq} +import scala.xml.NodeSeq import net.liftweb.util.Helpers._ /** @@ -39,7 +38,7 @@ trait JSArtifacts { def hide(id: String): JsExp /** - * SHows the element denominated by this id + * Shows the element denominated by this id */ def show(id: String): JsExp @@ -65,7 +64,7 @@ trait JSArtifacts { def setHtml(id: String, content: NodeSeq): JsCmd /** - * Sets the JavScript that willbe executed when document is ready + * Sets the JavScript that will be executed when document is ready * for processing */ def onLoad(cmd: JsCmd): JsCmd diff --git a/web/webkit/src/main/scala/net/liftweb/http/js/JsCommands.scala b/web/webkit/src/main/scala/net/liftweb/http/js/JsCommands.scala index a398e28de1..37db1e0c75 100755 --- a/web/webkit/src/main/scala/net/liftweb/http/js/JsCommands.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/js/JsCommands.scala @@ -19,11 +19,9 @@ package js import scala.xml.{NodeSeq, Group, Unparsed, Elem} import net.liftweb.util.Helpers._ -import net.liftweb.util.Helpers -import net.liftweb.util.TimeHelpers import net.liftweb.common._ import net.liftweb.util._ -import scala.xml.{Node, SpecialNode, Text} +import scala.xml.Node object JsCommands { def create = new JsCommands(Nil) diff --git a/web/webkit/src/main/scala/net/liftweb/http/js/extcore/ExtCoreArtifacts.scala b/web/webkit/src/main/scala/net/liftweb/http/js/extcore/ExtCoreArtifacts.scala index a7d9e12e1d..ee36f9d709 100755 --- a/web/webkit/src/main/scala/net/liftweb/http/js/extcore/ExtCoreArtifacts.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/js/extcore/ExtCoreArtifacts.scala @@ -19,7 +19,7 @@ package http package js package extcore -import scala.xml.{Elem, NodeSeq} +import scala.xml.NodeSeq import net.liftweb.http.S import net.liftweb.http.js.JE @@ -28,27 +28,49 @@ import JE._ import JsCmds._ import util.Helpers._ +/** + * JavaScript DSL to use the Ext Core JavaScript library + */ object ExtCoreArtifacts extends JSArtifacts { + /** + * Toggles between current JS object and the object denominated by id + */ def toggle(id: String) = new JsExp { def toJsCmd = "Ext.fly(" + id.encJs + ").toggle()" } + /** + * Hides the element denominated by id + */ def hide(id: String) = new JsExp { def toJsCmd = "Ext.fly(" + id.encJs + ").hide()" } + /** + * Shows the element denominated by this id + */ def show(id: String) = new JsExp { def toJsCmd = "Ext.fly(" + id.encJs + ").show()" } + /** + * Shows the element denoinated by id and puts the focus on it + */ def showAndFocus(id: String) = new JsExp { def toJsCmd = "Ext.fly(" + id.encJs + ").show().focus(200)" } + /** + * Serializes a form denominated by the id. It returns a query string + * containing the fields that are to be submitted + */ def serialize(id: String) = new JsExp { def toJsCmd = "Ext.Ajax.serializeForm(" + id.encJs + ")" } + /** + * Replaces the content of the node with the provided id with the markup given by content + */ def replace(id: String, content: NodeSeq): JsCmd = new JsCmd with HtmlFixer { override val toJsCmd = { val (html, js) = fixHtmlAndJs("inline", content) @@ -72,30 +94,55 @@ object ExtCoreArtifacts extends JSArtifacts { } } + /** + * Sets the inner HTML of the element denominated by the id + */ def setHtml(id: String, xml: NodeSeq): JsCmd = new JsCmd { def toJsCmd = fixHtmlCmdFunc(id, xml){s => "try { Ext.fly(" + id.encJs + ").dom.innerHTML = " + s + "; } catch (e) {}"} } + /** + * Sets the JavScript that will be executed when document is ready + * for processing + */ def onLoad(cmd: JsCmd): JsCmd = new JsCmd { def toJsCmd = "Ext.onReady(function() {" + cmd.toJsCmd + "})" } + /** + * Fades out the element having the provided id, by waiting + * for the given duration and fades out during fadeTime + */ def fadeOut(id: String, duration: TimeSpan, fadeTime: TimeSpan) = Noop + /** + * Makes an Ajax request using lift's Ajax path and the request + * attributes described by data parameter + */ def ajax(data: AjaxInfo): String = { "Ext.Ajax.request(" + toJson(data, S.contextPath, prefix => JsRaw(S.encodeURL(prefix + "/" +LiftRules.ajaxPath + "/").encJs))+");" } + /** + * Makes a Ajax comet request using lift's Comet path and the request + * attributes described by data parameter + */ def comet(data: AjaxInfo): String = { "Ext.Ajax.request(" + toJson(data, LiftRules.cometServer(), LiftRules.calcCometPath) + ");" } + /** + * Trabsforms a JSON object intoits string representation + */ def jsonStringify(in: JsExp) : JsExp = new JsExp { def toJsCmd = "Ext.encode(" + in.toJsCmd + ")" } + /** + * Converts a form denominated by formId into a JSON object + */ def formToJSON(formId: String):JsExp = new JsExp() { def toJsCmd = "Ext.urlDecode(Ext.Ajax.serializeForm(" + formId.encJs + "));" } diff --git a/web/webkit/src/main/scala/net/liftweb/http/js/jquery/JQueryArtifacts.scala b/web/webkit/src/main/scala/net/liftweb/http/js/jquery/JQueryArtifacts.scala index b48156b917..f4402b074d 100755 --- a/web/webkit/src/main/scala/net/liftweb/http/js/jquery/JQueryArtifacts.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/js/jquery/JQueryArtifacts.scala @@ -31,49 +31,93 @@ import util.Helpers._ import util.Props trait JQueryArtifacts extends JSArtifacts { + /** + * Toggles between current JS object and the object denominated by id + */ def toggle(id: String) = JqId(id) ~> new JsMember { def toJsCmd = "toggle()" } + /** + * Hides the element denominated by id + */ def hide(id: String) = JqId(id) ~> new JsMember { def toJsCmd = "hide()" } + /** + * Shows the element denominated by this id + */ def show(id: String) = JqId(id) ~> new JsMember { def toJsCmd = "show()" } + /** + * Shows the element denoinated by id and puts the focus on it + */ def showAndFocus(id: String) = JqId(id) ~> new JsMember { def toJsCmd = "show().each(function(i) {var t = this; setTimeout(function() { t.focus(); }, 200);})" } + /** + * Serializes a form denominated by the id. It returns a query string + * containing the fields that are to be submitted + */ def serialize(id: String) = JqId(id) ~> new JsMember { def toJsCmd = "serialize()" } + /** + * Replaces the content of the node with the provided id with the markup given by content + */ def replace(id: String, content: NodeSeq): JsCmd = JqJsCmds.JqReplace(id, content) + /** + * Sets the inner HTML of the element denominated by the id + */ def setHtml(id: String, content: NodeSeq): JsCmd = JqJsCmds.JqSetHtml(id, content) + /** + * Sets the JavScript that will be executed when document is ready + * for processing + */ def onLoad(cmd: JsCmd): JsCmd = JqJsCmds.JqOnLoad(cmd) + /** + * Fades out the element having the provided id, by waiting + * for the given duration and fades out during fadeTime + */ def fadeOut(id: String, duration: TimeSpan, fadeTime: TimeSpan) = FadeOut(id, duration, fadeTime) + /** + * Makes an Ajax request using lift's Ajax path and the request + * attributes described by data parameter + */ def ajax(data: AjaxInfo): String = { "jQuery.ajax(" + toJson(data, S.contextPath, prefix => JsRaw("liftAjax.addPageName(" + S.encodeURL(prefix + "/" + LiftRules.ajaxPath + "/").encJs + ")")) + ");" } + /** + * Makes a Ajax comet request using lift's Comet path and the request + * attributes described by data parameter + */ def comet(data: AjaxInfo): String = { "jQuery.ajax(" + toJson(data, LiftRules.cometServer(), LiftRules.calcCometPath) + ");" } + /** + * Trabsforms a JSON object intoits string representation + */ def jsonStringify(in: JsExp): JsExp = new JsExp { def toJsCmd = "JSON.stringify(" + in.toJsCmd + ")" } + /** + * Converts a form denominated by formId into a JSON object + */ def formToJSON(formId: String): JsExp = new JsExp() { def toJsCmd = "lift$.formToJSON('" + formId + "')"; } diff --git a/web/webkit/src/main/scala/net/liftweb/http/js/jquery/JqJsCmds.scala b/web/webkit/src/main/scala/net/liftweb/http/js/jquery/JqJsCmds.scala index 72715597f1..18300211f8 100755 --- a/web/webkit/src/main/scala/net/liftweb/http/js/jquery/JqJsCmds.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/js/jquery/JqJsCmds.scala @@ -18,7 +18,7 @@ package http package js package jquery -import scala.xml.{NodeSeq, Group, Elem, Node, SpecialNode} +import scala.xml.NodeSeq import net.liftweb.util.Helpers._ import net.liftweb.util.Helpers import net.liftweb.util.TimeHelpers @@ -159,10 +159,16 @@ object JqJE { def toJsCmd = "each(function(i) {this.scrollTop=this.scrollHeight;})" } + /** + * Bind an event handler to the "click" JavaScript event, or trigger that event on an element. + */ case class JqClick(exp: JsExp) extends JsExp with JsMember with JQueryLeft with JQueryRight { def toJsCmd = "click(" + exp.toJsCmd + ")" } + /** + * Get the value of the first attribute specified by key + */ case class JqGetAttr(key: String) extends JsExp with JsMember with JQueryRight with JQueryLeft { def toJsCmd = "attr(" + key.encJs + ")" } @@ -178,6 +184,10 @@ object JqJE { override def toJsCmd = "jQuery(document)" } + /** + * Execute a JsCmd when the Char is pressed. Uses jQuery keypress + * @param what a tuple of (Char, JsCmd) + */ case class JqKeypress(what: (Char, JsCmd)*) extends JsExp with JsMember with JQueryRight { override def toJsCmd = "keypress(function(e) {" + what.map { @@ -194,6 +204,11 @@ object JqJE { override def toJsCmd = "jQuery('#'+" + id.toJsCmd + ")" } + /** + * Set the value of an attribute key, the value "value" + * @param key the attribute to set its value + * @param value the value :) + */ case class JqAttr(key: String, value: JsExp) extends JsExp with JsMember with JQueryRight with JQueryLeft { def toJsCmd = "attr(" + key.encJs + ", " + value.toJsCmd + ")" } @@ -238,6 +253,9 @@ object JqJE { "prependTo(" + fixHtmlFunc("inline", content){str => str} + ")" } + /** + * Set the css value of an element + */ case class JqCss (name: JsExp, value: JsExp) extends JsExp with JsMember with JQueryRight with JQueryLeft { override def toJsCmd = "css(" + name.toJsCmd + "," + value.toJsCmd + ")" } @@ -251,6 +269,9 @@ object JqJE { "empty().after(" + fixHtmlFunc("inline", content){str => str} + ")" } + /** + * Replace the html node with content + */ case class JqReplace(content: NodeSeq) extends JsExp with JsMember { override val toJsCmd = fixHtmlCmdFunc("inline", content){"replaceWith(" + _ + ")"} } @@ -266,10 +287,16 @@ object JqJE { } object JqText { + /** + * Get the combined text contents of each element in the set of matched elements, including their descendants. + */ def apply(): JsExp with JsMember with JQueryRight = new JsExp with JsMember with JQueryRight { def toJsCmd = "text()" } + /** + * Sets the content of an element to "content", html tags are escaped. + */ def apply(content: String): JsExp with JsMember with JQueryRight with JQueryLeft = new JsExp with JsMember with JQueryRight with JQueryLeft { def toJsCmd = "text(" + content.encJs + ")" } @@ -318,6 +345,10 @@ object JqJE { object JqJsCmds { implicit def jsExpToJsCmd(in: JsExp) = in.cmd + /** + * Sets the JavScript that will be executed when document is ready + * for processing + */ case class JqOnLoad(cmd: JsCmd) extends JsCmd { def toJsCmd = "jQuery(document).ready(function() {" + cmd.toJsCmd + "});" } @@ -362,10 +393,16 @@ object JqJsCmds { JqJE.JqId(JE.Str(uid)) ~> JqJE.JqPrependTo(content) } + /** + * Replaces the content of the node with the provided id with the markup given by content + */ case class JqReplace(uid: String, content: NodeSeq) extends JsCmd { val toJsCmd = (JqJE.JqId(JE.Str(uid)) ~> JqJE.JqReplace(content)).cmd.toJsCmd } + /** + * Sets the inner HTML of the element denominated by the id + */ case class JqSetHtml(uid: String, content: NodeSeq) extends JsCmd { /** * Eagerly evaluate @@ -373,26 +410,67 @@ object JqJsCmds { val toJsCmd = (JqJE.JqId(JE.Str(uid)) ~> JqJE.JqHtml(content)).cmd.toJsCmd } + /** + * Show an element identified by uid. + * There are two apply methods, one takes just the id, the other takes the id and timespan + * that represents how long the animation will last + */ object Show { + /** + * Show an element based on the ID uid + */ def apply(uid: String) = new Show(uid, Empty) + /** + * + * Show an element identified by uid + * + * @param uid the element id + * @param time the duration of the effect. + */ def apply(uid: String, time: TimeSpan) = new Show(uid, Full(time)) } + /** + * Show an element identified by uid + * + * @param uid the element id + * @param time the duration of the effect. + */ class Show(val uid: String, val time: Box[TimeSpan]) extends JsCmd with HasTime { def toJsCmd = "try{jQuery(" + ("#" + uid).encJs + ").show(" + timeStr + ");} catch (e) {}" } + /** + * Hide an element identified by uid + */ object Hide { + /** + * Hide an element identified by uid + */ def apply(uid: String) = new Hide(uid, Empty) + /** + * Hide an element identified by uid and the animation will last @time + */ def apply(uid: String, time: TimeSpan) = new Hide(uid, Full(time)) } + /** + * Hide an element identified by uid and the animation will last @time + */ class Hide(val uid: String, val time: Box[TimeSpan]) extends JsCmd with HasTime { def toJsCmd = "try{jQuery(" + ("#" + uid).encJs + ").hide(" + timeStr + ");} catch (e) {}" } + /** + * Show a message @msg in the id @where for @duration milliseconds and fade out in @fadeout milliseconds + * + * @param where the id of where to show the message + * @param msg the message as a NodeSeq + * @param duration show the msessage for @duration in milliseconds or "slow", "fast" + * @param fadeTime fadeout in @fadeout milliseconds or "slow", "fast" + */ case class DisplayMessage(where: String, msg: NodeSeq, duration: TimeSpan, fadeTime: TimeSpan) extends JsCmd { def toJsCmd = (Show(where) & JqSetHtml(where, msg) & After(duration, Hide(where, fadeTime))).toJsCmd } @@ -407,6 +485,10 @@ object JqJsCmds { def apply(id: String) = new FadeOut(id, JsRules.prefadeDuration, JsRules.fadeTime) } + /** + * Fades out the element having the provided id, by waiting + * for the given duration and fades out during fadeTime + */ case class FadeOut(id: String, duration: TimeSpan, fadeTime: TimeSpan) extends JsCmd { def toJsCmd = (After(duration, JqJE.JqId(id) ~> (new JsRaw("fadeOut(" + fadeTime.millis + ")") with JsMember))).toJsCmd } @@ -421,16 +503,42 @@ object JqJsCmds { def apply(id: String) = new FadeIn(id, JsRules.prefadeDuration, JsRules.fadeTime) } + /** + * Fade in an element with @id for @duration in milliseconds or "slow", "fast" + * and use @fadeTime + * + * @param id + * @param duration + * @param fadeTime + */ case class FadeIn(id: String, duration: TimeSpan, fadeTime: TimeSpan) extends JsCmd { def toJsCmd = (After(duration, JqJE.JqId(id) ~> (new JsRaw("fadeIn(" + fadeTime.millis + ")") with JsMember))).toJsCmd } + /** + * Companion object for ModelDialog that provides two alternative factories + */ object ModalDialog { + + /** + * Requires the jQuery blockUI plugin + * + * @param html the html for the ModalDialog + */ def apply(html: NodeSeq) = new ModalDialog(html, Empty) + /** + * Requires the jQuery blockUI plugin + * + * @param html the html for the ModalDialog + * @param css the css to apply to the dialog + */ def apply(html: NodeSeq, css: JsObj) = new ModalDialog(html, Full(css)) } + /** + * Requires the jQuery blockUI plugin + */ class ModalDialog(html: NodeSeq, css: Box[JsObj]) extends JsCmd { /* private def contentAsJsStr = { @@ -451,8 +559,10 @@ object JqJsCmds { "jQuery.blockUI({ message: " + str + (css.map(", css: " + _.toJsCmd + " ").openOr("")) + "});"} } - + /** + * Remove the jQuery.Block dialog + */ case object Unblock extends JsCmd { def toJsCmd = "jQuery.unblockUI();" } diff --git a/web/webkit/src/main/scala/net/liftweb/http/js/yui/YUIArtifacts.scala b/web/webkit/src/main/scala/net/liftweb/http/js/yui/YUIArtifacts.scala index 7f38830c32..a3ce7214a1 100755 --- a/web/webkit/src/main/scala/net/liftweb/http/js/yui/YUIArtifacts.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/js/yui/YUIArtifacts.scala @@ -37,27 +37,46 @@ import JE._ * event.js */ object YUIArtifacts extends JSArtifacts { + /** + * Toggles between current JS object and the object denominated by id + */ def toggle(id: String) = new JsExp { def toJsCmd = "YAHOO.lift.toggle(this, " + id.encJs + ");"; } + /** + * Hides the element denominated by id + */ def hide(id: String) = new JsExp { def toJsCmd = "YAHOO.util.Dom.setStyle(" + id.encJs + ", 'display', 'none');" } + /** + * Shows the element denominated by this id + */ def show(id: String) = new JsExp { def toJsCmd = "YAHOO.util.Dom.setStyle(" + id.encJs + ", 'display', 'block');" } + /** + * Shows the element denoinated by id and puts the focus on it + */ def showAndFocus(id: String) = new JsExp { def toJsCmd = "YAHOO.util.Dom.setStyle(" + id.encJs + ", 'display', 'block');" + "setTimeout(function() { document.getElementById(" + id.encJs + ").focus(); }, 200);" } + /** + * Serializes a form denominated by the id. It returns a query string + * containing the fields that are to be submitted + */ def serialize(id: String) = new JsExp { def toJsCmd = "YAHOO.util.Connect.setForm(" + id.encJs + ", false)" } + /** + * Replaces the content of the node with the provided id with the markup given by content + */ def replace(id: String, content: NodeSeq): JsCmd = new JsCmd with HtmlFixer { override val toJsCmd = { val (html, js) = fixHtmlAndJs("inline", content) @@ -81,17 +100,31 @@ object YUIArtifacts extends JSArtifacts { } } + /** + * Sets the inner HTML of the element denominated by the id + */ def setHtml(uid: String, content: NodeSeq): JsCmd = new JsCmd { val toJsCmd = fixHtmlCmdFunc(uid, content){s => "try{document.getElementById(" + uid.encJs + ").innerHTML = " + s + ";} catch (e) {}"} } + /** + * Sets the JavScript that will be executed when document is ready + * for processing + */ def onLoad(cmd: JsCmd): JsCmd = new JsCmd { def toJsCmd = "YAHOO.util.Event.onDOMReady(function(){" + cmd.toJsCmd + "})" } + /** + * Fades out the element having the provided id, by waiting + * for the given duration and fades out during fadeTime + */ def fadeOut(id: String, duration: TimeSpan, fadeTime: TimeSpan) = Noop - + /** + * Makes an Ajax request using lift's Ajax path and the request + * attributes described by data parameter + */ def ajax(data: AjaxInfo): String = { val url = S.encodeURL(S.contextPath + "/" + LiftRules.ajaxPath + "/") @@ -99,16 +132,26 @@ object YUIArtifacts extends JSArtifacts { "YAHOO.util.Connect.asyncRequest(" + data.action.encJs + ", url, " + toJson(data) + ");" } + /** + * Makes a Ajax comet request using lift's Comet path and the request + * attributes described by data parameter + */ def comet(data: AjaxInfo): String = { val url = LiftRules.calcCometPath(LiftRules.cometServer()) "url = YAHOO.lift.buildURI(" + url.toJsCmd + ", YAHOO.lift.simpleJsonToQS(" + data.data.toJsCmd + "));" + "YAHOO.util.Connect.asyncRequest(" + data.action.encJs + ", url, " + toJson(data) + ");"; } + /** + * Trabsforms a JSON object intoits string representation + */ def jsonStringify(in: JsExp): JsExp = new JsExp { def toJsCmd = "YAHOO.lang.JSON.stringify(" + in.toJsCmd + ")" } + /** + * Converts a form denominated by formId into a JSON object + */ def formToJSON(formId: String): JsExp = new JsExp() { def toJsCmd = "YAHOO.lift.formToJSON('" + formId + "')"; } From 61ec6bdda9881ad89253dc75ed492241eae80cc4 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Tue, 10 Apr 2012 23:57:51 -0400 Subject: [PATCH 0044/1949] Improve verbiage of Jq* docs, include links to jQuery API where relevant. --- .../net/liftweb/http/js/jquery/JqJsCmds.scala | 174 ++++++++++++++---- 1 file changed, 136 insertions(+), 38 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/js/jquery/JqJsCmds.scala b/web/webkit/src/main/scala/net/liftweb/http/js/jquery/JqJsCmds.scala index 18300211f8..f9467b3b13 100755 --- a/web/webkit/src/main/scala/net/liftweb/http/js/jquery/JqJsCmds.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/js/jquery/JqJsCmds.scala @@ -154,39 +154,74 @@ object JqWiringSupport { } +/** + * Contains Scala JsExps for jQuery behaviors. + * + * These functions are meant to be combined using the ~> operator. For + * example: + * + *
    JqJE.Jq("button") ~> JqClick(AnonFunc(...))
    + * + * Documentation on the case classes themselves will point to the + * relevant jQuery documentation, if there is any. + */ object JqJE { + /** + * Changes the scroll position of each matched element to its maximum. + */ case object JqScrollToBottom extends JsExp with JsMember with JQueryRight with JQueryLeft { def toJsCmd = "each(function(i) {this.scrollTop=this.scrollHeight;})" } /** - * Bind an event handler to the "click" JavaScript event, or trigger that event on an element. + * Calls the jQuery click function with the parameter in exp. + * + * Used to set a click handler function (also see AnonFunc). + * + * See http://api.jquery.com/click/ . */ case class JqClick(exp: JsExp) extends JsExp with JsMember with JQueryLeft with JQueryRight { def toJsCmd = "click(" + exp.toJsCmd + ")" } /** - * Get the value of the first attribute specified by key + * Calls the jQuery attr function with the given key. + * + * Used to get the value of the given attribute. + * + * See http://api.jquery.com/attr/ . */ case class JqGetAttr(key: String) extends JsExp with JsMember with JQueryRight with JQueryLeft { def toJsCmd = "attr(" + key.encJs + ")" } /** - * A JQuery query + * Calls the main jQuery (or $) function with the parameter in query. + * + * Used to get a set of elements to apply the other JqJE expressions to. + * + * See http://api.jquery.com/jQuery/ . */ case class Jq(query: JsExp) extends JsExp with JQueryLeft { override def toJsCmd = "jQuery(" + query.toJsCmd + ")" } + /** + * Calls the main jQuery (or $) function with "document". This returns + * the jQueryied document object (e.g., for calling ready()). + * + * See http://api.jquery.com/jQuery/ . + */ case object JqDoc extends JsExp with JQueryLeft { override def toJsCmd = "jQuery(document)" } /** - * Execute a JsCmd when the Char is pressed. Uses jQuery keypress - * @param what a tuple of (Char, JsCmd) + * For every passed tuple, executes the given JsCmd when the given + * Char is pressed by the user. Watches using the jQuery keypress + * function. + * + * See http://api.jquery.com/keypress/ . */ case class JqKeypress(what: (Char, JsCmd)*) extends JsExp with JsMember with JQueryRight { override def toJsCmd = "keypress(function(e) {" + @@ -205,16 +240,22 @@ object JqJE { } /** - * Set the value of an attribute key, the value "value" - * @param key the attribute to set its value - * @param value the value :) + * Calls the jQuery attr function with the given key and the given value. + * + * Used to set the given attribute to the given value. + * + * See http://api.jquery.com/attr/ . */ case class JqAttr(key: String, value: JsExp) extends JsExp with JsMember with JQueryRight with JQueryLeft { def toJsCmd = "attr(" + key.encJs + ", " + value.toJsCmd + ")" } /** - * Append content to a JQuery + * Calls the jQuery append function with the given content. + * + * Used to append the given content to the matched elements. + * + * See http://api.jquery.com/append/ . */ case class JqAppend(content: NodeSeq) extends JsExp with JsMember with JQueryRight with JQueryLeft { override val toJsCmd = @@ -222,7 +263,11 @@ object JqJE { } /** - * Remove JQuery + * Calls the jQuery remove function. + * + * Used to remove the matched elements from the DOM. + * + * See http://api.jquery.com/remove/ . */ case class JqRemove() extends JsExp with JsMember with JQueryRight with JQueryLeft { override def toJsCmd = "remove()" @@ -230,7 +275,11 @@ object JqJE { /** - * AppendTo content to a JQuery + * Calls the jQuery appendTo function with the given content. + * + * Used to wrap the matched elements in the given content. + * + * See http://api.jquery.com/appendTo/ . */ case class JqAppendTo(content: NodeSeq) extends JsExp with JsMember with JQueryRight with JQueryLeft { override val toJsCmd = @@ -238,7 +287,11 @@ object JqJE { } /** - * Prepend content to a JQuery + * Calls the jQuery prepend function with the given content. + * + * Used to prepend the given content to each matched element. + * + * See http://api.jquery.com/prepend/ . */ case class JqPrepend(content: NodeSeq) extends JsExp with JsMember with JQueryRight with JQueryLeft { override val toJsCmd = @@ -246,7 +299,11 @@ object JqJE { } /** - * PrependTo content to a JQuery + * Calls the jQuery prependTo function with the given content. + * + * Used to prepend the matched elements to the given content. + * + * See http://api.jquery.com/prependTo/ . */ case class JqPrependTo(content: NodeSeq) extends JsExp with JsMember with JQueryRight with JQueryLeft { override val toJsCmd = @@ -254,15 +311,24 @@ object JqJE { } /** - * Set the css value of an element + * Calls the jQuery css function with the given name and value. + * + * Used to set the value of the given CSS property to the given value. + * + * See http://api.jquery.com/css/ . */ case class JqCss (name: JsExp, value: JsExp) extends JsExp with JsMember with JQueryRight with JQueryLeft { override def toJsCmd = "css(" + name.toJsCmd + "," + value.toJsCmd + ")" } /** - * EmptyAfter will empty the node at the given uid and stick the given content behind it. Like - * a cleaner innerHTML. + * Calls the jQuery empty function followed by calling the jQuery + * after function with the given content. + * + * The intent is to empty the matched nodes and stick the given + * content at their tails. Like a cleaner innerHTML. + * + * See http://api.jquery.com/empty/ and http://api.jquery.com/after/ . */ case class JqEmptyAfter(content: NodeSeq) extends JsExp with JsMember with JQueryRight with JQueryLeft { override val toJsCmd = @@ -270,17 +336,35 @@ object JqJE { } /** - * Replace the html node with content + * Calls the jQuery replaceWith function with the given content. + * + * Used to replace the matched elements with the given content. + * + * See http://api.jquery.com/replaceWith/ . */ case class JqReplace(content: NodeSeq) extends JsExp with JsMember { override val toJsCmd = fixHtmlCmdFunc("inline", content){"replaceWith(" + _ + ")"} } object JqHtml { + /** + * Calls the jQuery html function with no parameters. + * + * Used to get the inner HTML of the matched elements. + * + * See http://api.jquery.com/html/ . + */ def apply(): JsExp with JsMember with JQueryRight = new JsExp with JsMember with JQueryRight { def toJsCmd = "html()" } + /** + * Calls the jQuery html function with the given content. + * + * Used to set the inner HTML of each matched element. + * + * See http://api.jquery.com/html/ . + */ def apply(content: NodeSeq): JsExp with JsMember with JQueryRight with JQueryLeft = new JsExp with JsMember with JQueryRight with JQueryLeft { val toJsCmd = fixHtmlCmdFunc("inline", content){"html(" + _ + ")"} } @@ -288,14 +372,22 @@ object JqJE { object JqText { /** - * Get the combined text contents of each element in the set of matched elements, including their descendants. + * Calls the jQuery text function with no parameters. + * + * Used to get the combined text contents of the matched elements. + * + * See http://api.jquery.com/text/ . */ def apply(): JsExp with JsMember with JQueryRight = new JsExp with JsMember with JQueryRight { def toJsCmd = "text()" } /** - * Sets the content of an element to "content", html tags are escaped. + * Calls the jQuery text function with the given content. + * + * Used to set the text contents of the matched elements. + * + * See http://api.jquery.com/text/ . */ def apply(content: String): JsExp with JsMember with JQueryRight with JQueryLeft = new JsExp with JsMember with JQueryRight with JQueryLeft { def toJsCmd = "text(" + content.encJs + ")" @@ -303,14 +395,23 @@ object JqJE { } /** - * Serialize input elements intoa string data. ALso works for serializing forms + * Calls the jQuery serialize function. + * + * Used to serialize input elements or forms into query string data. + * + * See http://api.jquery.com/serialize/ . */ case object JqSerialize extends JsExp with JsMember with JQueryRight { def toJsCmd = "serialize()" } /** - * Serialize the jquery into a JSON array + * Calls the jQuery serializeArray function. + * + * Used to serialize the matched elements into a JSON array containing + * objects with name and value properties. + * + * See http://api.jquery.com/serializeArray/ . */ case object JsonSerialize extends JsExp with JsMember with JQueryRight { def toJsCmd = "serializeArray()" @@ -346,8 +447,8 @@ object JqJsCmds { implicit def jsExpToJsCmd(in: JsExp) = in.cmd /** - * Sets the JavScript that will be executed when document is ready - * for processing + * Queues the JavaScript in cmd for execution when the document is + * ready for processing */ case class JqOnLoad(cmd: JsCmd) extends JsCmd { def toJsCmd = "jQuery(document).ready(function() {" + cmd.toJsCmd + "});" @@ -442,16 +543,21 @@ object JqJsCmds { } /** - * Hide an element identified by uid + * Hide an element identified by uid. + * There are two apply methods, one takes just the id, the other takes the id and timespan + * that represents how long the animation will last */ object Hide { /** - * Hide an element identified by uid + * Hide an element based on the ID uid */ def apply(uid: String) = new Hide(uid, Empty) /** - * Hide an element identified by uid and the animation will last @time + * Hide an element identified by uid + * + * @param uid the element id + * @param time the duration of the effect. */ def apply(uid: String, time: TimeSpan) = new Hide(uid, Full(time)) } @@ -464,12 +570,7 @@ object JqJsCmds { } /** - * Show a message @msg in the id @where for @duration milliseconds and fade out in @fadeout milliseconds - * - * @param where the id of where to show the message - * @param msg the message as a NodeSeq - * @param duration show the msessage for @duration in milliseconds or "slow", "fast" - * @param fadeTime fadeout in @fadeout milliseconds or "slow", "fast" + * Show a message msg in the element with id where for duration milliseconds and fade out in fadeout milliseconds */ case class DisplayMessage(where: String, msg: NodeSeq, duration: TimeSpan, fadeTime: TimeSpan) extends JsCmd { def toJsCmd = (Show(where) & JqSetHtml(where, msg) & After(duration, Hide(where, fadeTime))).toJsCmd @@ -487,7 +588,7 @@ object JqJsCmds { /** * Fades out the element having the provided id, by waiting - * for the given duration and fades out during fadeTime + * for the given duration and fading out during fadeTime */ case class FadeOut(id: String, duration: TimeSpan, fadeTime: TimeSpan) extends JsCmd { def toJsCmd = (After(duration, JqJE.JqId(id) ~> (new JsRaw("fadeOut(" + fadeTime.millis + ")") with JsMember))).toJsCmd @@ -504,12 +605,9 @@ object JqJsCmds { } /** - * Fade in an element with @id for @duration in milliseconds or "slow", "fast" + * Fades in the element having the provided id, by waiting + * for the given duration and fading in during fadeTime * and use @fadeTime - * - * @param id - * @param duration - * @param fadeTime */ case class FadeIn(id: String, duration: TimeSpan, fadeTime: TimeSpan) extends JsCmd { def toJsCmd = (After(duration, JqJE.JqId(id) ~> (new JsRaw("fadeIn(" + fadeTime.millis + ")") with JsMember))).toJsCmd From 915dcba6a5d6f7b9e4b9312d13cbdc1f3d214a9a Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 11 Apr 2012 00:47:59 -0400 Subject: [PATCH 0045/1949] Verbiage and uniformity tweaks to JSArtifacts API docs. --- .../net/liftweb/http/js/JSArtifacts.scala | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/js/JSArtifacts.scala b/web/webkit/src/main/scala/net/liftweb/http/js/JSArtifacts.scala index f330890420..d719954026 100755 --- a/web/webkit/src/main/scala/net/liftweb/http/js/JSArtifacts.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/js/JSArtifacts.scala @@ -28,7 +28,7 @@ import net.liftweb.util.Helpers._ trait JSArtifacts { /** - * Toggles between current JS object and the object denominated by id + * Toggles the visibility of the element denomiated by id */ def toggle(id: String): JsExp @@ -38,40 +38,40 @@ trait JSArtifacts { def hide(id: String): JsExp /** - * Shows the element denominated by this id + * Shows the element denominated by id */ def show(id: String): JsExp /** - * Shows the element denoinated by id and puts the focus on it + * Shows the element denominated by id and puts the focus on it */ def showAndFocus(id: String): JsExp /** - * Serializes a form denominated by the id. It returns a query string + * Serializes a form denominated by id. It returns a query string * containing the fields that are to be submitted */ def serialize(id: String): JsExp /** - * Replaces the content of the node with the provided id with the markup given by content + * Replaces the content of the node denominated by id with the markup given by content */ def replace(id: String, content: NodeSeq): JsCmd /** - * Sets the inner HTML of the element denominated by the id + * Sets the inner HTML of the element denominated by id */ def setHtml(id: String, content: NodeSeq): JsCmd /** - * Sets the JavScript that will be executed when document is ready - * for processing + * Queues the JavaScript in cmd for execution when the document is + * ready for processing */ def onLoad(cmd: JsCmd): JsCmd /** - * Fades out the element having the provided id, by waiting - * for the given duration and fades out during fadeTime + * Fades out the element denominated by id, by waiting + * for duration milliseconds and fading out for fadeTime milliseconds */ def fadeOut(id: String, duration: TimeSpan, fadeTime: TimeSpan): JsCmd @@ -88,7 +88,7 @@ trait JSArtifacts { def comet(data: AjaxInfo): String /** - * Trabsforms a JSON object intoits string representation + * Transforms a JSON object into its string representation */ def jsonStringify(in: JsExp): JsExp @@ -146,7 +146,7 @@ object AjaxInfo { } /** - * Represents the meta data of an AJax request. + * Represents the meta data of an Ajax request. */ case class AjaxInfo(data: JsExp, action: String, timeout: Long, cache: Boolean, dataType: String, From 54a023e1ad572cbae054b1c484516c6a7b06e5e7 Mon Sep 17 00:00:00 2001 From: Indrajit Raychaudhuri Date: Fri, 13 Apr 2012 23:19:08 +0530 Subject: [PATCH 0046/1949] Add support for 2.9.2 --- build.sbt | 2 +- project/Dependencies.scala | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build.sbt b/build.sbt index 6f65cbbd6b..2b51e33dc0 100644 --- a/build.sbt +++ b/build.sbt @@ -12,7 +12,7 @@ startYear in ThisBuild := Some(2006) organizationName in ThisBuild := "WorldWide Conferencing, LLC" -crossScalaVersions in ThisBuild := Seq("2.9.1-1", "2.9.1", "2.9.0-1", "2.9.0", "2.8.2", "2.8.1", "2.8.0") +crossScalaVersions in ThisBuild := Seq("2.9.2", "2.9.1-1", "2.9.1", "2.9.0-1", "2.9.0", "2.8.2", "2.8.1", "2.8.0") libraryDependencies in ThisBuild <++= scalaVersion { sv => Seq(specs, scalacheck).map(_(sv)) } diff --git a/project/Dependencies.scala b/project/Dependencies.scala index cc11bacebf..9eb99e0704 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -24,15 +24,15 @@ object Dependencies { type ModuleMap = String => ModuleID lazy val CVMapping282 = crossMapped("2.8.2" -> "2.8.1") - lazy val CVMapping2911 = crossMapped("2.9.1-1" -> "2.9.1") - lazy val CVMappingAll = crossMapped("2.9.1-1" -> "2.9.1", "2.8.2" -> "2.8.1") + lazy val CVMapping2911 = crossMapped("2.9.2" -> "2.9.1", "2.9.1-1" -> "2.9.1") + lazy val CVMappingAll = crossMapped("2.9.2" -> "2.9.1", "2.9.1-1" -> "2.9.1", "2.8.2" -> "2.8.1") lazy val slf4jVersion = "1.6.4" lazy val scalazGroup = defaultOrMapped("org.scalaz", "2.8.0" -> "com.googlecode.scalaz") lazy val scalazVersion = defaultOrMapped("6.0.4", "2.8.0" -> "5.0", "2.9.0" -> "6.0.RC2") lazy val scalacheckVersion = defaultOrMapped("1.9", "2.8.0" -> "1.7", "2.8.1" -> "1.8", "2.8.2" -> "1.8") - lazy val specsVersion = defaultOrMapped("1.6.8", "2.8.0" -> "1.6.5", "2.9.1" -> "1.6.9", "2.9.1-1" -> "1.6.9") + lazy val specsVersion = defaultOrMapped("1.6.8", "2.8.0" -> "1.6.5", "2.9.1" -> "1.6.9", "2.9.1-1" -> "1.6.9", "2.9.2" -> "1.6.9") // Compile scope: // Scope available in all classpath, transitive by default. From d59a033ce18af3696c9f5ae8d546b8424c54aba7 Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Tue, 17 Apr 2012 14:03:09 -0400 Subject: [PATCH 0047/1949] * Fixed #1251 - Add LiftNamedComet to Lift-webkit --- .../liftweb/http/NamedCometActorSnippet.scala | 57 +++++++++++ .../liftweb/http/NamedCometActorTrait.scala | 50 ++++++++++ .../liftweb/http/NamedCometDispatcher.scala | 95 +++++++++++++++++++ .../net/liftweb/http/NamedCometListener.scala | 94 ++++++++++++++++++ .../liftweb/http/NamedCometPerTabSpec.scala | 63 ++++++++++++ 5 files changed, 359 insertions(+) create mode 100644 web/webkit/src/main/scala/net/liftweb/http/NamedCometActorSnippet.scala create mode 100644 web/webkit/src/main/scala/net/liftweb/http/NamedCometActorTrait.scala create mode 100644 web/webkit/src/main/scala/net/liftweb/http/NamedCometDispatcher.scala create mode 100644 web/webkit/src/main/scala/net/liftweb/http/NamedCometListener.scala create mode 100644 web/webkit/src/test/scala/net/liftweb/http/NamedCometPerTabSpec.scala diff --git a/web/webkit/src/main/scala/net/liftweb/http/NamedCometActorSnippet.scala b/web/webkit/src/main/scala/net/liftweb/http/NamedCometActorSnippet.scala new file mode 100644 index 0000000000..e43943c8dd --- /dev/null +++ b/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 + * + *
    
    +   *   def name= S.param(p).openOr("A")
    +   * 
    + * + * 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 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) + ) + {xhtml} + } +} diff --git a/web/webkit/src/main/scala/net/liftweb/http/NamedCometActorTrait.scala b/web/webkit/src/main/scala/net/liftweb/http/NamedCometActorTrait.scala new file mode 100644 index 0000000000..1314d10a72 --- /dev/null +++ b/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) + +} diff --git a/web/webkit/src/main/scala/net/liftweb/http/NamedCometDispatcher.scala b/web/webkit/src/main/scala/net/liftweb/http/NamedCometDispatcher.scala new file mode 100644 index 0000000000..e64937be18 --- /dev/null +++ b/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) diff --git a/web/webkit/src/main/scala/net/liftweb/http/NamedCometListener.scala b/web/webkit/src/main/scala/net/liftweb/http/NamedCometListener.scala new file mode 100644 index 0000000000..4a90f62cf5 --- /dev/null +++ b/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 + } + + +} diff --git a/web/webkit/src/test/scala/net/liftweb/http/NamedCometPerTabSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/NamedCometPerTabSpec.scala new file mode 100644 index 0000000000..a28ee5ac6b --- /dev/null +++ b/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 + ) + } + } + + +} From 94df3305f44434f9f6c95244a25a4e803549505c Mon Sep 17 00:00:00 2001 From: Tim Nelson Date: Thu, 19 Apr 2012 14:52:39 -0500 Subject: [PATCH 0048/1949] Issues 997/906 - Remove deprecated DB code from Record. Remove MongoDateListField and JObjectField. --- .../mongodb/record/field/JObjectField.scala | 3 +- .../mongodb/record/field/MongoListField.scala | 10 +-- .../net/liftweb/record/DBMetaRecord.scala | 65 -------------- .../scala/net/liftweb/record/DBRecord.scala | 88 ------------------- .../main/scala/net/liftweb/record/Field.scala | 36 ++------ project/Build.scala | 2 +- 6 files changed, 11 insertions(+), 193 deletions(-) delete mode 100644 persistence/record/src/main/scala/net/liftweb/record/DBMetaRecord.scala delete mode 100644 persistence/record/src/main/scala/net/liftweb/record/DBRecord.scala diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala index 06cd5aaa5f..1e81d6f7da 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala @@ -1,5 +1,5 @@ /* - * Copyright 2010-2011 WorldWide Conferencing, LLC + * 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. @@ -27,7 +27,6 @@ import net.liftweb.record.{Field, MandatoryTypedField, Record} import scala.xml.NodeSeq -@deprecated("Use JsonObjectField instead.") class JObjectField[OwnerType <: Record[OwnerType]](rec: OwnerType) extends Field[JObject, OwnerType] with MandatoryTypedField[JObject] { def asJs = Str(toString) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoListField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoListField.scala index 3a7b1c3f09..dbeabd5665 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoListField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoListField.scala @@ -1,5 +1,5 @@ /* - * Copyright 2010-2011 WorldWide Conferencing, LLC + * 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. @@ -134,14 +134,6 @@ class MongoListField[OwnerType <: BsonRecord[OwnerType], ListType: Manifest](rec setBox(Full(dbo.asInstanceOf[BasicDBList].toList.asInstanceOf[MyType])) } -/* -* List of Dates. Use MongListField[OwnerType, Date] instead. -*/ -@deprecated("Use MongListField[OwnerType, Date] instead") -class MongoDateListField[OwnerType <: BsonRecord[OwnerType]](rec: OwnerType) - extends MongoListField[OwnerType, Date](rec: OwnerType) { -} - /* * List of JsonObject case classes */ diff --git a/persistence/record/src/main/scala/net/liftweb/record/DBMetaRecord.scala b/persistence/record/src/main/scala/net/liftweb/record/DBMetaRecord.scala deleted file mode 100644 index 003323136f..0000000000 --- a/persistence/record/src/main/scala/net/liftweb/record/DBMetaRecord.scala +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2007-2011 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 record - -import util._ -import common._ -import db.{ConnectionIdentifier, DefaultConnectionIdentifier} -import scala.xml._ -import java.sql.{ResultSet, Types, PreparedStatement, Statement} - -@deprecated("This was never fully implemented. If you're looking for a SQL implementation of Record, please see Squeryl-Record. If you have any questions, please bring them up on the mailing list.") -trait DBMetaRecord[BaseRecord <: DBRecord[BaseRecord]] extends MetaRecord[BaseRecord] { - self: BaseRecord => - - /** - * Save the instance in the appropriate backing store - */ - def save(inst: BaseRecord): Boolean = { - foreachCallback(inst, _.beforeSave) - try { - true // TODO: implement this - } finally { - foreachCallback(inst, _.afterSave) - } - } - - /** - * Was this instance saved in backing store? - */ - def saved_?(inst: BaseRecord): Boolean = true - - /** - * Delete the instance from backing store - */ - def delete_!(inst: BaseRecord): Boolean = { - foreachCallback(inst, _.beforeDelete) - try { - true // TODO: implement this - } finally { - foreachCallback(inst, _.afterDelete) - } - } - - def dbDefaultConnectionIdentifier: ConnectionIdentifier = DefaultConnectionIdentifier - - def afterCommit: List[BaseRecord => Unit] = Nil - - // To be continued with DB related stuff -} - diff --git a/persistence/record/src/main/scala/net/liftweb/record/DBRecord.scala b/persistence/record/src/main/scala/net/liftweb/record/DBRecord.scala deleted file mode 100644 index c5b76d9744..0000000000 --- a/persistence/record/src/main/scala/net/liftweb/record/DBRecord.scala +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2007-2011 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 record - -import common._ -import db.{ConnectionIdentifier, DB} -import util._ -import scala.xml._ - -@deprecated("This was never fully implemented. If you're looking for a SQL implementation of Record, please see Squeryl-Record. If you have any questions, please bring them up on the mailing list.") -trait DBRecord[MyType <: DBRecord[MyType]] extends Record[MyType] { - self: MyType => - - /** - * Was this instance deleted from backing store? - */ - private var was_deleted_? = false - - /** - * The meta record (the object that contains the meta result for this type) - */ - def meta: DBMetaRecord[MyType] - - /** - * Save the instance and return the instance - */ - def save(): MyType = { - runSafe { - meta.save(this) - } - this - } - - - /** - * Save the instance and return the instance - */ - override def saveTheRecord(): Box[MyType] = {save(); Full(this)} - - /** - * Delete the instance from backing store - */ - def delete_! : Boolean = { - if (!can_delete_?) false else - runSafe { - was_deleted_? = meta.delete_!(this) - was_deleted_? - } - } - - /** - * Can this model object be deleted? - */ - def can_delete_? : Boolean = meta.saved_?(this) && !was_deleted_? - - private var dbConnectionIdentifier: Box[ConnectionIdentifier] = Empty - - def connectionIdentifier = dbConnectionIdentifier openOr calcDbId - - def dbCalculateConnectionIdentifier: PartialFunction[MyType, ConnectionIdentifier] = Map.empty - - private def calcDbId = if (dbCalculateConnectionIdentifier.isDefinedAt(this)) dbCalculateConnectionIdentifier(this) - else meta.dbDefaultConnectionIdentifier - - /** - * Append a function to perform after the commit happens - * @param func - the function to perform after the commit happens - */ - def doPostCommit(func: () => Unit) { - DB.appendPostFunc(connectionIdentifier, func) - } -} - diff --git a/persistence/record/src/main/scala/net/liftweb/record/Field.scala b/persistence/record/src/main/scala/net/liftweb/record/Field.scala index b2bd3196c4..32bfc83e12 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/Field.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/Field.scala @@ -152,7 +152,7 @@ trait TypedField[ThisType] extends BaseField { private[record] var data: Box[MyType] = Empty private[record] var needsDefault: Boolean = true - + /** * Helper for implementing asJValue for a conversion to an encoded JString * @@ -259,7 +259,7 @@ trait TypedField[ThisType] extends BaseField { * - null|None|Empty => setBox(defaultValueBox) * - f: Failure => setBox(f) * And usually convert the input to a string and uses setFromString as a last resort. - * + * * Note that setFromAny should _always_ call setBox, even if the conversion fails. This is so that validation * properly notes the error. * @@ -279,17 +279,17 @@ trait TypedField[ThisType] extends BaseField { case (value: String)::_ => setFromString(value) case null|None|Empty => setBox(defaultValueBox) case (failure: Failure) => setBox(failure) - case Some(other) => setFromString(String.valueOf(other)) + case Some(other) => setFromString(String.valueOf(other)) case Full(other) => setFromString(String.valueOf(other)) case other => setFromString(String.valueOf(other)) } - + /** * Set the value of the field using some kind of type-specific conversion from a String. * By convention, if the field is optional_?, then the empty string should be treated as no-value (Empty). * Note that setFromString should _always_ call setBox, even if the conversion fails. This is so that validation * properly notes the error. - * + * * @return Full(convertedValue) if the conversion succeeds (the field value will be set by side-effect) * Empty or Failure if the conversion does not succeed */ @@ -317,7 +317,7 @@ trait MandatoryTypedField[ThisType] extends TypedField[ThisType] with Product1[T //TODO: fullfil the contract of Product1[ThisType] def canEqual(a:Any) = false - + def _1 = value override def optional_? = false @@ -352,13 +352,13 @@ trait MandatoryTypedField[ThisType] extends TypedField[ThisType] with Product1[T case _ => defaultValueBox.map(v => if (v != null) v.toString else "null") openOr "" } } - + trait OptionalTypedField[ThisType] extends TypedField[ThisType] with Product1[Box[ThisType]] { type ValueType = Option[ThisType] // For util.BaseField //TODO: fullfil the contract of Product1[ThisType] def canEqual(a:Any) = false - + def _1 = value final override def optional_? = true @@ -426,26 +426,6 @@ trait DisplayWithLabel[OwnerType <: Record[OwnerType]] extends OwnedField[OwnerT
    } - -import java.sql.{ResultSet, Types} -import net.liftweb.db.{DriverType} - -/** - * Desribes common aspects related with JDBC - */ -@deprecated("This was never fully implemented. If you're looking for a SQL implementation of Record, please see Squeryl-Record. If you have any questions, please bring them up on the mailing list.") -trait JDBCFieldFlavor[MyType] { - - def jdbcFriendly(field : String) : AnyRef - - def targetSQLType : Int - - /** - * Given the driver type, return the string required to create the column in the database - */ - def fieldCreatorString(dbType: DriverType, colName: String): String -} - trait KeyField[MyType, OwnerType <: Record[OwnerType] with KeyedRecord[OwnerType, MyType]] extends Field[MyType, OwnerType] { def ===(other: KeyField[MyType, OwnerType]): Boolean = this.valueBox == other.valueBox } diff --git a/project/Build.scala b/project/Build.scala index f5a1039697..910ef9246e 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -133,7 +133,7 @@ object BuildDef extends Build { lazy val record = persistenceProject("record") - .dependsOn(proto, db) + .dependsOn(proto) lazy val couchdb = persistenceProject("couchdb") From feb7caba5cc7aa2c61b6ac01ddbbdca321cfbdbe Mon Sep 17 00:00:00 2001 From: Dave Whittaker Date: Tue, 28 Feb 2012 10:31:07 -0500 Subject: [PATCH 0049/1949] Updated RecordTypeMode to work with Squeryl 0.9.5 --- .../squerylrecord/RecordTypeMode.scala | 37 +++++++++++-------- .../liftweb/squerylrecord/SquerylRecord.scala | 23 ++++++------ .../squerylrecord/SquerylRecordSpec.scala | 5 ++- project/Dependencies.scala | 2 +- 4 files changed, 38 insertions(+), 29 deletions(-) diff --git a/persistence/squeryl-record/src/main/scala/net/liftweb/squerylrecord/RecordTypeMode.scala b/persistence/squeryl-record/src/main/scala/net/liftweb/squerylrecord/RecordTypeMode.scala index 95e76b876d..2dd65246bb 100644 --- a/persistence/squeryl-record/src/main/scala/net/liftweb/squerylrecord/RecordTypeMode.scala +++ b/persistence/squeryl-record/src/main/scala/net/liftweb/squerylrecord/RecordTypeMode.scala @@ -75,43 +75,43 @@ trait RecordTypeMode extends PrimitiveTypeMode { /** Conversion of mandatory String fields to Squeryl Expressions. */ implicit def string2ScalarString(f: MandatoryTypedField[String]) = fieldReference match { case Some(e) => new SelectElementReference[String](e)(createOutMapperStringType) with StringExpression[String] with SquerylRecordNonNumericalExpression[String] - case None => new ConstantExpressionNode[String](f.is) with StringExpression[String] with SquerylRecordNonNumericalExpression[String] + case None => new ConstantExpressionNode[String](f.is)(createOutMapperStringType) with StringExpression[String] with SquerylRecordNonNumericalExpression[String] } /** Conversion of optional String fields to Squeryl Expressions. */ implicit def optionString2ScalarString(f: OptionalTypedField[String]) = fieldReference match { case Some(e) => new SelectElementReference[Option[String]](e)(createOutMapperStringTypeOption) with StringExpression[Option[String]] with SquerylRecordNonNumericalExpression[Option[String]] - case None => new ConstantExpressionNode[Option[String]](f.is) with StringExpression[Option[String]] with SquerylRecordNonNumericalExpression[Option[String]] + case None => new ConstantExpressionNode[Option[String]](f.is)(createOutMapperStringTypeOption) with StringExpression[Option[String]] with SquerylRecordNonNumericalExpression[Option[String]] } /** Needed for outer joins */ implicit def optionStringField2OptionString(f: Option[TypedField[String]]) = fieldReference match { case Some(e) => new SelectElementReference[String](e)(createOutMapperStringType) with StringExpression[String] with SquerylRecordNonNumericalExpression[String] - case None => new ConstantExpressionNode[String](getValueOrNull(f)) with StringExpression[String] with SquerylRecordNonNumericalExpression[String] + case None => new ConstantExpressionNode[String](getValueOrNull(f))(createOutMapperStringType) with StringExpression[String] with SquerylRecordNonNumericalExpression[String] } /** Conversion of mandatory Boolean fields to Squeryl Expressions. */ implicit def bool2ScalarBoolean(f: MandatoryTypedField[Boolean]) = fieldReference match { case Some(e) => new SelectElementReference[Boolean](e)(createOutMapperBooleanType) with BooleanExpression[Boolean] with SquerylRecordNonNumericalExpression[Boolean] - case None => new ConstantExpressionNode[Boolean](f.is) with BooleanExpression[Boolean] with SquerylRecordNonNumericalExpression[Boolean] + case None => new ConstantExpressionNode[Boolean](f.is)(createOutMapperBooleanType) with BooleanExpression[Boolean] with SquerylRecordNonNumericalExpression[Boolean] } /** Conversion of optional Boolean fields to Squeryl Expressions. */ implicit def optionBoolean2ScalarBoolean(f: OptionalTypedField[Boolean]) = fieldReference match { case Some(e) => new SelectElementReference[Option[Boolean]](e)(createOutMapperBooleanTypeOption) with BooleanExpression[Option[Boolean]] with SquerylRecordNonNumericalExpression[Option[Boolean]] - case None => new ConstantExpressionNode[Option[Boolean]](f.is) with BooleanExpression[Option[Boolean]] with SquerylRecordNonNumericalExpression[Option[Boolean]] + case None => new ConstantExpressionNode[Option[Boolean]](f.is)(createOutMapperBooleanTypeOption) with BooleanExpression[Option[Boolean]] with SquerylRecordNonNumericalExpression[Option[Boolean]] } /** Needed for outer joins. */ implicit def optionBooleanField2Boolean(f: Option[TypedField[Boolean]]) = fieldReference match { case Some(e) => new SelectElementReference[Boolean](e)(createOutMapperBooleanType) with BooleanExpression[Boolean] with SquerylRecordNonNumericalExpression[Boolean] - case None => new ConstantExpressionNode[Boolean](getValue(f).getOrElse(false)) with BooleanExpression[Boolean] with SquerylRecordNonNumericalExpression[Boolean] + case None => new ConstantExpressionNode[Boolean](getValue(f).getOrElse(false))(createOutMapperBooleanType) with BooleanExpression[Boolean] with SquerylRecordNonNumericalExpression[Boolean] } /** Conversion of mandatory Calendar fields to Squeryl Expressions. */ implicit def date2ScalarDate(f: MandatoryTypedField[Calendar]) = fieldReference match { case Some(e) => new SelectElementReference[Timestamp](e)(createOutMapperTimestampType) with DateExpression[Timestamp] with SquerylRecordNonNumericalExpression[Timestamp] - case None => new ConstantExpressionNode[Timestamp](new Timestamp(f.is.getTimeInMillis)) with DateExpression[Timestamp] with SquerylRecordNonNumericalExpression[Timestamp] + case None => new ConstantExpressionNode[Timestamp](new Timestamp(f.is.getTimeInMillis))(createOutMapperTimestampType) with DateExpression[Timestamp] with SquerylRecordNonNumericalExpression[Timestamp] } /** Conversion of optional Calendar fields to Squeryl Expressions. */ @@ -122,7 +122,7 @@ trait RecordTypeMode extends PrimitiveTypeMode { case Some(calendar) => Some(new Timestamp(calendar.getTimeInMillis)) case None => None } - new ConstantExpressionNode[Option[Timestamp]](date) with DateExpression[Option[Timestamp]] with SquerylRecordNonNumericalExpression[Option[Timestamp]] + new ConstantExpressionNode[Option[Timestamp]](date)(createOutMapperTimestampTypeOption) with DateExpression[Option[Timestamp]] with SquerylRecordNonNumericalExpression[Option[Timestamp]] } } @@ -133,7 +133,7 @@ trait RecordTypeMode extends PrimitiveTypeMode { /** Needed for outer joins. */ implicit def optionDateField2OptionDate(f: Option[TypedField[Calendar]]) = fieldReference match { case Some(e) => new SelectElementReference[Timestamp](e)(createOutMapperTimestampType) with DateExpression[Timestamp] with SquerylRecordNonNumericalExpression[Timestamp] - case None => new ConstantExpressionNode[Timestamp](getValue(f).map(field => new Timestamp(field.getTimeInMillis)).orNull) with DateExpression[Timestamp] with SquerylRecordNonNumericalExpression[Timestamp] + case None => new ConstantExpressionNode[Timestamp](getValue(f).map(field => new Timestamp(field.getTimeInMillis)).orNull)(createOutMapperTimestampType) with DateExpression[Timestamp] with SquerylRecordNonNumericalExpression[Timestamp] } /** Needed for inner queries on date fields */ @@ -149,24 +149,29 @@ trait RecordTypeMode extends PrimitiveTypeMode { /** * Neeed for queries on constant date values. */ - implicit def dateToTimestampExpression(d: java.util.Date) = new ConstantExpressionNode[Timestamp](new java.sql.Timestamp(d.getTime)) with DateExpression[Timestamp] with SquerylRecordNonNumericalExpression[Timestamp] + implicit def dateToTimestampExpression(d: java.util.Date) = + new ConstantExpressionNode[Timestamp](new java.sql.Timestamp(d.getTime))(createOutMapperTimestampType) with DateExpression[Timestamp] with SquerylRecordNonNumericalExpression[Timestamp] /** Conversion of mandatory Enum fields to Squeryl Expressions. */ implicit def enum2EnumExpr[EnumType <: Enumeration](f: MandatoryTypedField[EnumType#Value]) = fieldReference match { case Some(e) => new SelectElementReference[Enumeration#Value](e)(e.createEnumerationMapper) with EnumExpression[Enumeration#Value] with SquerylRecordNonNumericalExpression[Enumeration#Value] - case None => new ConstantExpressionNode[Enumeration#Value](f.is) with EnumExpression[Enumeration#Value] with SquerylRecordNonNumericalExpression[Enumeration#Value] + case None => new ConstantExpressionNode[Enumeration#Value](f.is)(outMapperFromEnumValue(f.get)) with EnumExpression[Enumeration#Value] with SquerylRecordNonNumericalExpression[Enumeration#Value] } /** Conversion of optional Enum fields to Squeryl Expressions. */ implicit def optionEnum2ScalaEnum[EnumType <: Enumeration](f: OptionalTypedField[EnumType#Value]) = fieldReference match { case Some(e) => new SelectElementReference[Option[Enumeration#Value]](e)(e.createEnumerationOptionMapper) with EnumExpression[Option[Enumeration#Value]] with SquerylRecordNonNumericalExpression[Option[Enumeration#Value]] - case None => new ConstantExpressionNode[Option[Enumeration#Value]](f.is) with EnumExpression[Option[Enumeration#Value]] with SquerylRecordNonNumericalExpression[Option[Enumeration#Value]] + case None => new ConstantExpressionNode[Option[Enumeration#Value]](f.is)(outMapperOptionFromOptionEnumValue(f.get) orNull) with EnumExpression[Option[Enumeration#Value]] with SquerylRecordNonNumericalExpression[Option[Enumeration#Value]] } /** Needed for outer joins. */ implicit def optionEnumField2OptionEnum[EnumType <: Enumeration](f: Option[TypedField[EnumType#Value]]) = fieldReference match { case Some(e) => new SelectElementReference[Enumeration#Value](e)(e.createEnumerationMapper) with EnumExpression[Enumeration#Value] with SquerylRecordNonNumericalExpression[Enumeration#Value] - case None => new ConstantExpressionNode[Enumeration#Value](getValue(f).orNull) with EnumExpression[Enumeration#Value] with SquerylRecordNonNumericalExpression[Enumeration#Value] + case None => new ConstantExpressionNode[Enumeration#Value](getValue(f).orNull)({ + val enumOption = f flatMap { f1: TypedField[EnumType#Value] => f1.valueBox.toOption } + val outMapperOption: Option[OutMapper[Enumeration#Value]] = enumOption map { e: EnumType#Value => outMapperFromEnumValue(e) : OutMapper[Enumeration#Value] /*crashes scala 2.9.1 without explicit type */ } + outMapperOption orNull + }) with EnumExpression[Enumeration#Value] with SquerylRecordNonNumericalExpression[Enumeration#Value] } implicit def enumFieldQuery2RightHandSideOfIn[EnumType <: Enumeration, T <: Record[T]](q: org.squeryl.Query[EnumNameField[T, EnumType]]) = new RightHandSideOfIn[Enumeration#Value](q.ast) @@ -184,7 +189,7 @@ trait RecordTypeMode extends PrimitiveTypeMode { */ private def convertNumericalMandatory[T](f: MandatoryTypedField[T], outMapper: OutMapper[T]) = fieldReference match { case Some(e) => new SelectElementReference[T](e)(outMapper) with NumericalExpression[T] with SquerylRecordNumericalExpression[T] - case None => new ConstantExpressionNode[T](f.is) with NumericalExpression[T] with SquerylRecordNumericalExpression[T] + case None => new ConstantExpressionNode[T](f.is)(outMapper) with NumericalExpression[T] with SquerylRecordNumericalExpression[T] } /** @@ -192,12 +197,12 @@ trait RecordTypeMode extends PrimitiveTypeMode { */ private def convertNumericalOptional[T](f: OptionalTypedField[T], outMapper: OutMapper[Option[T]]) = fieldReference match { case Some(e: SelectElement) => new SelectElementReference[Option[T]](e)(outMapper) with NumericalExpression[Option[T]] with SquerylRecordNumericalExpression[Option[T]] - case None => new ConstantExpressionNode[Option[T]](f.is) with NumericalExpression[Option[T]] with SquerylRecordNumericalExpression[Option[T]] + case None => new ConstantExpressionNode[Option[T]](f.is)(outMapper) with NumericalExpression[Option[T]] with SquerylRecordNumericalExpression[Option[T]] } private def convertNumericalOption[T](f: Option[TypedField[T]], outMapper: OutMapper[Option[T]]) = fieldReference match { case Some(e) => new SelectElementReference[Option[T]](e)(outMapper) with NumericalExpression[Option[T]] with SquerylRecordNumericalExpression[Option[T]] - case None => new ConstantExpressionNode[Option[T]](getValue(f)) with NumericalExpression[Option[T]] with SquerylRecordNumericalExpression[Option[T]] + case None => new ConstantExpressionNode[Option[T]](getValue(f))(outMapper) with NumericalExpression[Option[T]] with SquerylRecordNumericalExpression[Option[T]] } private def getValue[T](f: Option[TypedField[T]]): Option[T] = f match { diff --git a/persistence/squeryl-record/src/main/scala/net/liftweb/squerylrecord/SquerylRecord.scala b/persistence/squeryl-record/src/main/scala/net/liftweb/squerylrecord/SquerylRecord.scala index cffae0dae2..05c4049a82 100644 --- a/persistence/squeryl-record/src/main/scala/net/liftweb/squerylrecord/SquerylRecord.scala +++ b/persistence/squeryl-record/src/main/scala/net/liftweb/squerylrecord/SquerylRecord.scala @@ -50,16 +50,17 @@ object SquerylRecord extends Loggable { */ def init(mkAdapter: () => DatabaseAdapter) = { FieldMetaData.factory = new RecordMetaDataFactory - SessionFactory.externalTransactionManagementAdapter = Some(() => currentSession.is openOr { - DB.currentConnection match { - case Full(superConn) => - val sess = Session.create(superConn.connection, mkAdapter()) - sess.setLogger(s => logger.debug(s)) - currentSession.set(sess) - sess - - case _ => error("no current connection in scope. wrap your transaction with DB.use or use one of the DB loan wrappers") - } - }) + SessionFactory.externalTransactionManagementAdapter = Some(() => + currentSession.get orElse { + DB.currentConnection match { + case Full(superConn) => + val sess: Session = Session.create(superConn.connection, mkAdapter()) + sess.setLogger(s => logger.info(s)) + currentSession.set(sess) + Some(sess) + case _ => None + } + }) } + } diff --git a/persistence/squeryl-record/src/test/scala/net/liftweb/squerylrecord/SquerylRecordSpec.scala b/persistence/squeryl-record/src/test/scala/net/liftweb/squerylrecord/SquerylRecordSpec.scala index 95db48b7b9..d31ddcaa57 100644 --- a/persistence/squeryl-record/src/test/scala/net/liftweb/squerylrecord/SquerylRecordSpec.scala +++ b/persistence/squeryl-record/src/test/scala/net/liftweb/squerylrecord/SquerylRecordSpec.scala @@ -367,11 +367,14 @@ object SquerylRecordSpec extends Specification("SquerylRecord Specification") { * back the transaction afterwards. */ private def transactionWithRollback[T](code: => T): T = { + + def rollback: Unit = throw new TransactionRollbackException() + var result: T = null.asInstanceOf[T] try { transaction { result = code - throw new TransactionRollbackException() + rollback } } catch { case e: TransactionRollbackException => // OK, was rolled back diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 9eb99e0704..2113a1f074 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -49,7 +49,7 @@ object Dependencies { lazy val scalap: ModuleMap = "org.scala-lang" % "scalap" % _ lazy val scalaz_core: ModuleMap = sv => scalazGroup(sv) % "scalaz-core" % scalazVersion(sv) cross CVMappingAll lazy val slf4j_api = "org.slf4j" % "slf4j-api" % slf4jVersion - lazy val squeryl = "org.squeryl" % "squeryl" % "0.9.4" cross CVMappingAll // TODO: 0.9.5 + lazy val squeryl = "org.squeryl" % "squeryl" % "0.9.5" cross CVMappingAll // TODO: 0.9.5 // Aliases lazy val mongo_driver = mongo_java_driver From e36742dba4abfa3aade61bb1b5c9a4758e2b963e Mon Sep 17 00:00:00 2001 From: Tyler Weir Date: Thu, 3 May 2012 12:04:02 -0400 Subject: [PATCH 0050/1949] add ajaxuntrustedSelect --- .../main/scala/net/liftweb/http/SHtml.scala | 253 +++++++++++------- 1 file changed, 160 insertions(+), 93 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala b/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala index 05cf3dfc0b..50f3e46dbf 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala @@ -65,18 +65,18 @@ trait SHtml { type EnumerationTypeWorkaround = Enumeration#Value - - implicit def enumToStrValPromo[EnumerationTypeWorkaround]: SHtml.PairStringPromoter[EnumerationTypeWorkaround] = + + implicit def enumToStrValPromo[EnumerationTypeWorkaround]: SHtml.PairStringPromoter[EnumerationTypeWorkaround] = new SHtml.PairStringPromoter[EnumerationTypeWorkaround]{ def apply(in: EnumerationTypeWorkaround): String = in.toString - } + } } /** * An attribute that can be applied to an element. Typically, * this will be a key-value pair, but there is a class of HTML5 - * attributes that should be similated in JavaScript. + * attributes that should be similated in JavaScript. */ trait ElemAttr extends Function1[Elem, Elem] { /** @@ -89,10 +89,10 @@ trait SHtml { * The companion object that has some very helpful conversion */ object ElemAttr { - implicit def pairToBasic(in: (String, String)): ElemAttr = + implicit def pairToBasic(in: (String, String)): ElemAttr = new BasicElemAttr(in._1, in._2) - implicit def funcToElemAttr(f: Elem => Elem): ElemAttr = + implicit def funcToElemAttr(f: Elem => Elem): ElemAttr = new ElemAttr{def apply(in: Elem): Elem = f(in)} implicit def strSeqToElemAttr(in: Seq[(String, String)]): @@ -150,7 +150,7 @@ trait SHtml { /** * Build a JavaScript function that will perform an AJAX call based on a value calculated in JavaScript - * + * * @param jsCalcValue the JavaScript that will be executed on the client to calculate the value to be sent to the server * @param func the function to call when the data is sent * @@ -160,7 +160,7 @@ trait SHtml { /** * Build a JavaScript function that will perform an AJAX call based on a value calculated in JavaScript - * + * * @param jsCalcValue the JavaScript that will be executed on the client to calculate the value to be sent to the server * @param jsContext the context instance that defines JavaScript to be executed on call success or failure * @param func the function to call when the data is sent @@ -200,7 +200,7 @@ trait SHtml { /** * Build a JavaScript function that will perform a JSON call based on a value calculated in JavaScript - * + * * @param jsCalcValue the JavaScript to calculate the value to be sent to the server * @param func the function to call when the data is sent * @@ -212,7 +212,7 @@ trait SHtml { /** * Build a JavaScript function that will perform a JSON call based on a value calculated in JavaScript - * + * * @param jsCalcValue the JavaScript to calculate the value to be sent to the server * @param jsContext the context instance that defines JavaScript to be executed on call success or failure * @param func the function to call when the data is sent @@ -333,7 +333,7 @@ trait SHtml { def idMemoize(f: IdMemoizeTransform => NodeSeqFuncOrSeqNodeSeqFunc): IdMemoizeTransform = { new IdMemoizeTransform { var latestElem: Elem = - + var latestKids: NodeSeq = NodeSeq.Empty var latestId = Helpers.nextFuncName @@ -345,12 +345,12 @@ trait SHtml { } } - def apply(ns: NodeSeq): NodeSeq = + def apply(ns: NodeSeq): NodeSeq = Helpers.findBox(ns){e => latestElem = fixElem(e); latestKids = e.child; Full(e)}. map(ignore => applyAgain()).openOr(NodeSeq.Empty) - def applyAgain(): NodeSeq = + def applyAgain(): NodeSeq = new Elem(latestElem.prefix, latestElem.label, latestElem.attributes, @@ -608,11 +608,11 @@ trait SHtml { * * @return a text field */ - def jsonText(value: String, ignoreBlur: Boolean, json: JsExp => JsCmd, attrs: ElemAttr*): Elem = + def jsonText(value: String, ignoreBlur: Boolean, json: JsExp => JsCmd, attrs: ElemAttr*): Elem = (attrs.foldLeft( "" case s => s}}/>)(_ % _)) % ("onkeypress" -> """liftUtils.lift_blurIfReturn(event)""") % (if (ignoreBlur) Null else ("onblur" -> (json(JE.JsRaw("this.value"))))) - + /** @@ -644,10 +644,10 @@ trait SHtml { def ajaxTextElem(settable: Settable{type ValueType = String}, attrs: ElemAttr*): Elem = ajaxText(settable.get, (b: String) => {settable.set(b); Noop}, attrs :_*) - def ajaxText(value: String, func: String => JsCmd, attrs: ElemAttr*): Elem = + def ajaxText(value: String, func: String => JsCmd, attrs: ElemAttr*): Elem = ajaxText_*(value, false, Empty, SFuncHolder(func), attrs: _*) - def ajaxText(value: String, jsFunc: Call, func: String => JsCmd, attrs: ElemAttr*): Elem = + def ajaxText(value: String, jsFunc: Call, func: String => JsCmd, attrs: ElemAttr*): Elem = ajaxText_*(value, false, Full(jsFunc), SFuncHolder(func), attrs: _*) def ajaxText(value: String, ignoreBlur: Boolean, func: String => JsCmd, attrs: ElemAttr*): Elem = @@ -658,7 +658,7 @@ trait SHtml { private def ajaxText_*(valuePreNull: String, ignoreBlur: Boolean, jsFunc: Box[Call], func: AFuncHolder, attrs: ElemAttr*): Elem = { val value = (Box !! valuePreNull).openOr("") - + val raw = (funcName: String, value: String) => JsRaw("'" + funcName + "=' + encodeURIComponent(" + value + ".value)") val key = formFuncName @@ -687,10 +687,10 @@ trait SHtml { * * @return a text area field */ - def jsonTextarea(value: String, json: JsExp => JsCmd, attrs: ElemAttr*): Elem = + def jsonTextarea(value: String, json: JsExp => JsCmd, attrs: ElemAttr*): Elem = (attrs.foldLeft()(_ % _)) % ("onblur" -> (json(JE.JsRaw("this.value")))) - + /** * Create a JSON text area widget that makes a JSON call on blur @@ -704,10 +704,10 @@ trait SHtml { def jsonTextarea(value: String, cmd: String, json: JsonCall, attrs: ElemAttr*): Elem = jsonTextarea(value, exp => json(cmd, exp), attrs: _*) - def ajaxTextarea(value: String, func: String => JsCmd, attrs: ElemAttr*): Elem = + def ajaxTextarea(value: String, func: String => JsCmd, attrs: ElemAttr*): Elem = ajaxTextarea_*(value, Empty, SFuncHolder(func), attrs: _*) - def ajaxTextarea(value: String, jsFunc: Call, func: String => JsCmd, attrs: ElemAttr*): Elem = + def ajaxTextarea(value: String, jsFunc: Call, func: String => JsCmd, attrs: ElemAttr*): Elem = ajaxTextarea_*(value, Full(jsFunc), SFuncHolder(func), attrs: _*) private def ajaxTextarea_*(value: String, jsFunc: Box[Call], func: AFuncHolder, attrs: ElemAttr*): Elem = { @@ -791,8 +791,8 @@ trait SHtml { ajaxCheckbox_*(value, Empty, LFuncHolder(in => func(in.exists(toBoolean(_)))), attrs: _*) def ajaxCheckboxElem(settable: Settable{type ValueType = Boolean}, jsFunc: Call, attrs: ElemAttr*): Elem = - ajaxCheckbox_*(settable.get, Full(jsFunc), - LFuncHolder(in => {settable.set(in.exists(toBoolean( _))); + ajaxCheckbox_*(settable.get, Full(jsFunc), + LFuncHolder(in => {settable.set(in.exists(toBoolean( _))); Noop}), attrs: _*) def ajaxCheckbox(value: Boolean, jsFunc: Call, func: Boolean => JsCmd, attrs: ElemAttr*): Elem = @@ -826,9 +826,9 @@ trait SHtml { val itemList = opts.map{ v => { ChoiceItem(v, attrs.foldLeft()(_ % _) % + value={Helpers.nextFuncName}/>)(_ % _) % checked(deflt == Full(v)) % - ("onclick" -> ajaxCall(Str(""), + ("onclick" -> ajaxCall(Str(""), ignore => ajaxFunc(v))._2.toJsCmd)) } } @@ -888,7 +888,7 @@ trait SHtml { jsFunc: Call, attrs: ElemAttr*) (onSubmit: T => JsCmd) - (implicit f: PairStringPromoter[T]): Elem = + (implicit f: PairStringPromoter[T]): Elem = { ajaxSelectObj[T](options.map(v => (v, f(v))), default, jsFunc, onSubmit) @@ -1038,7 +1038,7 @@ trait SHtml { private def dupWithName(elem: Elem, name: String): Elem = { new Elem(elem.prefix, elem.label, - new UnprefixedAttribute("name", name, + new UnprefixedAttribute("name", name, elem.attributes.filter { case up: UnprefixedAttribute => up.key != "name" @@ -1048,10 +1048,10 @@ trait SHtml { elem.child :_*) } - private def isRadio(in: MetaData): Boolean = + private def isRadio(in: MetaData): Boolean = in.get("type").map(_.text equalsIgnoreCase "radio") getOrElse false - private def isCheckbox(in: MetaData): Boolean = + private def isCheckbox(in: MetaData): Boolean = in.get("type").map(_.text equalsIgnoreCase "checkbox") getOrElse false @@ -1068,7 +1068,7 @@ trait SHtml { val allEvent = List("href") ns => { - def runNodes(in: NodeSeq): NodeSeq = + def runNodes(in: NodeSeq): NodeSeq = in.flatMap { case Group(g) => runNodes(g) // button @@ -1084,17 +1084,17 @@ trait SHtml { } fmapFunc(func) { - funcName => + funcName => new Elem(e.prefix, e.label, allEvent.foldLeft(newAttr){ case (meta, attr) => new UnprefixedAttribute(attr, Helpers. appendFuncToURL(oldAttr. - getOrElse(attr, ""), + getOrElse(attr, ""), funcName+"=_"), meta) - + }, e.scope, e.child :_*) } } @@ -1102,7 +1102,7 @@ trait SHtml { case x => x } - + runNodes(ns) } } @@ -1114,7 +1114,7 @@ trait SHtml { * "input [onblur]" #> SHtml.onEvent(s => Alert("Thanks: "+s)) * */ - def onEvent(func: String => JsCmd): GUIDJsExp = + def onEvent(func: String => JsCmd): GUIDJsExp = ajaxCall(JsRaw("this.value"), func) /** @@ -1130,7 +1130,7 @@ trait SHtml { NodeSeq => NodeSeq = { val allEvent = event :: events.toList ns => { - def runNodes(in: NodeSeq): NodeSeq = + def runNodes(in: NodeSeq): NodeSeq = in.flatMap { case Group(g) => runNodes(g) // button @@ -1146,7 +1146,7 @@ trait SHtml { } val cmd = ajaxCall(JsRaw("this.value"), func)._2.toJsCmd - + new Elem(e.prefix, e.label, allEvent.foldLeft(newAttr){ case (meta, attr) => @@ -1154,14 +1154,14 @@ trait SHtml { oldAttr.getOrElse(attr, "") + cmd, meta) - + }, e.scope, e.child :_*) } case x => x } - + runNodes(ns) } } @@ -1172,7 +1172,7 @@ trait SHtml { * form fields (input, button, textarea, select) and the * function is executed when the form containing the field is submitted. */ - def onSubmitUnit(func: () => Any): NodeSeq => NodeSeq = + def onSubmitUnit(func: () => Any): NodeSeq => NodeSeq = onSubmitImpl(func: AFuncHolder) /** @@ -1191,7 +1191,7 @@ trait SHtml { * form fields (input, button, textarea, select) and the * function is executed when the form containing the field is submitted. */ - def onSubmitList(func: List[String] => Any): NodeSeq => NodeSeq = + def onSubmitList(func: List[String] => Any): NodeSeq => NodeSeq = onSubmitImpl(func: AFuncHolder) /** @@ -1200,7 +1200,7 @@ trait SHtml { * form fields (input, button, textarea, select) and the * function is executed when the form containing the field is submitted. */ - def onSubmitBoolean(func: Boolean => Any): NodeSeq => NodeSeq = + def onSubmitBoolean(func: Boolean => Any): NodeSeq => NodeSeq = onSubmitImpl(func: AFuncHolder) /** @@ -1217,27 +1217,27 @@ trait SHtml { var checkBoxName: Box[String] = Empty var checkBoxCnt = 0 - def runNodes(in: NodeSeq): NodeSeq = + def runNodes(in: NodeSeq): NodeSeq = in.flatMap { case Group(g) => runNodes(g) // button - case e: Elem if e.label == "button" => + case e: Elem if e.label == "button" => _formGroup.is match { - case Empty => + case Empty => formGroup(1)(fmapFunc(func) {dupWithName(e, _)}) case _ => fmapFunc(func) {dupWithName(e, _)} } // textarea - case e: Elem if e.label == "textarea" => + case e: Elem if e.label == "textarea" => fmapFunc(func) {dupWithName(e, _)} // select - case e: Elem if e.label == "select" => + case e: Elem if e.label == "select" => fmapFunc(func) {dupWithName(e, _)} // radio - case e: Elem if e.label == "input" && isRadio(e.attributes) => + case e: Elem if e.label == "input" && isRadio(e.attributes) => radioName match { case Full(name) => dupWithName(e, name) case _ => @@ -1248,9 +1248,9 @@ trait SHtml { } } } - + // checkbox - case e: Elem if e.label == "input" && isCheckbox(e.attributes) => + case e: Elem if e.label == "input" && isCheckbox(e.attributes) => checkBoxName match { case Full(name) => checkBoxCnt += 1 @@ -1266,26 +1266,26 @@ trait SHtml { } // submit - case e: Elem if e.label == "input" && e.attribute("type").map(_.text) == Some("submit") => + case e: Elem if e.label == "input" && e.attribute("type").map(_.text) == Some("submit") => _formGroup.is match { - case Empty => + case Empty => formGroup(1)(fmapFunc(func) {dupWithName(e, _)}) case _ => fmapFunc(func) {dupWithName(e, _)} } // generic input - case e: Elem if e.label == "input" => + case e: Elem if e.label == "input" => fmapFunc(func) {dupWithName(e, _)} case x => x } val ret = runNodes(in) - + checkBoxName match { // if we've got a single checkbox, add a hidden false checkbox case Full(name) if checkBoxCnt == 1 => { - ret ++ + ret ++ } case _ => ret @@ -1346,7 +1346,7 @@ trait SHtml { */ def number(value: Int, func: Int => Any, min: Int, max: Int, attrs: ElemAttr*): Elem = - number_*(value, + number_*(value, min, max, SFuncHolder(s => Helpers.asInt(s).map(func)), attrs: _*) @@ -1360,17 +1360,17 @@ trait SHtml { number_*(settable.get, min, max, SFuncHolder(s => Helpers.asInt(s).map(s => settable.set(s))), attrs: _*) - + private def number_*(value: Int, min: Int, max: Int, func: AFuncHolder, attrs: ElemAttr*): Elem = { import Helpers._ - makeFormElement("number", + makeFormElement("number", func, - attrs: _*) % + attrs: _*) % ("value" -> value.toString) % - ("min" -> min.toString) % + ("min" -> min.toString) % ("max" -> max.toString) } @@ -1380,7 +1380,7 @@ trait SHtml { */ def range(value: Int, func: Int => Any, min: Int, max: Int, attrs: ElemAttr*): Elem = - range_*(value, + range_*(value, min, max, SFuncHolder(s => Helpers.asInt(s).map(func)), attrs: _*) @@ -1394,17 +1394,17 @@ trait SHtml { range_*(settable.get, min, max, SFuncHolder(s => Helpers.asInt(s).map(s => settable.set(s))), attrs: _*) - + private def range_*(value: Int, min: Int, max: Int, func: AFuncHolder, attrs: ElemAttr*): Elem = { import Helpers._ - makeFormElement("range", + makeFormElement("range", func, - attrs: _*) % + attrs: _*) % ("value" -> value.toString) % - ("min" -> min.toString) % + ("min" -> min.toString) % ("max" -> max.toString) } @@ -1453,7 +1453,7 @@ trait SHtml { case _ => doit } } - + /** * Generates a form submission button. * @@ -1477,7 +1477,7 @@ trait SHtml { /** * Constructs an Ajax submit button that can be used inside ajax forms. * Multiple buttons can be used in the same form. - * + * * @param value - the button text * @param func - the ajax function to be called * @param attrs - button attributes @@ -1490,7 +1490,7 @@ trait SHtml { (attrs.foldLeft()(_ % _)) % new UnprefixedAttribute("value", Text(value), Null) % ("onclick" -> ("liftAjax.lift_uriSuffix = '"+funcName+"=_'; return true;")) - + } /** @@ -1580,12 +1580,12 @@ trait SHtml { * Vend a function that will take all of the form elements and turns them * into Ajax forms */ - def makeFormsAjax: NodeSeq => NodeSeq = "form" #> ((ns: NodeSeq) => + def makeFormsAjax: NodeSeq => NodeSeq = "form" #> ((ns: NodeSeq) => (ns match { case e: Elem => { val id: String = e.attribute("id").map(_.text) getOrElse Helpers.nextFuncName - + val newMeta = e.attributes.filter{ case up: UnprefixedAttribute => up.key match { @@ -1598,26 +1598,26 @@ trait SHtml { case _ => true } - new Elem(e.prefix, e.label, + new Elem(e.prefix, e.label, newMeta, e.scope, e.child :_*) % ("id" -> id) % - ("action" -> "javascript://") % - ("onsubmit" -> + ("action" -> "javascript://") % + ("onsubmit" -> (SHtml.makeAjaxCall(LiftRules.jsArtifacts.serialize(id)).toJsCmd + "; return false;")) } case x => x }): NodeSeq) - /** + /** * Submits a form denominated by a formId and execute the func function * after form fields functions are executed. - */ + */ def submitAjaxForm(formId: String, func: () => JsCmd): JsCmd = { val funcName = "Z" + Helpers.nextFuncName addFunctionMap(funcName, (func)) makeAjaxCall(JsRaw( - LiftRules.jsArtifacts.serialize(formId).toJsCmd + " + " + + LiftRules.jsArtifacts.serialize(formId).toJsCmd + " + " + Str("&" + funcName + "=true").toJsCmd)) } @@ -1666,7 +1666,7 @@ trait SHtml { */ def selectElem[T](options: Seq[T], default: Box[T], attrs: ElemAttr*) (onSubmit: T => Any) - (implicit f: PairStringPromoter[T]): + (implicit f: PairStringPromoter[T]): Elem = { selectObj[T](options.map(v => (v, f(v))), default, onSubmit, attrs :_*) } @@ -1683,12 +1683,12 @@ trait SHtml { * @param onSubmit -- the function to execute on form submission * @param f -- the function that converts a T to a Display String. */ - def selectElem[T](options: Seq[T], - settable: LiftValue[T], + def selectElem[T](options: Seq[T], + settable: LiftValue[T], attrs: ElemAttr*) - (implicit f: PairStringPromoter[T]): + (implicit f: PairStringPromoter[T]): Elem = { - selectObj[T](options.map(v => (v, f(v))), Full(settable.get), + selectObj[T](options.map(v => (v, f(v))), Full(settable.get), s => settable.set(s), attrs :_*) } @@ -1786,6 +1786,73 @@ trait SHtml { } } + /** + * Create a select box based on the list with a default value and the function to be executed on + * form submission. No check is made to see if the resulting value was in the original list. + * For use with DHTML form updating. + * + * @param opts -- the options. A list of value and text pairs + * @param deflt -- the default value (or Empty if no default value) + * @param func -- the function to execute on form submission + * @param attrs -- select box attributes + */ + def ajaxUntrustedSelect(opts: Seq[(String, String)], + deflt: Box[String], + func: String => JsCmd, + attrs: (String, String)*): Elem = + ajaxUntrustedSelect_*(opts, deflt, Empty, SFuncHolder(func), attrs: _*) + + /** + * Create a select box based on the list with a default value and the function to be executed on + * form submission. No check is made to see if the resulting value was in the original list. + * For use with DHTML form updating. + * + * @param opts -- the options. A list of value and text pairs + * @poram jsFunc -- user provided function + * @param deflt -- the default value (or Empty if no default value) + * @param func -- the function to execute on form submission + * @param attrs -- select box attributes + */ + def ajaxUntrustedSelect(opts: Seq[(String, String)], + deflt: Box[String], + jsFunc: Call, + func: String => JsCmd, + attrs: (String, String)*): Elem = + ajaxUntrustedSelect_*(opts, deflt, Full(jsFunc), SFuncHolder(func), attrs: _*) + + /** + * Create a select box based on the list with a default value and the function to be executed on + * form submission. No check is made to see if the resulting value was in the original list. + * For use with DHTML form updating. + * + * @param opts -- the options. A list of value and text pairs + * @param deflt -- the default value (or Empty if no default value) + * @poram jsFunc -- user provided function + * @param func -- the function to execute on form submission + * @param attrs -- select box attributes + */ + private def ajaxUntrustedSelect_*(opts: Seq[(String, String)], + deflt: Box[String], + jsFunc: Box[Call], + func: AFuncHolder, + attrs: (String, String)*): Elem = + { + val raw = (funcName: String, value: String) => JsRaw("'" + funcName + "=' + this.options[" + value + ".selectedIndex].value") + val key = formFuncName + + val vals = opts.map(_._1) + + val testFunc = LFuncHolder(in => in match { case Nil => false case xs => func(xs) }, func.owner) + fmapFunc(contextFuncBuilder(testFunc)) { + import net.liftweb.http.js.JsCmds.JsCrVar + funcName => + (attrs.foldLeft()(_ % _)) % + ("onchange" -> (jsFunc match { + case Full(f) => JsCrVar(key, JsRaw("this")) & deferCall(raw(funcName, key), f) + case _ => makeAjaxCall(raw(funcName, "this")) + })) + } + } private def selected(in: Boolean) = if (in) new UnprefixedAttribute("selected", "selected", Null) else Null @@ -1804,7 +1871,7 @@ trait SHtml { def multiSelectElem[T](options: Seq[T], default: Seq[T], attrs: ElemAttr*) (onSubmit: List[T] => Any) (implicit f: PairStringPromoter[T]): Elem = { - multiSelectObj[T](options.map(v => (v, f(v))), default, + multiSelectObj[T](options.map(v => (v, f(v))), default, onSubmit, attrs :_*) } @@ -1849,7 +1916,7 @@ trait SHtml { def textarea(value: String, func: String => Any, attrs: ElemAttr*): Elem = textarea_*(value, SFuncHolder(func), attrs: _*) - def textareaElem(settable: Settable{type ValueType = String}, + def textareaElem(settable: Settable{type ValueType = String}, attrs: ElemAttr*): Elem = textarea_*(settable.get, SFuncHolder(s => settable.set(s)), attrs: _*) @@ -1879,7 +1946,7 @@ trait SHtml { name => { val items = possible.zipWithIndex.map { case ((id, value), idx) => { - val radio = + val radio = attrs.foldLeft()(_ % _) % checked(deflt.filter(_ == value).isDefined) @@ -1889,11 +1956,11 @@ trait SHtml { } else { radio } - + ChoiceItem(value, elem) } } - + ChoiceHolder(items) } } @@ -1925,7 +1992,7 @@ trait SHtml { */ def fileUpload(func: FileParamHolder => Any, attrs: ElemAttr*): Elem = { val f2: FileParamHolder => Any = fp => if (fp.length > 0) func(fp) - fmapFunc(BinFuncHolder(f2)) { name => + fmapFunc(BinFuncHolder(f2)) { name => attrs.foldLeft() { _ % _ } } } @@ -1954,7 +2021,7 @@ trait SHtml { def toForm: NodeSeq = flatMap(ChoiceHolder.htmlize) } - + object ChoiceHolder { /** Convert a ChoiceItem into a span containing the control and the toString of the key */ var htmlize: ChoiceItem[_] => NodeSeq = c => ({c.xhtml} {c.key.toString}
    ) @@ -2027,7 +2094,7 @@ trait SHtml { def checkbox_*(value: Boolean, func: AFuncHolder, id: Box[String], attrs: ElemAttr*): NodeSeq = { fmapFunc(func)(name => - (attrs.foldLeft()(_ % _) % checked(value) % setId(id)) ++ + (attrs.foldLeft()(_ % _) % checked(value) % setId(id)) ++ () ) } @@ -2111,7 +2178,7 @@ trait MemoizeTransform extends Function1[NodeSeq, NodeSeq] { */ trait IdMemoizeTransform extends Function1[NodeSeq, NodeSeq] { /** - * The latest ID of the outer + * The latest ID of the outer */ def latestId: String @@ -2119,7 +2186,7 @@ trait IdMemoizeTransform extends Function1[NodeSeq, NodeSeq] { * The outer Elem */ def latestElem: Elem - + /** * The children of the Elem */ @@ -2127,7 +2194,7 @@ trait IdMemoizeTransform extends Function1[NodeSeq, NodeSeq] { def applyAgain(): NodeSeq - + def setHtml(): JsCmd } From 9c53f7e002bcf9d0d97f74aaf7ccd03afdd2050a Mon Sep 17 00:00:00 2001 From: Dave Whittaker Date: Thu, 3 May 2012 12:57:52 -0400 Subject: [PATCH 0051/1949] It seems I had some issues with an older version of Squeryl 0.9.5 in my ivy cache when I put together #1257. This should fix any problems and get Jenkins building again. --- .../squerylrecord/RecordMetaDataFactory.scala | 8 +++---- .../squerylrecord/RecordTypeMode.scala | 24 +++++++++++++------ 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/persistence/squeryl-record/src/main/scala/net/liftweb/squerylrecord/RecordMetaDataFactory.scala b/persistence/squeryl-record/src/main/scala/net/liftweb/squerylrecord/RecordMetaDataFactory.scala index 2a58cb06c9..913c161be7 100644 --- a/persistence/squeryl-record/src/main/scala/net/liftweb/squerylrecord/RecordMetaDataFactory.scala +++ b/persistence/squeryl-record/src/main/scala/net/liftweb/squerylrecord/RecordMetaDataFactory.scala @@ -37,8 +37,8 @@ class RecordMetaDataFactory extends FieldMetaDataFactory { def fieldFrom(mr: MetaRecord[_]): BaseField = mr.asInstanceOf[Record[_]].fieldByName(name) match { case Full(f: BaseField) => f - case Full(_) => error("field " + name + " in Record metadata for " + clasz + " is not a TypedField") - case _ => error("failed to find field " + name + " in Record metadata for " + clasz) + case Full(_) => org.squeryl.internals.Utils.throwError("field " + name + " in Record metadata for " + clasz + " is not a TypedField") + case _ => org.squeryl.internals.Utils.throwError("failed to find field " + name + " in Record metadata for " + clasz) } metaRecordsByClass get clasz match { @@ -50,7 +50,7 @@ class RecordMetaDataFactory extends FieldMetaDataFactory { metaRecordsByClass = metaRecordsByClass updated (clasz, mr) fieldFrom(mr) } catch { - case ex => error("failed to find MetaRecord for " + clasz + " due to exception " + ex.toString) + case ex => org.squeryl.internals.Utils.throwError("failed to find MetaRecord for " + clasz + " due to exception " + ex.toString) } } } @@ -88,7 +88,7 @@ class RecordMetaDataFactory extends FieldMetaDataFactory { case (_: LocaleTypedField) => classOf[String] case (_: EnumTypedField[_]) => classOf[Int] case (_: EnumNameTypedField[_]) => classOf[String] - case _ => error("Unsupported field type. Consider implementing " + + case _ => org.squeryl.internals.Utils.throwError("Unsupported field type. Consider implementing " + "SquerylRecordField for defining the persistent class." + "Field: " + metaField) } diff --git a/persistence/squeryl-record/src/main/scala/net/liftweb/squerylrecord/RecordTypeMode.scala b/persistence/squeryl-record/src/main/scala/net/liftweb/squerylrecord/RecordTypeMode.scala index 2dd65246bb..57029a7be0 100644 --- a/persistence/squeryl-record/src/main/scala/net/liftweb/squerylrecord/RecordTypeMode.scala +++ b/persistence/squeryl-record/src/main/scala/net/liftweb/squerylrecord/RecordTypeMode.scala @@ -154,19 +154,29 @@ trait RecordTypeMode extends PrimitiveTypeMode { /** Conversion of mandatory Enum fields to Squeryl Expressions. */ implicit def enum2EnumExpr[EnumType <: Enumeration](f: MandatoryTypedField[EnumType#Value]) = fieldReference match { - case Some(e) => new SelectElementReference[Enumeration#Value](e)(e.createEnumerationMapper) with EnumExpression[Enumeration#Value] with SquerylRecordNonNumericalExpression[Enumeration#Value] + case Some(e) => new SelectElementReference[Enumeration#Value](e)(e.createEnumerationMapper(f.defaultValue)) with EnumExpression[Enumeration#Value] with SquerylRecordNonNumericalExpression[Enumeration#Value] case None => new ConstantExpressionNode[Enumeration#Value](f.is)(outMapperFromEnumValue(f.get)) with EnumExpression[Enumeration#Value] with SquerylRecordNonNumericalExpression[Enumeration#Value] } + + def reifySingleton[T](m: Manifest[T]) = { + val cls = m.erasure + val field = cls.getField("MODULE$") + field.get(null).asInstanceOf[T] + } /** Conversion of optional Enum fields to Squeryl Expressions. */ - implicit def optionEnum2ScalaEnum[EnumType <: Enumeration](f: OptionalTypedField[EnumType#Value]) = fieldReference match { - case Some(e) => new SelectElementReference[Option[Enumeration#Value]](e)(e.createEnumerationOptionMapper) with EnumExpression[Option[Enumeration#Value]] with SquerylRecordNonNumericalExpression[Option[Enumeration#Value]] - case None => new ConstantExpressionNode[Option[Enumeration#Value]](f.is)(outMapperOptionFromOptionEnumValue(f.get) orNull) with EnumExpression[Option[Enumeration#Value]] with SquerylRecordNonNumericalExpression[Option[Enumeration#Value]] - } + implicit def optionEnum2ScalaEnum[EnumType <: Enumeration](f: OptionalTypedField[EnumType#Value])(implicit m: Manifest[EnumType]) = + fieldReference match { + case Some(e) => + new SelectElementReference[Option[Enumeration#Value]](e)(e.createEnumerationOptionMapper(Some(reifySingleton(m).values.iterator.next))) with EnumExpression[Option[Enumeration#Value]] with SquerylRecordNonNumericalExpression[Option[Enumeration#Value]] + case None => + new ConstantExpressionNode[Option[Enumeration#Value]](f.is)(outMapperOptionFromOptionEnumValue(f.get) orNull) with EnumExpression[Option[Enumeration#Value]] with SquerylRecordNonNumericalExpression[Option[Enumeration#Value]] + } /** Needed for outer joins. */ - implicit def optionEnumField2OptionEnum[EnumType <: Enumeration](f: Option[TypedField[EnumType#Value]]) = fieldReference match { - case Some(e) => new SelectElementReference[Enumeration#Value](e)(e.createEnumerationMapper) with EnumExpression[Enumeration#Value] with SquerylRecordNonNumericalExpression[Enumeration#Value] + implicit def optionEnumField2OptionEnum[EnumType <: Enumeration](f: Option[TypedField[EnumType#Value]])(implicit m: Manifest[EnumType]) = fieldReference match { + case Some(e) => + new SelectElementReference[Enumeration#Value](e)(e.createEnumerationMapper(reifySingleton(m).values.iterator.next)) with EnumExpression[Enumeration#Value] with SquerylRecordNonNumericalExpression[Enumeration#Value] case None => new ConstantExpressionNode[Enumeration#Value](getValue(f).orNull)({ val enumOption = f flatMap { f1: TypedField[EnumType#Value] => f1.valueBox.toOption } val outMapperOption: Option[OutMapper[Enumeration#Value]] = enumOption map { e: EnumType#Value => outMapperFromEnumValue(e) : OutMapper[Enumeration#Value] /*crashes scala 2.9.1 without explicit type */ } From dfe67d950d3b05171dc7239ecb59f44726c63557 Mon Sep 17 00:00:00 2001 From: Dave Whittaker Date: Fri, 4 May 2012 15:18:42 -0400 Subject: [PATCH 0052/1949] Deprecated DB.use transaction support and added a buildLoanWrapper method for better integration with Squeryl transactions. --- .../liftweb/squerylrecord/SquerylRecord.scala | 40 +++++++++++++------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/persistence/squeryl-record/src/main/scala/net/liftweb/squerylrecord/SquerylRecord.scala b/persistence/squeryl-record/src/main/scala/net/liftweb/squerylrecord/SquerylRecord.scala index 05c4049a82..c79b512904 100644 --- a/persistence/squeryl-record/src/main/scala/net/liftweb/squerylrecord/SquerylRecord.scala +++ b/persistence/squeryl-record/src/main/scala/net/liftweb/squerylrecord/SquerylRecord.scala @@ -17,37 +17,53 @@ package squerylrecord import common.{Box, Full, Loggable} import db.DB import util.DynoVar - import org.squeryl.{Session, SessionFactory} import org.squeryl.internals.{DatabaseAdapter, FieldMetaData} +import net.liftweb.util.LoanWrapper +import RecordTypeMode._ /** Object containing initialization logic for the Squeryl/Record integration */ object SquerylRecord extends Loggable { - /** Keep track of the current Squeryl Session we've created using DB */ - private object currentSession extends DynoVar[Session] /** * We have to remember the default Squeryl metadata factory before * we override it with our own implementation, so that we can use * the original factory for non-record classes. */ - private[squerylrecord] var posoMetaDataFactory = FieldMetaData.factory + private[squerylrecord] val posoMetaDataFactory = FieldMetaData.factory + /** - * Initialize the Squeryl/Record integration. This must be called somewhere during your Boot, and before you use any - * Records with Squeryl. Use this function instead of init if you want to use the squeryl session factory - * instead of mapper.DB as the transaction manager with squeryl-record. + * Initialize the Squeryl/Record integration. This must be called somewhere during your Boot before you use any + * Records with Squeryl. When using this method, configure your Session separately + * (see [[http://squeryl.org/sessions-and-tx.html]] for details) or you can use initWithSquerylSession to do both at once. */ - def initWithSquerylSession(sessionFactory: => Session) { + def init() { FieldMetaData.factory = new RecordMetaDataFactory - SessionFactory.concreteFactory = Some(() => sessionFactory) } /** - * Initialize the Squeryl/Record integration. This must be called somewhere during your Boot, and before you use any - * Records with Squeryl. Use this function if you want to use mapper.DB as the transaction manager - * with squeryl-record. + * Initialize the Squeryl/Record integration and configure a default Session at the same time. */ + def initWithSquerylSession(sessionFactory: => Session) { + init() + SessionFactory.concreteFactory = Some(() => sessionFactory) + } + + def buildLoanWrapper() = new LoanWrapper { + override def apply[T](f: => T): T = inTransaction { + f + } + } + + /** + * + * NOTE: Remove this along with the deprecated method below + * Keep track of the current Squeryl Session we've created using DB + * */ + private object currentSession extends DynoVar[Session] + + @deprecated(message = "Lift DB.use style transactions do not properly clean up Squeryl resources. Please use initWithSquerylSession instead.") def init(mkAdapter: () => DatabaseAdapter) = { FieldMetaData.factory = new RecordMetaDataFactory SessionFactory.externalTransactionManagementAdapter = Some(() => From 598fb03518ece48e817c61d089881fce6f23c1dd Mon Sep 17 00:00:00 2001 From: Dave Whittaker Date: Tue, 8 May 2012 13:50:04 -0400 Subject: [PATCH 0053/1949] As discussed a while back, S.location is stored in a RequestVar and if the value was accessed before request state had been initialized it wasn't being retried. This will check repeatedly, but only if the RequestVar does not already hold a Full value. --- web/webkit/src/main/scala/net/liftweb/http/S.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/S.scala b/web/webkit/src/main/scala/net/liftweb/http/S.scala index d92f0e1107..477a398cbc 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/S.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/S.scala @@ -386,7 +386,10 @@ trait S extends HasParams with Loggable { private[http] object CurrentLocation extends RequestVar[Box[sitemap.Loc[_]]](request.flatMap(_.location)) - def location: Box[sitemap.Loc[_]] = CurrentLocation.is + def location: Box[sitemap.Loc[_]] = CurrentLocation.is or { + //try again in case CurrentLocation was accessed before the request was available + request flatMap { r => CurrentLocation(r.location) } + } /** From 27f5c8b435705e9f90769847753d49dd27985ec9 Mon Sep 17 00:00:00 2001 From: Dave Whittaker Date: Tue, 8 May 2012 19:04:58 -0400 Subject: [PATCH 0054/1949] Makes Squeryl-Record both aware of the dirty_? flag, which is reset after values are loaded from the DB, and utilize runSafe during data loading. --- .../squerylrecord/RecordMetaDataFactory.scala | 25 +++++++++++++++---- .../squerylrecord/SquerylRecordSpec.scala | 6 +++++ 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/persistence/squeryl-record/src/main/scala/net/liftweb/squerylrecord/RecordMetaDataFactory.scala b/persistence/squeryl-record/src/main/scala/net/liftweb/squerylrecord/RecordMetaDataFactory.scala index 913c161be7..b5e87a34c3 100644 --- a/persistence/squeryl-record/src/main/scala/net/liftweb/squerylrecord/RecordMetaDataFactory.scala +++ b/persistence/squeryl-record/src/main/scala/net/liftweb/squerylrecord/RecordMetaDataFactory.scala @@ -141,15 +141,30 @@ class RecordMetaDataFactory extends FieldMetaDataFactory { fieldScale getOrElse super.scale } - private def fieldFor(o: AnyRef) = getter.get.invoke(o).asInstanceOf[TypedField[_ <: AnyRef]] + private def fieldFor(o: AnyRef) = getter.get.invoke(o) match { + case tf: TypedField[_] => tf + case other => org.squeryl.internals.Utils.throwError("Field's used with Squeryl must inherit from net.liftweb.record.TypedField : " + other ) + } - override def set(target: AnyRef, value: AnyRef) = { - val typedField: TypedField[_] = fieldFor(target) - typedField.setFromAny(Box !! value) + /** + * Sets the value which was retrieved from the DB into the appropriate Record field + */ + override def set(target: AnyRef, value: AnyRef) = target match { + case record: Record[_] => + record.runSafe { + val typedField: TypedField[_] = fieldFor(target) + typedField.setFromAny(Box !! value) + typedField.resetDirty + } + case other => + org.squeryl.internals.Utils.throwError("RecordMetaDataFactory can not set fields on non Record objects : " + other) } override def setFromResultSet(target: AnyRef, rs: ResultSet, index: Int) = set(target, resultSetHandler(rs, index)) + /** + * Extracts the value from the field referenced by o that will be stored in the DB + */ override def get(o: AnyRef) = fieldFor(o) match { case enumField: EnumTypedField[_] => enumField.valueBox match { case Full(enum: Enumeration#Value) => enum.id: java.lang.Integer @@ -161,7 +176,7 @@ class RecordMetaDataFactory extends FieldMetaDataFactory { } case other => other.valueBox match { case Full(c: Calendar) => new Timestamp(c.getTime.getTime) - case Full(other) => other + case Full(other: AnyRef) => other case _ => null } } diff --git a/persistence/squeryl-record/src/test/scala/net/liftweb/squerylrecord/SquerylRecordSpec.scala b/persistence/squeryl-record/src/test/scala/net/liftweb/squerylrecord/SquerylRecordSpec.scala index d31ddcaa57..cdb31c9d89 100644 --- a/persistence/squeryl-record/src/test/scala/net/liftweb/squerylrecord/SquerylRecordSpec.scala +++ b/persistence/squeryl-record/src/test/scala/net/liftweb/squerylrecord/SquerylRecordSpec.scala @@ -352,6 +352,12 @@ object SquerylRecordSpec extends Specification("SquerylRecord Specification") { val columnDefinition = new PostgreSqlAdapter().writeColumnDeclaration(fieldMetaData, false, MySchema) columnDefinition.endsWith("numeric(" + Company.employeeSatisfaction.context.getPrecision() +"," + Company.employeeSatisfaction.scale + ")") must_== true } + + forExample("Properly reset the dirty_? flag after loading entities") >> inTransaction { + val company = from(companies)(company => + select(company)).page(0, 1).single + company.allFields map { f => f.dirty_? must_== false } + } } From f3e45ffbc68d9771f2f56a8b8452f2f969e67d16 Mon Sep 17 00:00:00 2001 From: Dave Whittaker Date: Thu, 10 May 2012 16:54:55 -0400 Subject: [PATCH 0055/1949] Some updates to Record Fields. Added a check on whether the newly set value differed from the previous value before setting the dirty_? flag in setBox. In the process of setting up unit testing for that feature, I noticed that non-mandatory fields weren't being tested properly, and fixing that caused some tests to fail that required a few other changes. I also tried to add a bit more API doc as I went through. --- .../main/scala/net/liftweb/record/Field.scala | 36 +++++++- .../record/field/PostalCodeField.scala | 7 +- .../scala/net/liftweb/record/FieldSpec.scala | 89 ++++++++++++++----- 3 files changed, 103 insertions(+), 29 deletions(-) diff --git a/persistence/record/src/main/scala/net/liftweb/record/Field.scala b/persistence/record/src/main/scala/net/liftweb/record/Field.scala index 32bfc83e12..859bc4fd75 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/Field.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/Field.scala @@ -146,6 +146,11 @@ trait OwnedField[OwnerType <: Record[OwnerType]] extends BaseField { /** Refined trait for fields holding a particular value type */ trait TypedField[ThisType] extends BaseField { + + /* + * Unless overriden, MyType is equal to ThisType. Available for + * backwards compatibility + */ type MyType = ThisType // For backwards compatability type ValidationFunction = ValueType => List[FieldError] @@ -212,6 +217,7 @@ trait TypedField[ThisType] extends BaseField { def setBox(in: Box[MyType]): Box[MyType] = synchronized { needsDefault = false + val oldValue = valueBox data = in match { case _ if !canWrite_? => Failure(noValueErrorMessage) case Full(_) => set_!(in) @@ -219,12 +225,17 @@ trait TypedField[ThisType] extends BaseField { case (f: Failure) => set_!(f) // preserve failures set in case _ => Failure(notOptionalErrorMessage) } - dirty_?(true) + val same = (oldValue, valueBox) match { + case (Full(ov), Full(nv)) => ov == nv + case (a, b) => a == b + } + dirty_?(!same) data } // Helper methods for things to easily use mixins and so on that use ValueType instead of Box[MyType], regardless of the optional-ness of the field protected def toValueType(in: Box[MyType]): ValueType + protected def toBoxMyType(in: ValueType): Box[MyType] protected def set_!(in: Box[MyType]): Box[MyType] = runFilters(in, setFilterBox) @@ -313,6 +324,11 @@ trait TypedField[ThisType] extends BaseField { } trait MandatoryTypedField[ThisType] extends TypedField[ThisType] with Product1[ThisType] { + + /** + * ValueType represents the type that users will work with. For MandatoryTypeField, this is + * equal to ThisType. + */ type ValueType = ThisType // For util.BaseField //TODO: fullfil the contract of Product1[ThisType] @@ -335,6 +351,7 @@ trait MandatoryTypedField[ThisType] extends TypedField[ThisType] with Product1[T def value: MyType = valueBox openOr defaultValue def get: MyType = value + def is: MyType = value protected def liftSetFilterToBox(in: Box[MyType]): Box[MyType] = in.map(v => setFilter.foldLeft(v)((prev, f) => f(prev))) @@ -354,6 +371,11 @@ trait MandatoryTypedField[ThisType] extends TypedField[ThisType] with Product1[T } trait OptionalTypedField[ThisType] extends TypedField[ThisType] with Product1[Box[ThisType]] { + + /** + * ValueType represents the type that users will work with. For OptionalTypedField, this is + * equal to Option[ThisType]. + */ type ValueType = Option[ThisType] // For util.BaseField //TODO: fullfil the contract of Product1[ThisType] @@ -371,15 +393,21 @@ trait OptionalTypedField[ThisType] extends TypedField[ThisType] with Product1[Bo def set(in: Option[MyType]): Option[MyType] = setBox(in) or defaultValueBox def toValueType(in: Box[MyType]) = in + def toBoxMyType(in: ValueType) = in def value: Option[MyType] = valueBox def get: Option[MyType] = value + def is: Option[MyType] = value - protected def liftSetFilterToBox(in: Box[MyType]): Box[MyType] = setFilter.foldLeft(in)((prev, f) => f(prev)) - + protected def liftSetFilterToBox(in: Box[MyType]): Box[MyType] = setFilter.foldLeft(in){ (prev, f) => + prev match { + case fail: Failure => fail //stop on failure, otherwise some filters will clober it to Empty + case other => f(other) + } + } def defaultValueBox: Box[MyType] = Empty @@ -392,7 +420,7 @@ trait OptionalTypedField[ThisType] extends TypedField[ThisType] with Product1[Bo } /** - * A simple field that can store and retreive a value of a given type + * A simple field that can store and retrieve a value of a given type */ trait Field[ThisType, OwnerType <: Record[OwnerType]] extends OwnedField[OwnerType] with TypedField[ThisType] { diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/PostalCodeField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/PostalCodeField.scala index 3262af46e0..f7162206a7 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/PostalCodeField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/PostalCodeField.scala @@ -29,6 +29,7 @@ import S._ trait PostalCodeTypedField extends StringTypedField { + protected val country: CountryField[_] override def setFilter = toUpper _ :: trim _ :: super.setFilter @@ -52,9 +53,7 @@ trait PostalCodeTypedField extends StringTypedField { } } -class PostalCodeField[OwnerType <: Record[OwnerType]](rec: OwnerType, val country: CountryField[OwnerType]) -extends StringField(rec, 32) with PostalCodeTypedField +class PostalCodeField[OwnerType <: Record[OwnerType]](rec: OwnerType, val country: CountryField[OwnerType]) extends StringField(rec, 32) with PostalCodeTypedField -class OptionalPostalCodeField[OwnerType <: Record[OwnerType]](rec: OwnerType, val country: CountryField[OwnerType]) -extends OptionalStringField(rec, 32) with PostalCodeTypedField +class OptionalPostalCodeField[OwnerType <: Record[OwnerType]](rec: OwnerType, val country: CountryField[OwnerType]) extends OptionalStringField(rec, 32) with PostalCodeTypedField diff --git a/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala b/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala index 4182955593..e801f567f3 100644 --- a/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala +++ b/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala @@ -42,44 +42,90 @@ object FieldSpec extends Specification("Record Field Specification") { def passBasicTests[A](example: A, mandatory: MandatoryTypedField[A], legacyOptional: MandatoryTypedField[A], optional: OptionalTypedField[A])(implicit m: scala.reflect.Manifest[A]): Unit = { val canCheckDefaultValues = !mandatory.defaultValue.isInstanceOf[Calendar] // don't try to use the default value of date/time typed fields, because it changes from moment to moment! + + def commonBehaviorsForMandatory(in: MandatoryTypedField[A]): Unit = { + + if (canCheckDefaultValues) { + "which have the correct initial value" in { + in.get must_== in.defaultValue + } + } + + "which are readable and writable" in { + in.set(example) + in.get must_== example + in.clear + in.get must_!= example + in.setBox(Box !! example) + in.get must_== example + } + if (canCheckDefaultValues) { + "which correctly clear back to the default value" in { + in.set(example) + in.clear + in.get must_== in.defaultValue + } + } + } + def commonBehaviorsForAllFlavors(in: TypedField[A]): Unit = { + if (canCheckDefaultValues) { - "which have the correct initial value" in { - mandatory.value must_== mandatory.defaultValue - mandatory.valueBox must_== mandatory.defaultValueBox + "which have the correct initial boxed value" in { + in match { + case mandatory: MandatoryTypedField[_] => + mandatory.value must_== mandatory.defaultValue + case _ => () + } + in.valueBox must_== in.defaultValueBox } } - "which are readable and writable" in { - mandatory.valueBox must verify(_.isDefined) - mandatory.set(example) - mandatory.value must_== example - mandatory.valueBox must_== Full(example) - mandatory.clear - mandatory.value must_!= example - mandatory.valueBox must_!= Full(example) - mandatory.setBox(Full(example)) - mandatory.value must_== example - mandatory.valueBox must_== Full(example) + "which have readable and writable boxed values" in { + in.setBox(Full(example)) + in.valueBox must verify(_.isDefined) + in.valueBox must_== Full(example) + in.clear + in.valueBox must_!= Full(example) } if (canCheckDefaultValues) { - "which correctly clear back to the default" in { - mandatory.valueBox must verify(_.isDefined) - mandatory.clear - mandatory.valueBox must_== mandatory.defaultValueBox + "which correctly clear back to the default box value" in { + in.setBox(Full(example)) + in.valueBox must verify(_.isDefined) + in.clear + in.valueBox must_== in.defaultValueBox } } "which capture error conditions set in" in { - mandatory.setBox(Failure("my failure")) - mandatory.valueBox must_== Failure("my failure") + in.setBox(Failure("my failure")) + in.valueBox must_== Failure("my failure") + } + + "which are only flagged as dirty_? when setBox is called with a different value" in { + in.clear + in match { + case owned: OwnedField[_] => owned.owner.runSafe { + in.resetDirty + } + case _ => in.resetDirty + } + in.dirty_? must_== false + val valueBox = in.valueBox + in.setBox(valueBox) + in.dirty_? must_== false + val exampleBox = Full(example) + valueBox must verify { v => ! (exampleBox === v) } + in.setBox(exampleBox) + in.dirty_? must_== true } } "support mandatory fields" in { commonBehaviorsForAllFlavors(mandatory) + commonBehaviorsForMandatory(mandatory) "which are configured correctly" in { mandatory.optional_? must_== false @@ -98,7 +144,8 @@ object FieldSpec extends Specification("Record Field Specification") { "support 'legacy' optional fields (override optional_?)" in { commonBehaviorsForAllFlavors(legacyOptional) - + commonBehaviorsForMandatory(legacyOptional) + "which are configured correctly" in { legacyOptional.optional_? must_== true } From 974322d58f60a7ee9f70e48dfcaf171b50694fcf Mon Sep 17 00:00:00 2001 From: Dave Whittaker Date: Thu, 10 May 2012 19:14:22 -0400 Subject: [PATCH 0056/1949] Change tabIndex to tabindex for consistency with XHTML spec. --- .../src/main/scala/net/liftweb/record/field/BooleanField.scala | 2 +- .../record/src/test/scala/net/liftweb/record/FieldSpec.scala | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/BooleanField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/BooleanField.scala index ca948052a7..101f172463 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/BooleanField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/BooleanField.scala @@ -40,7 +40,7 @@ trait BooleanTypedField extends TypedField[Boolean] { def setFromString(s: String): Box[Boolean] = setBox(tryo(toBoolean(s))) private def elem(attrs: SHtml.ElemAttr*) = - SHtml.checkbox(valueBox openOr false, (b: Boolean) => this.setBox(Full(b)), (("tabIndex" -> tabIndex.toString): SHtml.ElemAttr) :: attrs.toList: _*) + SHtml.checkbox(valueBox openOr false, (b: Boolean) => this.setBox(Full(b)), (("tabindex" -> tabIndex.toString): SHtml.ElemAttr) :: attrs.toList: _*) def toForm: Box[NodeSeq] = // FIXME? no support for optional_? diff --git a/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala b/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala index 4182955593..199fecc686 100644 --- a/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala +++ b/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala @@ -189,7 +189,6 @@ object FieldSpec extends Specification("Record Field Specification") { case _ => false }) andThen "* [value]" #> ".*"))(fprime) val ret: Boolean = Helpers.compareXml(f, fp) - ret must_== true } } @@ -217,7 +216,7 @@ object FieldSpec extends Specification("Record Field Specification") { rec.mandatoryBooleanField, JsTrue, JBool(bool), - Full() + Full() ) "support java.lang.Boolean" in { rec.mandatoryBooleanField.setFromAny(java.lang.Boolean.TRUE) From b18d6fc7fb54d577be8e3dc2461fedda7b418e55 Mon Sep 17 00:00:00 2001 From: David Pollak Date: Fri, 25 May 2012 14:18:43 -0700 Subject: [PATCH 0057/1949] Added Whitelist and other security measures to template processing. Fixed attribute rendering issues Closes #1262 --- .../scala/net/liftweb/util/HtmlParser.scala | 2 +- .../scala/net/liftweb/http/LiftRules.scala | 30 ++++++++++++++ .../scala/net/liftweb/http/LiftSession.scala | 41 ++++++++++++++----- .../scala/net/liftweb/http/Templates.scala | 10 ++++- 4 files changed, 71 insertions(+), 12 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/HtmlParser.scala b/core/util/src/main/scala/net/liftweb/util/HtmlParser.scala index e9c855183e..1d5f759f4b 100644 --- a/core/util/src/main/scala/net/liftweb/util/HtmlParser.scala +++ b/core/util/src/main/scala/net/liftweb/util/HtmlParser.scala @@ -39,7 +39,7 @@ trait Html5Writer { m match { case null => case Null => - case md if (null eq md.value) => // issue 807. Don't do empty + case md if (null eq md.value) => writeAttributes(md.next, writer) case up: UnprefixedAttribute => { writer.append(' ') writer.append(up.key) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index fa14f97e3f..6a6ace6ac4 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -782,6 +782,36 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { val resourceForCurrentLoc: FactoryMaker[() => List[ResourceBundle]] = new FactoryMaker(() => () => DefaultRoutines.resourceForCurrentReq()) {} + + /** + * There may be times when you want to entirely control the templating process. You can insert + * a function to this factory that will do your custom template resolution. If the PartialFunction + * isDefinedAt the given locale/path, then that's the template returned. In this way, you can + * return Empty for a template that's not found and the template will not be found. Otherwise, + * if the function is not defined for the locale/path pair, the normal templating system will + * be used. Also, keep in mind how FactoryMaker can be used... it can be global, per request, etc. + */ + val externalTemplateResolver: FactoryMaker[() => PartialFunction[(Locale, List[String]), Box[NodeSeq]]] = + new FactoryMaker(() => (() => Map.empty: PartialFunction[(Locale, List[String]), Box[NodeSeq]])) {} + + /** + * There may be times when you want to entirely control the templating process. You can insert a function + * that creates a white list of snippets. The white list is the exhaustive list of snippets. The + * snippets are class/method pairs. If the partial function is defined and the result is a Full Box, + * the function is run. If the Box is an EmptyBox, then the result is a snippet lookup failure. If the + * partial function is not defined, then the normal snippet resolution mechanism is used. Please note that + * in Scala a Map is PartialFunction and you can create Maps that have a default value using the withDefaultValue + * method. + */ + val snippetWhiteList: FactoryMaker[() => PartialFunction[(String, String), Box[NodeSeq => NodeSeq]]] = + new FactoryMaker(() => (() => Map.empty: PartialFunction[(String, String), Box[NodeSeq => NodeSeq]])) {} + + /** + * This FactoryMaker can be used to disable the little used attributeSnippets + */ + val allowAttributeSnippets: FactoryMaker[() => Boolean] = + new FactoryMaker(() => () => true) {} + private var _sitemap: Box[SiteMap] = Empty private var sitemapFunc: Box[() => SiteMap] = Empty diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index 31112799df..db0484b1cc 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -546,6 +546,11 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, private val sessionVarSync = new Object + /** + * Cache the value of allowing snippet attribute processing + */ + private object allowAttributeProcessing extends TransientRequestVar(LiftRules.allowAttributeSnippets.vend()) + /** * A mapping between pages denoted by RenderVersion and * functions to execute at the end of the page rendering @@ -1376,19 +1381,21 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, } yield res } - private def processAttributes(in: MetaData): MetaData = { + private def processAttributes(in: MetaData, allow: Boolean): MetaData = { + if (!allow) in else { in match { case Null => Null case mine: PrefixedAttribute if (mine.pre == "lift") => { mine.key match { - case s if s.indexOf('.') > -1 => findAttributeSnippet(s, processAttributes(in.next), mine) - case "snippet" => findAttributeSnippet(mine.value.text, processAttributes(in.next)) - case _ => mine.copy(processAttributes(in.next)) + case s if s.indexOf('.') > -1 => findAttributeSnippet(s, processAttributes(in.next, allow), mine) + case "snippet" => findAttributeSnippet(mine.value.text, processAttributes(in.next, allow)) + case _ => mine.copy(processAttributes(in.next, allow)) } } - case notMine => notMine.copy(processAttributes(in.next)) + case notMine => notMine.copy(processAttributes(in.next, allow)) } } + } /** * See if there's a object singleton with the right name @@ -1578,14 +1585,28 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, } } + def runWhitelist(snippet: String, cls: String, method: String, kids: NodeSeq)(f: => NodeSeq): NodeSeq = { + val pf = LiftRules.snippetWhiteList.vend() + val pair = (cls, method) + if (pf.isDefinedAt(pair)) { + val func = pf(pair) + func.map(_.apply(kids)) openOr reportSnippetError(page, snippetName, + LiftRules.SnippetFailures.MethodNotFound, + NodeSeq.Empty, + wholeTag) + } else f + } + val ret: NodeSeq = try { - snippetName.map(snippet => + + snippetName.map{snippet => + val (cls, method) = splitColonPair(snippet) S.doSnippet(snippet)( - (S.locateMappedSnippet(snippet).map(_(kids)) or + runWhitelist(snippet, cls, method, kids){(S.locateMappedSnippet(snippet).map(_(kids)) or locSnippet(snippet)).openOr( S.locateSnippet(snippet).map(_(kids)) openOr { - val (cls, method) = splitColonPair(snippet) + (locateAndCacheSnippet(cls)) match { // deal with a stateless request when a snippet has // different behavior in stateless mode @@ -1715,7 +1736,7 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, wholeTag) } - }))).openOr { + })})}.openOr { reportSnippetError(page, snippetName, LiftRules.SnippetFailures.NoNameSpecified, NodeSeq.Empty, @@ -1907,7 +1928,7 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, } case v: Elem => - Elem(v.prefix, v.label, processAttributes(v.attributes), + Elem(v.prefix, v.label, processAttributes(v.attributes, this.allowAttributeProcessing.is), v.scope, processSurroundAndInclude(page, v.child): _*) case pcd: scala.xml.PCData => pcd diff --git a/web/webkit/src/main/scala/net/liftweb/http/Templates.scala b/web/webkit/src/main/scala/net/liftweb/http/Templates.scala index 56debeccc4..268930f0c8 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Templates.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Templates.scala @@ -171,13 +171,20 @@ object Templates { yield better performance. Please don't change this method without chatting with me first. Thanks! DPP */ + + val resolver = LiftRules.externalTemplateResolver.vend() + val key = (locale, places) + + if (resolver.isDefinedAt(key)) { + resolver(key) + } else { val lrCache = LiftRules.templateCache val cache = if (lrCache.isDefined) lrCache.open_! else NoCache val parserFunction: InputStream => Box[NodeSeq] = S.htmlProperties.htmlParser - val key = (locale, places) + val tr = cache.get(key) if (tr.isDefined) tr @@ -247,6 +254,7 @@ object Templates { } } } +} private def lookForClasses(places: List[String]): Box[NodeSeq] = { val (controller, action) = places match { From efe3ca853007f8890f7d1e61fc0df70db6cddaf5 Mon Sep 17 00:00:00 2001 From: Dave Whittaker Date: Sat, 26 May 2012 13:34:29 -0400 Subject: [PATCH 0058/1949] Fixed failing test for DateTimeField. --- .../scala/net/liftweb/record/FieldSpec.scala | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala b/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala index dfeab9b5f7..b96b3fccee 100644 --- a/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala +++ b/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala @@ -104,22 +104,24 @@ object FieldSpec extends Specification("Record Field Specification") { in.valueBox must_== Failure("my failure") } - "which are only flagged as dirty_? when setBox is called with a different value" in { - in.clear - in match { - case owned: OwnedField[_] => owned.owner.runSafe { - in.resetDirty - } - case _ => in.resetDirty - } - in.dirty_? must_== false - val valueBox = in.valueBox - in.setBox(valueBox) - in.dirty_? must_== false - val exampleBox = Full(example) - valueBox must verify { v => ! (exampleBox === v) } - in.setBox(exampleBox) - in.dirty_? must_== true + if(canCheckDefaultValues) { + "which are only flagged as dirty_? when setBox is called with a different value" in { + in.clear + in match { + case owned: OwnedField[_] => owned.owner.runSafe { + in.resetDirty + } + case _ => in.resetDirty + } + in.dirty_? must_== false + val valueBox = in.valueBox + in.setBox(valueBox) + in.dirty_? must_== false + val exampleBox = Full(example) + valueBox must verify { v => ! (exampleBox === v) } + in.setBox(exampleBox) + in.dirty_? must_== true + } } } From 86e7b30a0b1a4b2027ca6420627f402475b37a21 Mon Sep 17 00:00:00 2001 From: David Pollak Date: Mon, 28 May 2012 16:17:03 -0700 Subject: [PATCH 0059/1949] Including the X-SSL header allows for correct SSL detection through a reverse proxy --- web/webkit/src/main/scala/net/liftweb/http/Req.scala | 1 + .../liftweb/http/provider/servlet/HTTPRequestServlet.scala | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/Req.scala b/web/webkit/src/main/scala/net/liftweb/http/Req.scala index 50e7a9156d..cd42a19aaa 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Req.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Req.scala @@ -997,6 +997,7 @@ class Req(val path: ParsePath, */ lazy val hostAndPath: String = containerRequest.map(r => (r.scheme, r.serverPort) match { + case ("http", 80) if r.header("X-SSL").isDefined => "https://" + r.serverName + contextPath case ("http", 80) => "https://" + r.serverName + contextPath case ("https", 443) => "https://" + r.serverName + contextPath case (sch, port) => sch + "://" + r.serverName + ":" + port + contextPath diff --git a/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPRequestServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPRequestServlet.scala index 56ff9ac88b..bf158e310d 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPRequestServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPRequestServlet.scala @@ -226,7 +226,10 @@ private class OfflineRequestSnapshot(req: HTTPRequest, val provider: HTTPProvide val scheme: String = req.scheme - val serverPort: Int = req.serverPort + lazy val serverPort: Int = req.serverPort match { + case 80 => headers("X-SSL").flatMap(Helpers.asBoolean _).filter(a => a).map(a => 443).headOption getOrElse 80 + case x => x + } val method: String = req.method From a184918ccdfd5339222b11483ffa2a6b27753439 Mon Sep 17 00:00:00 2001 From: David Pollak Date: Mon, 28 May 2012 18:44:03 -0700 Subject: [PATCH 0060/1949] Allows for context specific changes to this rule --- web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 6a6ace6ac4..c4a1f121d5 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -1448,7 +1448,11 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { /** * Tells Lift if the Ajax JavaScript shoukd be included. By default it is set to true. */ - @volatile var autoIncludeAjax: LiftSession => Boolean = session => true + @deprecated("Use autoIncludeAjaxCalc") + @volatile var autoIncludeAjax: LiftSession => Boolean = session => autoIncludeAjaxCalc.vend().apply(session) + + val autoIncludeAjaxCalc: FactoryMaker[() => LiftSession => Boolean] = + new FactoryMaker(() => () => (session: LiftSession) => true) {} /** * Define the XHTML validator From 4a4dbd39ed87ff5d52e808e008465ea3629e7745 Mon Sep 17 00:00:00 2001 From: David Pollak Date: Thu, 31 May 2012 10:22:50 -0700 Subject: [PATCH 0061/1949] Added utility to allow user switching for a stack frame --- .../src/main/scala/net/liftweb/proto/ProtoUser.scala | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/persistence/proto/src/main/scala/net/liftweb/proto/ProtoUser.scala b/persistence/proto/src/main/scala/net/liftweb/proto/ProtoUser.scala index 539c071db0..cca6650aab 100644 --- a/persistence/proto/src/main/scala/net/liftweb/proto/ProtoUser.scala +++ b/persistence/proto/src/main/scala/net/liftweb/proto/ProtoUser.scala @@ -605,6 +605,18 @@ trait ProtoUser { S.session.foreach(_.destroySession()) } + /** + * There may be times when you want to be another user + * for some stack frames. Here's how to do it. + */ + def doWithUser[T](u: Box[TheUserType])(f: => T): T = + curUserId.doWith(u.map(_.userIdAsString)) { + curUser.doWith(u) { + f + } + } + + private object curUserId extends SessionVar[Box[String]](Empty) { override lazy val __nameSalt = Helpers.nextFuncName } From 5d1174851c67ebcfd6e24b32d176bc43055f5539 Mon Sep 17 00:00:00 2001 From: Peter Brant Date: Tue, 24 Jan 2012 00:01:35 +0100 Subject: [PATCH 0062/1949] Add CSS bound LiftScreen --- .../net/liftweb/http/CssBoundLiftScreen.scala | 92 +++++ .../net/liftweb/http/CssBoundScreen.scala | 377 ++++++++++++++++++ .../scala/net/liftweb/http/LiftScreen.scala | 192 ++++++++- 3 files changed, 652 insertions(+), 9 deletions(-) create mode 100644 web/webkit/src/main/scala/net/liftweb/http/CssBoundLiftScreen.scala create mode 100644 web/webkit/src/main/scala/net/liftweb/http/CssBoundScreen.scala diff --git a/web/webkit/src/main/scala/net/liftweb/http/CssBoundLiftScreen.scala b/web/webkit/src/main/scala/net/liftweb/http/CssBoundLiftScreen.scala new file mode 100644 index 0000000000..8943c2199f --- /dev/null +++ b/web/webkit/src/main/scala/net/liftweb/http/CssBoundLiftScreen.scala @@ -0,0 +1,92 @@ +/* + * Copyright 2011-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 net.liftweb.http.js.JsCmds._ +import net.liftweb.http.js.JsCmd +import net.liftweb.common._ +import util._ + +import xml._ + +trait CssBoundLiftScreen extends LiftScreen with CssBoundScreen { + protected object SavedDefaultXml extends ScreenVar[NodeSeq](defaultXml) { + override lazy val __nameSalt = Helpers.nextFuncName + } + + protected object LocalAction extends TransientRequestVar[String]("") { + override lazy val __nameSalt = Helpers.nextFuncName + } + + protected object LocalActionRef extends ScreenVar[String](S.fmapFunc(setLocalAction _)(s => s)) { + override lazy val __nameSalt = Helpers.nextFuncName + } + + protected object PrevId extends TransientRequestVar[Box[String]](Empty) { + override lazy val __nameSalt = Helpers.nextFuncName + } + + protected object CancelId extends TransientRequestVar[String]("") { + override lazy val __nameSalt = Helpers.nextFuncName + } + + protected object LocalActions extends ScreenVar[Map[String, () => JsCmd]](Map[String, () => JsCmd]()) { + override lazy val __nameSalt = Helpers.nextFuncName + } + + override def localSetup() { + SavedDefaultXml.get + LocalActionRef.get + } + + override def allTemplate = SavedDefaultXml.get + + protected def defaultAllTemplate = super.allTemplate + + override protected def doFinish(): JsCmd= { + val fMap: Map[String, () => JsCmd] = LocalActions.get + if (! LocalAction.get.isEmpty) + fMap.get(LocalAction.get) map (_()) getOrElse ( + throw new IllegalArgumentException("No local action available with that binding")) + else { + validate match { + case Nil => + val snapshot = createSnapshot + PrevSnapshot.set(Full(snapshot)) + finish() + redirectBack() + case xs => { + S.error(xs) + if (ajaxForms_?) { + replayForm + } else { + Noop + } + } + } + } + } + + protected def renderWithErrors(errors: List[FieldError]) { + S.error(errors) + AjaxOnDone.set(replayForm) + } + + protected def renderFormCmd: JsCmd = SetHtml(FormGUID, renderHtml()) + + protected def replayForm: JsCmd = renderFormCmd +} diff --git a/web/webkit/src/main/scala/net/liftweb/http/CssBoundScreen.scala b/web/webkit/src/main/scala/net/liftweb/http/CssBoundScreen.scala new file mode 100644 index 0000000000..6c75cebfe1 --- /dev/null +++ b/web/webkit/src/main/scala/net/liftweb/http/CssBoundScreen.scala @@ -0,0 +1,377 @@ +/* + * Copyright 2011-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.jquery.JqJE.JqId +import js.JsCmd +import js.JsCmds._ +import js.jquery.JqJsCmds._ +import js.JE +import net.liftweb.util._ +import Helpers._ +import net.liftweb.common._ +import xml._ + +import FieldBinding._ + +trait CssBoundScreen extends ScreenWizardRendered with Loggable { + self: AbstractScreen => + + def formName: String + + def labelSuffix: NodeSeq = Text(":") + + protected lazy val cssClassBinding = new CssClassBinding + + protected val LocalActionRef: AnyVar[String, _] + protected val LocalAction: AnyVar[String, _] + protected val LocalActions: AnyVar[Map[String, () => JsCmd], _] + + val NextId: AnyVar[String, _] + + protected val PrevId: AnyVar[Box[String], _] + protected val CancelId: AnyVar[String, _] + + protected def additionalFormBindings: Box[CssSel] = Empty + + private def traceInline[T](msg: => String, v: T): T = { + logger.trace(msg) + v + } + + private object FieldBindingUtils { + def sel(f: CssClassBinding => String, sel: String) = sel format (f(cssClassBinding)) + def replace(f: CssClassBinding => String) = ".%s" format (f(cssClassBinding)) + def replaceChildren(f: CssClassBinding => String) = ".%s *" format (f(cssClassBinding)) + + def remove(f: CssClassBinding => String) = + traceInline("Removing %s".format(f(cssClassBinding)), ".%s".format(f(cssClassBinding))) #> NodeSeq.Empty + + def nsSetChildren(f: CssClassBinding => String, value: NodeSeq) = + traceInline("Binding %s to %s".format(replaceChildren(f), value), replaceChildren(f) #> value) + + def funcSetChildren(f: CssClassBinding => String, value: NodeSeq => NodeSeq) = + traceInline("Binding %s to function".format(replaceChildren(f)), replaceChildren(f) #> value) + + def optSetChildren(f: CssClassBinding => String, value: Box[NodeSeq]) = + traceInline("Binding %s to %s".format(replaceChildren(f), value), replaceChildren(f) #> value) + + def nsReplace(f: CssClassBinding=>String, value: NodeSeq) = + traceInline("Binding %s to %s".format(replace(f), value), replace(f) #> value) + + def funcReplace(f: CssClassBinding=>String, value: NodeSeq => NodeSeq) = + traceInline("Binding %s to function".format(replace(f)), replace(f) #> value) + + def optReplace(f: CssClassBinding=>String, value: Box[NodeSeq]) = + traceInline("Binding %s to %s".format(replace(f), value), replace(f) #> value) + + def updateAttrs(metaData: MetaData): NodeSeq => NodeSeq = { + case e:Elem => e % metaData + } + + def update(f: CssClassBinding => String, metaData: MetaData) = + traceInline("Update %s with %s".format(f(cssClassBinding), metaData), + ".%s".format(f(cssClassBinding)) #> updateAttrs(metaData)) + } + + protected def bindLocalAction(selector: String, func: () => JsCmd): CssSel = { + mapLocalAction(func)(name => + selector #> ( + SHtml.makeAjaxCall(LiftRules.jsArtifacts.serialize(NextId.get) + ("&" + LocalActionRef.get + "=" + name)).cmd + ).toJsCmd) + } + + protected def mapLocalAction[T](func: () => JsCmd)(f: String => T): T = { + val name = randomString(20) + LocalActions.set(LocalActions.is + (name -> func)) + f(name) + } + + protected def setLocalAction(s: String) { + logger.debug("Setting LocalAction (%s) to %s".format( + Integer.toString(System.identityHashCode(LocalAction), 16), s)) + LocalAction.set(s) + } + + override protected def renderAll(currentScreenNumber: Box[NodeSeq], + screenCount: Box[NodeSeq], + wizardTop: Box[Elem], + screenTop: Box[Elem], + fields: List[ScreenFieldInfo], + prev: Box[Elem], + cancel: Box[Elem], + next: Box[Elem], + finish: Box[Elem], + screenBottom: Box[Elem], + wizardBottom: Box[Elem], + nextId: (String, () => JsCmd), + prevId: Box[(String, () => JsCmd)], + cancelId: (String, () => JsCmd), + theScreen: AbstractScreen, + ajax_? : Boolean): NodeSeq = { + + import FieldBindingUtils._ + + NextId.set(nextId._1) + PrevId.set(prevId map (_._1)) + CancelId.set(cancelId._1) + + val notices: List[(NoticeType.Value, NodeSeq, Box[String])] = S.getAllNotices + + def fieldsWithStyle(style: BindingStyle, includeMissing: Boolean) = + logger.trace("Looking for fields with style %s, includeMissing = %s".format(style, includeMissing), + fields filter (field => field.binding map (_.bindingStyle == style) openOr (includeMissing))) + + def bindingInfoWithFields(style: BindingStyle) = + logger.trace("Looking for fields with style %s".format(style), + (for { + field <- fields; + bindingInfo <- field.binding if bindingInfo.bindingStyle == style + } yield (bindingInfo, field)).toList) + + def templateFields: List[CssBindFunc] = List(sel(_.fieldContainer, ".%s") #> (fieldsWithStyle(Template, true) map (field => bindField(field)))) + + def selfFields: List[CssBindFunc] = + for ((bindingInfo, field) <- bindingInfoWithFields(Self)) + yield traceInline("Binding self field %s".format(bindingInfo.selector(formName)), + bindingInfo.selector(formName) #> bindField(field)) + + def defaultFields: List[CssBindFunc] = + for ((bindingInfo, field) <- bindingInfoWithFields(Default)) + yield traceInline("Binding default field %s to %s".format(bindingInfo.selector(formName), defaultFieldNodeSeq), + bindingInfo.selector(formName) #> bindField(field)(defaultFieldNodeSeq)) + + def customFields: List[CssBindFunc] = + for { + field <- fields + bindingInfo <- field.binding + custom <- Some(bindingInfo.bindingStyle) collect { case c:Custom => c } + } yield traceInline("Binding custom field %s to %s".format(bindingInfo.selector(formName), custom.template), + bindingInfo.selector(formName) #> bindField(field)(custom.template)) + + def bindFields: CssBindFunc = { + logger.trace("Binding fields", fields) + List(templateFields, selfFields, defaultFields, customFields).flatten.reduceLeft(_ & _) + } + + def bindField(f: ScreenFieldInfo): NodeSeq => NodeSeq = { + val theFormEarly = f.input + val curId = theFormEarly.flatMap(Helpers.findId) or + f.field.uniqueFieldId openOr Helpers.nextFuncName + + val theForm = theFormEarly.map{ + fe => { + val f = Helpers.deepEnsureUniqueId(fe) + val id = Helpers.findBox(f)(_.attribute("id"). + map(_.text). + filter(_ == curId)) + if (id.isEmpty) { + Helpers.ensureId(f, curId) + } else { + f + } + } + } + + val myNotices = notices.filter(fi => fi._3.isDefined && fi._3 == curId) + + def bindLabel(): CssBindFunc = { + val basicLabel = sel(_.label, ".%s [for]") #> curId & nsSetChildren(_.label, f.text ++ labelSuffix) + myNotices match { + case Nil => basicLabel + case _ => + val maxN = myNotices.map(_._1).sortWith{_.id > _.id}.head // get the maximum type of notice (Error > Warning > Notice) + val metaData: MetaData = noticeTypeToAttr(theScreen).map(_(maxN)) openOr Null + basicLabel & update(_.label, metaData) + } + } + + def bindForm(): CssBindFunc = + traceInline("Replacing %s with %s".format(replace(_.value), theForm), + replace(_.value) #> theForm) + + def bindHelp(): CssBindFunc = + f.help match { + case Full(hlp) => nsSetChildren(_.help, hlp) + case _ => remove(_.help) + } + + def bindErrors(): CssBindFunc = + myNotices match { + case Nil => remove(_.errors) + case xs => replaceChildren(_.errors) #> xs.map { case(noticeType, msg, _) => + val metaData: MetaData = noticeTypeToAttr(theScreen).map(_(noticeType)) openOr Null + nsSetChildren(_.error, msg) & update(_.error, metaData) + } + } + + def bindAll() = bindLabel() & bindForm() & bindHelp() & bindErrors() + + f.transform map (func => bindAll() andThen func()) openOr (bindAll()) + } + + def url = S.uri + + val savAdditionalFormBindings = additionalFormBindings + + def bindErrors: CssBindFunc = notices.filter(_._3.isEmpty) match { + case Nil => remove(_.globalErrors) + case xs => replaceChildren(_.globalErrors) #> xs.map { case(noticeType, msg, _) => + val metaData: MetaData = noticeTypeToAttr(theScreen).map(_(noticeType)) openOr Null + nsSetChildren(_.error, msg) & update(_.error, metaData) + } + } + + def bindFieldsWithAdditional(xhtml: NodeSeq) = + (savAdditionalFormBindings map (bindFields & _) openOr (bindFields))(xhtml) + + def liftScreenAttr(s: String) = + new UnprefixedAttribute("data-lift-screen-control", Text(s), Null) + + def bindForm(xhtml: NodeSeq): NodeSeq = { + val fields = bindFieldsWithAdditional(xhtml) + + val snapshot = createSnapshot + + val ret = + (
    {S.formGroup(-1)(SHtml.hidden(() => + snapshot.restore()) % liftScreenAttr("restoreAction"))}{fields}{ + S.formGroup(4)( + SHtml.hidden(() => + {val res = nextId._2(); + if (!ajax_?) { + val localSnapshot = createSnapshot + S.seeOther(url, () => { + localSnapshot.restore + })} + res + })) % liftScreenAttr("nextAction") }
    % + theScreen.additionalAttributes) ++ + prevId.toList.map{case (id, func) => +
    { + SHtml.hidden(() => {snapshot.restore(); + val res = func(); + if (!ajax_?) { + val localSnapshot = createSnapshot; + S.seeOther(url, () => localSnapshot.restore) + } + res + }) % liftScreenAttr("restoreAction")}
    + } ++ +
    {SHtml.hidden(() => { + snapshot.restore(); + val res = cancelId._2() // WizardRules.deregisterWizardSession(CurrentSession.is) + if (!ajax_?) { + S.seeOther(Referer.get) + } + res + }) % liftScreenAttr("restoreAction")}
    + + if (ajax_?) { + SHtml.makeFormsAjax(ret) + } else { + ret + } + } + + def bindScreenInfo: CssBindFunc = (currentScreenNumber, screenCount) match { + case (Full(num), Full(cnt)) => + replaceChildren(_.screenInfo) #> (nsSetChildren(_.screenNumber, num) & nsSetChildren(_.totalScreens, cnt)) + case _ => remove(_.screenInfo) + } + + logger.trace("Preparing to bind", fields) + + val bindingFunc: CssBindFunc = + bindScreenInfo & + optSetChildren(_.wizardTop, wizardTop) & + optSetChildren(_.screenTop, screenTop) & + optSetChildren(_.wizardBottom, wizardBottom) & + optSetChildren(_.screenBottom, screenBottom) & + nsReplace(_.prev, prev openOr EntityRef("nbsp")) & + nsReplace(_.next, ((next or finish) openOr EntityRef("nbsp"))) & + nsReplace(_.cancel, cancel openOr EntityRef("nbsp")) & + bindErrors & + funcSetChildren(_.fields, bindForm _) + + val processed = S.session map (_.runTemplate("css-bound-screen", allTemplate)) openOr (allTemplate) + + (savAdditionalFormBindings map (bindingFunc & _) openOr (bindingFunc))(processed) + } + + override protected def allTemplateNodeSeq: NodeSeq = { +
    +
    + Page of +
    +
    +
    +
    +
    +
    +
    + + + + + +
    + + +
    +
    +
    +
    +
    +
    + + + + + + +
    +
    +
    +
    +
    + } + + def defaultFieldNodeSeq: NodeSeq = NodeSeq.Empty + + class CssClassBinding { + def screenInfo = "screenInfo" + def wizardTop = "wizardTop" + def screenTop = "screenTop" + def globalErrors = "globalErrors" + def fields = "fields" + def fieldContainer = "fieldContainer" + def label = "label" + def help = "help" + def errors = "errors" + def error = "error" + def value = "value" + def prev = "prev" + def cancel = "cancel" + def next = "next" + def screenBottom = "screenBottom" + def wizardBottom = "wizardBottom" + def screenNumber = "screenNumber" + def totalScreens = "totalScreens" + } +} diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala index 87f8969b45..230aa2f38f 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala @@ -169,6 +169,8 @@ trait AbstractScreen extends Factory { */ def otherValue: OtherValueType = _otherValue.get + def setOtherValue(v: OtherValueType) = _otherValue.set(v) + def default: ValueType def is = _currentValue.is @@ -224,6 +226,10 @@ trait AbstractScreen extends Factory { vendAVar(Helpers.nextFuncName) override def toString = is.toString + + def binding: Box[FieldBinding] = Empty + + def transform: Box[() => CssSel] = Empty } protected object currentField extends ThreadGlobal[FieldIdentifier] @@ -271,6 +277,22 @@ trait AbstractScreen extends Factory { case OnConfirmScreen => true }.headOption + val newBinding: Box[FieldBinding] = (stuff.collect { + case AFieldBinding(i) => i + }).headOption + + val newHelp: Box[NodeSeq] = help or (stuff.collect { + case Help(ns) => ns + }).headOption + + val newTransform: Box[() => CssSel] = (stuff.collect { + case FieldTransform(func) => func + }).headOption + + val newShow: Box[() => Boolean] = (stuff.collect { + case DisplayIf(func) => func + }).headOption + new Field { type ValueType = T @@ -290,7 +312,7 @@ trait AbstractScreen extends Factory { override implicit def manifest: Manifest[ValueType] = FieldBuilder.this.manifest - override def helpAsHtml = help + override def helpAsHtml = newHelp override def validations = FieldBuilder.this.validations @@ -299,8 +321,14 @@ trait AbstractScreen extends Factory { override def uniqueFieldId: Box[String] = paramFieldId or Full(_theFieldId.get) + override def binding = newBinding + private lazy val _theFieldId: NonCleanAnyVar[String] = vendAVar(Helpers.nextFuncName) + + override def transform = newTransform + + override def show_? = newShow map (_()) openOr (super.show_?) } } } @@ -345,6 +373,8 @@ trait AbstractScreen extends Factory { implicit def promoteToFormParam(a: SHtml.ElemAttr): FilterOrValidate[Nothing] = FormParam(a) implicit def promoteToFormParam(a: (String, String)): FilterOrValidate[Nothing] = FormParam(a) + + implicit def promoteFieldBinding(binding: FieldBinding): FilterOrValidate[Nothing] = AFieldBinding(binding) } sealed protected trait FilterOrValidate[+T] @@ -370,6 +400,14 @@ trait AbstractScreen extends Factory { protected final case class AVal[T](v: T => List[FieldError]) extends FilterOrValidate[T] + protected final case class AFieldBinding(binding: FieldBinding) extends FilterOrValidate[Nothing] + + protected final case class Help(ns: NodeSeq) extends FilterOrValidate[Nothing] + + protected final case class FieldTransform(func: () => CssSel) extends FilterOrValidate[Nothing] + + protected final case class DisplayIf(func: () => Boolean) extends FilterOrValidate[Nothing] + protected def field[T](underlying: => BaseField {type ValueType = T}, stuff: FilterOrValidate[T]*)(implicit man: Manifest[T]): Field {type ValueType = T} = { val paramFieldId: Box[String] = (stuff.collect { @@ -383,6 +421,22 @@ trait AbstractScreen extends Factory { case OnConfirmScreen => true }.headOption + val newBinding: Box[FieldBinding] = (stuff.collect { + case AFieldBinding(i) => i + }).headOption + + val newHelp: Box[NodeSeq] = (stuff.collect { + case Help(ns) => ns + }).headOption + + val newTransform: Box[() => CssSel] = (stuff.collect { + case FieldTransform(func) => func + }).headOption + + val newShow: Box[() => Boolean] = (stuff.collect { + case DisplayIf(func) => func + }).headOption + new Field { type ValueType = T @@ -396,7 +450,7 @@ trait AbstractScreen extends Factory { /** * Give the current state of things, should the this field be shown */ - override def show_? = underlying.show_? + override def show_? = newShow map (_()) openOr underlying.show_? /** * What form elements are we going to add to this field? @@ -420,7 +474,7 @@ trait AbstractScreen extends Factory { override implicit def manifest: Manifest[ValueType] = man - override def helpAsHtml = underlying.helpAsHtml + override def helpAsHtml = newHelp or underlying.helpAsHtml override def validate: List[FieldError] = underlying.validate @@ -441,6 +495,10 @@ trait AbstractScreen extends Factory { override def set(v: T) = underlying.set(setFilter.foldLeft(v)((v, f) => f(v))) override def uniqueFieldId: Box[String] = paramFieldId or underlying.uniqueFieldId or super.uniqueFieldId + + override def binding = newBinding or super.binding + + override def transform = newTransform or super.transform } } @@ -458,6 +516,22 @@ trait AbstractScreen extends Factory { case FormFieldId(id) => id }).headOption + val newBinding: Box[FieldBinding] = (stuff.collect { + case AFieldBinding(i) => i + }).headOption + + val newHelp: Box[NodeSeq] = (stuff.collect { + case Help(ns) => ns + }).headOption + + val newTransform: Box[() => CssSel] = (stuff.collect { + case FieldTransform(func) => func + }).headOption + + val newShow: Box[() => Boolean] = (stuff.collect { + case DisplayIf(func) => func + }).headOption + val confirmInfo = stuff.collect { case NotOnConfirmScreen => false }.headOption orElse @@ -478,7 +552,7 @@ trait AbstractScreen extends Factory { /** * Give the current state of things, should the this field be shown */ - override def show_? = underlying.map(_.show_?) openOr false + override def show_? = newShow map (_()) openOr (underlying.map(_.show_?) openOr false) /** * What form elements are we going to add to this field? @@ -502,7 +576,7 @@ trait AbstractScreen extends Factory { override implicit def manifest: Manifest[ValueType] = man - override def helpAsHtml = underlying.flatMap(_.helpAsHtml) + override def helpAsHtml = newHelp or underlying.flatMap(_.helpAsHtml) override def validate: List[FieldError] = underlying.toList.flatMap(_.validate) @@ -517,6 +591,10 @@ trait AbstractScreen extends Factory { override def set(v: T) = underlying.open_!.set(setFilter.foldLeft(v)((v, f) => f(v))) override def uniqueFieldId: Box[String] = paramFieldId or underlying.flatMap(_.uniqueFieldId) or super.uniqueFieldId + + override def binding = newBinding or super.binding + + override def transform = newTransform or super.transform } } @@ -636,6 +714,22 @@ trait AbstractScreen extends Factory { otherValue: OtherValueInitializer[OV], stuff: FilterOrValidate[T]*): Field {type ValueType = T; type OtherValueType = OV} = { + val newBinding: Box[FieldBinding] = (stuff.collect { + case AFieldBinding(i) => i + }).headOption + + val newHelp: Box[NodeSeq] = (stuff.collect { + case Help(ns) => ns + }).headOption + + val newTransform: Box[() => CssSel] = (stuff.collect { + case FieldTransform(func) => func + }).headOption + + val newShow: Box[() => Boolean] = (stuff.collect { + case DisplayIf(func) => func + }).headOption + otherValue match { case OtherValueInitializerImpl(otherValueInitFunc) => { new Field { @@ -664,7 +758,15 @@ trait AbstractScreen extends Factory { case _ => Nil }.toList + override def binding = newBinding + + override def helpAsHtml = newHelp + override def toForm: Box[NodeSeq] = theToForm(this) + + override def transform = newTransform + + override def show_? = newShow map (_()) openOr (super.show_?) } } @@ -693,7 +795,15 @@ trait AbstractScreen extends Factory { case _ => Nil }.toList + override def binding = newBinding + + override def helpAsHtml = newHelp + override def toForm: Box[NodeSeq] = theToForm(this) + + override def transform = newTransform + + override def show_? = newShow map (_()) openOr (super.show_?) } } } @@ -870,6 +980,7 @@ trait AbstractScreen extends Factory { OtherValueInitializerImpl[Seq[String]](() => choices), stuff: _*) } + } @@ -1205,7 +1316,21 @@ trait ScreenWizardRendered { } -case class ScreenFieldInfo(field: FieldIdentifier, text: NodeSeq, help: Box[NodeSeq], input: Box[NodeSeq]) +case class ScreenFieldInfo( + field: FieldIdentifier, + text: NodeSeq, + help: Box[NodeSeq], + input: Box[NodeSeq], + binding: Box[FieldBinding], + transform: Box[() => CssSel]) { + def this(field: FieldIdentifier, text: NodeSeq, help: Box[NodeSeq], input: Box[NodeSeq]) = + this(field, text, help, input, Empty, Empty) + } + +object ScreenFieldInfo { + def apply(field: FieldIdentifier, text: NodeSeq, help: Box[NodeSeq], input: Box[NodeSeq]) = + new ScreenFieldInfo(field, text, help, input) +} trait LiftScreen extends AbstractScreen with StatefulSnippet with ScreenWizardRendered { def dispatch = { @@ -1237,7 +1362,7 @@ trait LiftScreen extends AbstractScreen with StatefulSnippet with ScreenWizardRe override lazy val __nameSalt = Helpers.nextFuncName } - private object PrevSnapshot extends TransientRequestVar[Box[ScreenSnapshot]](Empty) { + protected object PrevSnapshot extends TransientRequestVar[Box[ScreenSnapshot]](Empty) { override lazy val __nameSalt = Helpers.nextFuncName } @@ -1257,6 +1382,10 @@ trait LiftScreen extends AbstractScreen with StatefulSnippet with ScreenWizardRe override lazy val __nameSalt = Helpers.nextFuncName } + object NextId extends ScreenVar[String](Helpers.nextFuncName) { + override lazy val __nameSalt = Helpers.nextFuncName + } + /** * What to do when the Screen is done. By default, will * do a redirect back to Whence, but you can change this behavior, @@ -1344,6 +1473,7 @@ trait LiftScreen extends AbstractScreen with StatefulSnippet with ScreenWizardRe Referer.get // touch to capture the referer Ajax_?.get // capture the ajaxiness of these forms FormGUID.get + NextId.get if (FirstTime) { FirstTime.set(false) @@ -1366,7 +1496,7 @@ trait LiftScreen extends AbstractScreen with StatefulSnippet with ScreenWizardRe } protected def renderHtml(): NodeSeq = { - val finishId = Helpers.nextFuncName + val finishId = NextId.get val cancelId = Helpers.nextFuncName val theScreen = this @@ -1389,13 +1519,25 @@ trait LiftScreen extends AbstractScreen with StatefulSnippet with ScreenWizardRe val url = S.uri + def fieldBinding(field: BaseField): Box[FieldBinding] = + field match { + case f: Field => f.binding + case _ => Empty + } + + def fieldTransform(field: BaseField): Box[() => CssSel] = + field match { + case f: Field => f.transform + case _ => Empty + } + renderAll( Empty, //currentScreenNumber: Box[NodeSeq], Empty, //screenCount: Box[NodeSeq], Empty, // wizardTop: Box[Elem], theScreen.screenTop, //screenTop: Box[Elem], theScreen.screenFields.filter(_.shouldDisplay_?).flatMap(f => - if (f.show_?) List(ScreenFieldInfo(f, f.displayHtml, f.helpAsHtml, f.toForm)) else Nil), //fields: List[ScreenFieldInfo], + if (f.show_?) List(ScreenFieldInfo(f, f.displayHtml, f.helpAsHtml, f.toForm, fieldBinding(f), fieldTransform(f))) else Nil), //fields: List[ScreenFieldInfo], Empty, // prev: Box[Elem], Full(cancelButton), // cancel: Box[Elem], Empty, // next: Box[Elem], @@ -1500,3 +1642,35 @@ object LiftScreenRules extends Factory with FormVendor { } +case class FieldBinding(val fieldName: String, val bindingStyle: FieldBinding.BindingStyle) { + def fieldId(formName: String) = "%s_%s_field" format (formName, fieldName) + def selector(formName: String) = "#%s" format (fieldId(formName)) + def childSelector(formName: String) = "#%s *" format (fieldId(formName)) + def idSelector(formName: String) = selector(formName) + " [id]" +} + +object FieldBinding { + sealed abstract class BindingStyle + + /** + * Bind the field using the default template defined in an external template (as in Bind.helpers()-based binding) + */ + case object Template extends BindingStyle + + /** + * Bind the field using the template defined in the body of the field reference + */ + case object Self extends BindingStyle + + /** + * Bind the field using the template returned by the defaultFieldNodeSeq method + */ + case object Default extends BindingStyle + + /** + * Bind the field using the template provided + */ + case class Custom(template: NodeSeq) extends BindingStyle + + def apply(fieldName: String) = new FieldBinding(fieldName, Default) +} From 71cdc780c0e38bf655292cd79dce2b1aa9545f58 Mon Sep 17 00:00:00 2001 From: Jeppe Nejsum Madsen Date: Sun, 3 Jun 2012 16:40:01 +0200 Subject: [PATCH 0063/1949] Minor changes Deprecated implicit conversion from MappedField[T] to T. Fixes #1247 Make SHtml.ApplicableElem public to allow user defined SHtml extensions in the same style as existing methods. Fixes #1273 --- .../src/main/scala/net/liftweb/mapper/MappedField.scala | 1 + web/webkit/src/main/scala/net/liftweb/http/SHtml.scala | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/persistence/mapper/src/main/scala/net/liftweb/mapper/MappedField.scala b/persistence/mapper/src/main/scala/net/liftweb/mapper/MappedField.scala index 357e7ba55f..39b05dc465 100644 --- a/persistence/mapper/src/main/scala/net/liftweb/mapper/MappedField.scala +++ b/persistence/mapper/src/main/scala/net/liftweb/mapper/MappedField.scala @@ -695,6 +695,7 @@ trait MappedField[FieldType <: Any,OwnerType <: Mapper[OwnerType]] extends Typed } object MappedField { + @deprecated("Automatic conversion to the field's type is not safe and will be removed. Please use field.get instead") implicit def mapToType[T, A<:Mapper[A]](in : MappedField[T, A]): T = in.is } diff --git a/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala b/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala index 7c75af043e..ec3dc58561 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala @@ -109,11 +109,11 @@ trait SHtml { } } - private class ApplicableElem(in: Elem) { + class ApplicableElem(in: Elem) { def %(attr: ElemAttr): Elem = attr.apply(in) } - private implicit def elemToApplicable(e: Elem): ApplicableElem = + implicit def elemToApplicable(e: Elem): ApplicableElem = new ApplicableElem(e) /** From ddbea986cff354bf966cc77e1c29eb2a9c6de557 Mon Sep 17 00:00:00 2001 From: Jeppe Nejsum Madsen Date: Tue, 5 Jun 2012 20:39:02 +0300 Subject: [PATCH 0064/1949] Update versions to 2.4 --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 34ea9c1b2a..eec04c7758 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Then, add the plugin and Lift to your `build.sbt` file: seq(webSettings :_*) libraryDependencies ++= { - val liftVersion = "2.4-M4" + val liftVersion = "2.4" Seq( "net.liftweb" %% "lift-webkit" % liftVersion % "compile", "org.mortbay.jetty" % "jetty" % "6.1.22" % "container", @@ -45,7 +45,7 @@ Or, you can add Lift to your `pom.xml` like so: net.liftweb lift-mapper_${scala.version} - 2.4-SNAPSHOT + 2.4 Where `${scala.version}` is `2.8.0`, `2.8.1`, `2.9.1` etc. @@ -112,7 +112,7 @@ The Lift wiki is hosted on Assembla and can be found at [http://www.assembla.com ### ScalaDocs -The ScalaDocs for each release of Lift, in additional to the actual JARs, are available on ScalaTools. You can access the source code-based documentation for releases via the site's homepage or by navigating directly to the URL for the specific release. For instance, the Lift 2.4-M4 release can be accessed at [http://scala-tools.org/mvnsites/liftweb-2.4-M4/](http://scala-tools.org/mvnsites/liftweb-2.4-M4/). +The ScalaDocs for each release of Lift, in additional to the actual JARs, are available on ScalaTools. You can access the source code-based documentation for releases via the site's homepage or by navigating directly to the URL for the specific release. For instance, the Lift 2.4 release can be accessed at [http://scala-tools.org/mvnsites/liftweb-2.4/](http://scala-tools.org/mvnsites/liftweb-2.4/). ## License From bc58db5ce2e43128a6bad637839c173715f5e00c Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Thu, 7 Jun 2012 18:35:05 -0400 Subject: [PATCH 0065/1949] Discard invalid comet requests when checking cometForHost. Invalid comet requests are requests that throw an exception when we try to access their hostname. This is known to happen in Jetty, and manifests in slightly different ways across containers, when we attempt to read data from a request that the container is no longer willing to service. These requests are immediately discarded using the existing makeCometBreakoutDecision code. --- .../scala/net/liftweb/http/LiftRules.scala | 8 ++++-- .../scala/net/liftweb/http/LiftSession.scala | 28 +++++++++++++++++-- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index c4a1f121d5..80eb0f24e2 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -301,8 +301,9 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { @volatile var makeCometBreakoutDecision: (LiftSession, Req) => Unit = (session, req) => { // get the open sessions to the host (this means that any DNS wildcarded - // Comet requests will not be counted - val which = session.cometForHost(req.hostAndPath) + // Comet requests will not be counted), as well as all invalid/expired + // sessions + val (which, invalid) = session.cometForHost(req.hostAndPath) // get the maximum requests given the browser type val max = maxConcurrentRequests.vend(req) - 2 // this request and any open comet requests @@ -311,6 +312,9 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { which.drop(max).foreach { case (actor, req) => actor ! BreakOut() } + invalid.foreach { + case (actor, req) => actor ! BreakOut() + } } /** diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index db0484b1cc..2bd68e7c05 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -612,11 +612,33 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, cl.foreach(_._1 ! BreakOut()) } - private[http] def cometForHost(hostAndPath: String): List[(LiftActor, Req)] = + // Returns a 2-tuple: _1 is a list of valid (LiftActor, Req) pairs for + // this session that match the given hostAndPath, while _2 is a list + // of invalid (LiftActor, Req) pairs. + // + // Invalid pairs are pairs where the hostAndPath lookup for the + // associated Req fails by throwing an exception. Typically this + // happens on overloaded containers that leave Reqs with underlying + // HttpServletRequests that have expired; these will then throw + // NullPointerExceptions when their server name or otherwise are + // accessed. + private[http] def cometForHost(hostAndPath: String): (List[(LiftActor, Req)], List[(LiftActor, Req)]) = synchronized { cometList - }.filter { - case (_, r) => r.hostAndPath == hostAndPath + }.foldLeft((List[(LiftActor, Req)](), List[(LiftActor, Req)]())) { + (soFar, current) => + (soFar, current) match { + case ((valid, invalid), pair @ (_, r)) => + try { + if (r.hostAndPath == hostAndPath) + (pair :: valid, invalid) + else + soFar + } catch { + case exception => + (valid, pair :: invalid) + } + } } private[http] def enterComet(what: (LiftActor, Req)): Unit = synchronized { From bec9a49a681d991d5cd7c5d8547e8daf9adb416a Mon Sep 17 00:00:00 2001 From: Tim Nelson Date: Thu, 7 Jun 2012 18:17:32 -0500 Subject: [PATCH 0066/1949] Issue 1227 - Add configurable ObjectId tester in JObjectParser --- .../mongodb/record/MongoRecordSpec.scala | 57 +++++++++++++++++- .../net/liftweb/mongodb/JObjectParser.scala | 16 ++++- .../liftweb/mongodb/JObjectParserSpec.scala | 59 +++++++++++++++++++ project/Build.scala | 2 +- 4 files changed, 127 insertions(+), 7 deletions(-) create mode 100644 persistence/mongodb/src/test/scala/net/liftweb/mongodb/JObjectParserSpec.scala diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala index 035fcfc908..8cfe355f04 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala @@ -658,17 +658,34 @@ object MongoRecordSpec extends Specification("MongoRecord Specification") with M .optionalStringField("optional string") .save - fttr.mandatoryBooleanField(false) + fttr.mandatoryBooleanField(true) + fttr.mandatoryBooleanField.dirty_? must_== true + fttr.mandatoryDecimalField(BigDecimal("3.14")) + fttr.mandatoryDecimalField.dirty_? must_== true + fttr.mandatoryDoubleField(1999) - fttr.mandatoryEnumField(MyTestEnum.ONE) + fttr.mandatoryDoubleField.dirty_? must_== true + + fttr.mandatoryEnumField(MyTestEnum.TWO) + fttr.mandatoryEnumField.dirty_? must_== true + fttr.mandatoryIntField(99) + fttr.mandatoryIntField.dirty_? must_== true + fttr.mandatoryLongField(100L) + fttr.mandatoryLongField.dirty_? must_== true + fttr.mandatoryStringField("string") + fttr.mandatoryStringField.dirty_? must_== true + fttr.optionalStringField(Empty) + fttr.optionalStringField.dirty_? must_== true + fttr.legacyOptionalStringField(Empty) + fttr.legacyOptionalStringField.dirty_? must_== true - fttr.dirtyFields.length must_== 9 + fttr.dirtyFields.length must_== 7 fttr.update fttr.dirtyFields.length must_== 0 @@ -682,7 +699,10 @@ object MongoRecordSpec extends Specification("MongoRecord Specification") with M val fttr2 = FieldTypeTestRecord.createRecord.save fttr2.legacyOptionalStringField("legacy optional string") + fttr2.legacyOptionalStringField.dirty_? must_== true + fttr2.optionalStringField("optional string") + fttr2.optionalStringField.dirty_? must_== true fttr2.dirtyFields.length must_== 2 fttr2.update @@ -704,13 +724,28 @@ object MongoRecordSpec extends Specification("MongoRecord Specification") with M .legacyOptionalObjectIdField(ObjectId.get) .save + Thread.sleep(100) // sleep so dates will be different + mfttr.mandatoryDateField(new Date) + mfttr.mandatoryDateField.dirty_? must_== true + mfttr.mandatoryJsonObjectField(TypeTestJsonObject(1, "jsonobj1", Map("x" -> "1"))) + mfttr.mandatoryJsonObjectField.dirty_? must_== true + mfttr.mandatoryObjectIdField(ObjectId.get) + mfttr.mandatoryObjectIdField.dirty_? must_== true + mfttr.mandatoryPatternField(Pattern.compile("^Mon", Pattern.CASE_INSENSITIVE)) + mfttr.mandatoryPatternField.dirty_? must_== true + mfttr.mandatoryUUIDField(UUID.randomUUID) + mfttr.mandatoryUUIDField.dirty_? must_== true + mfttr.legacyOptionalDateField(Empty) + mfttr.legacyOptionalDateField.dirty_? must_== true + mfttr.legacyOptionalObjectIdField(Empty) + mfttr.legacyOptionalObjectIdField.dirty_? must_== true mfttr.dirtyFields.length must_== 7 mfttr.update @@ -726,7 +761,10 @@ object MongoRecordSpec extends Specification("MongoRecord Specification") with M val mfttr2 = MongoFieldTypeTestRecord.createRecord.save mfttr2.legacyOptionalDateField(new Date) + mfttr2.legacyOptionalDateField.dirty_? must_== true + mfttr2.legacyOptionalObjectIdField(ObjectId.get) + mfttr2.legacyOptionalObjectIdField.dirty_? must_== true mfttr2.dirtyFields.length must_== 2 mfttr2.update @@ -746,9 +784,16 @@ object MongoRecordSpec extends Specification("MongoRecord Specification") with M val ltr = ListTestRecord.createRecord.save ltr.mandatoryStringListField(List("abc", "def", "ghi")) + ltr.mandatoryStringListField.dirty_? must_== true + ltr.mandatoryIntListField(List(4, 5, 6)) + ltr.mandatoryIntListField.dirty_? must_== true + ltr.mandatoryMongoJsonObjectListField(List(TypeTestJsonObject(1, "jsonobj1", Map("x" -> "1")), TypeTestJsonObject(2, "jsonobj2", Map("x" -> "2")))) + ltr.mandatoryMongoJsonObjectListField.dirty_? must_== true + ltr.mongoCaseClassListField(List(MongoCaseClassTestObject(1,"str"))) + ltr.mongoCaseClassListField.dirty_? must_== true ltr.dirtyFields.length must_== 4 ltr.update @@ -768,7 +813,10 @@ object MongoRecordSpec extends Specification("MongoRecord Specification") with M val mtr = MapTestRecord.save mtr.mandatoryStringMapField(Map("a" -> "abc", "b" -> "def", "c" -> "ghi")) + mtr.mandatoryStringMapField.dirty_? must_== true + mtr.mandatoryIntMapField(Map("a" -> 4, "b" -> 5, "c" -> 6)) + mtr.mandatoryIntMapField.dirty_? must_== true mtr.dirtyFields.length must_== 2 mtr.update @@ -801,7 +849,10 @@ object MongoRecordSpec extends Specification("MongoRecord Specification") with M val sr2 = SubRecord.createRecord.name("SubRecord2") srtr.mandatoryBsonRecordField(sr1) + srtr.mandatoryBsonRecordField.dirty_? must_== true + srtr.mandatoryBsonRecordListField(List(sr1,sr2)) + srtr.mandatoryBsonRecordListField.dirty_? must_== true srtr.dirtyFields.length must_== 2 srtr.update diff --git a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/JObjectParser.scala b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/JObjectParser.scala index 443b917c21..d664aee401 100644 --- a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/JObjectParser.scala +++ b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/JObjectParser.scala @@ -24,11 +24,22 @@ import java.util.regex.Pattern import net.liftweb.json._ import net.liftweb.common.Box +import net.liftweb.util.SimpleInjector import com.mongodb.{BasicDBObject, BasicDBList, DBObject} import org.bson.types.ObjectId -object JObjectParser { +object JObjectParser extends SimpleInjector { + /** + * Set this to override JObjectParser turning strings that are valid + * ObjectIds into actual ObjectIds. For example, place the following in Boot.boot: + * + * JObjectParser.stringProcessor.default.set((s: String) => s) + */ + val stringProcessor = new Inject(() => (s: String) => { + if (ObjectId.isValid(s)) new ObjectId(s) + else s + }) {} /* * Parse a JObject into a DBObject @@ -113,8 +124,7 @@ object JObjectParser { case JNull => null case JNothing => error("can't render 'nothing'") case JString(null) => "null" - case JString(s) if (ObjectId.isValid(s)) => new ObjectId(s) - case JString(s) => s + case JString(s) => stringProcessor.vend(s) case _ => "" } diff --git a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/JObjectParserSpec.scala b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/JObjectParserSpec.scala new file mode 100644 index 0000000000..6aa12477de --- /dev/null +++ b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/JObjectParserSpec.scala @@ -0,0 +1,59 @@ +/* + * Copyright 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 mongodb + +import json._ +import JsonDSL._ +import util.Helpers._ + +import org.bson.types.ObjectId +import org.specs.Specification + +import com.mongodb.DBObject + +object JObjectParserSpec extends Specification("JObjectParser Specification") { + + def buildTestData: (ObjectId, DBObject) = { + val oid = ObjectId.get + val dbo = JObjectParser.parse(("x" -> oid.toString))(DefaultFormats) + (oid, dbo) + } + + "JObjectParser" should { + "convert strings to ObjectId by default" in { + val (oid, dbo) = buildTestData + val xval = tryo(dbo.get("x").asInstanceOf[ObjectId]) + + xval must notBeEmpty + xval.foreach { x => + x must_== oid + } + } + "not convert strings to ObjectId when configured not to" in { + JObjectParser.stringProcessor.default.set((s: String) => s) + + val (oid, dbo) = buildTestData + val xval = tryo(dbo.get("x").asInstanceOf[String]) + + xval must notBeEmpty + xval.foreach { x => + x must_== oid.toString + } + } + } +} diff --git a/project/Build.scala b/project/Build.scala index 910ef9246e..51fb5578b7 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -147,7 +147,7 @@ object BuildDef extends Build { lazy val mongodb = persistenceProject("mongodb") - .dependsOn(json_ext) + .dependsOn(json_ext, util) .settings(parallelExecution in Test := false, libraryDependencies += mongo_driver, initialize in Test <<= (resourceDirectory in Test) { rd => From 48b2d5bc902906e49acd92b622fd68d8ea58abb4 Mon Sep 17 00:00:00 2001 From: Jeppe Nejsum Madsen Date: Tue, 12 Jun 2012 11:16:41 +0200 Subject: [PATCH 0067/1949] Temporary (hopefully!) hack to make tests deterministic --- .../test/scala/net/liftweb/http/NamedCometPerTabSpec.scala | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/web/webkit/src/test/scala/net/liftweb/http/NamedCometPerTabSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/NamedCometPerTabSpec.scala index a28ee5ac6b..065e5753b0 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/NamedCometPerTabSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/NamedCometPerTabSpec.scala @@ -41,7 +41,11 @@ object NamedCometPerTabSpec extends Specification("NamedCometPerTabSpec Specific doBefore { val cometA= new CometA{override def name= Full("1")} cometA.localSetup + + // HACK! to ensure tests doesn't fail when trying to access actor before they've been registered + Thread.sleep(500) } + "be created for a comet" in { NamedCometListener.getDispatchersFor(Full("1")).foreach( actor => actor.open_!.toString must startWith("net.liftweb.http.NamedCometDispatcher") From 238de8ab4b816fda6f8e986d3759db3469b9048a Mon Sep 17 00:00:00 2001 From: Jeppe Nejsum Madsen Date: Tue, 12 Jun 2012 12:48:10 +0300 Subject: [PATCH 0068/1949] Add link to Jenkins --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index eec04c7758..97f59a7b89 100644 --- a/README.md +++ b/README.md @@ -117,3 +117,7 @@ The ScalaDocs for each release of Lift, in additional to the actual JARs, are av ## License Lift is open source software released under the **Apache 2.0 license**. You must be a committer with signed comitter agreement to submit patches. You can learn more about Lift's comitter policy on the Lift website. + +## Continuous Integration + +SNAPSHOTs are built at CloudBees: https://lift.ci.cloudbees.com/ \ No newline at end of file From fbc3efb39864dbc8a9adda70861f82c0678806ab Mon Sep 17 00:00:00 2001 From: Jeppe Nejsum Madsen Date: Wed, 13 Jun 2012 15:32:30 +0200 Subject: [PATCH 0069/1949] Add YUI processor plugin to minify js resources --- project/Build.scala | 4 ++-- project/project/Plugins.scala | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index 910ef9246e..470d9c0dc1 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -87,14 +87,14 @@ object BuildDef extends Build { lazy val webkit = webProject("webkit") .dependsOn(util, testkit % "provided") - .settings(description := "Webkit Library", + .settings((org.scala_tools.sbt.yuiCompressor.Plugin.yuiSettings ++ Seq(description := "Webkit Library", parallelExecution in Test := false, libraryDependencies <++= scalaVersion { sv => Seq(commons_fileupload, servlet_api, specs(sv).copy(configurations = Some("provided")), jetty6, jwebunit) }, initialize in Test <<= (sourceDirectory in Test) { src => System.setProperty("net.liftweb.webapptest.src.test.webapp", (src / "webapp").absString) - }) + })):_*) lazy val wizard = webProject("wizard") diff --git a/project/project/Plugins.scala b/project/project/Plugins.scala index e06d71bc4f..e942eb1983 100644 --- a/project/project/Plugins.scala +++ b/project/project/Plugins.scala @@ -1,6 +1,7 @@ import sbt._ object PluginDef extends Build { - lazy val root = Project("plugins", file(".")) dependsOn(buildPlugin) + lazy val root = Project("plugins", file(".")) dependsOn(buildPlugin, yuiPlugin) + lazy val yuiPlugin = uri("git://github.com/indrajitr/sbt-yui-compressor") lazy val buildPlugin = uri("git://github.com/indrajitr/sbt-lift-build-plugin.git#c78f617f62") } From d7ce7f46ff6189ad92a22628dffeff7f609937b2 Mon Sep 17 00:00:00 2001 From: rusho Date: Wed, 13 Jun 2012 17:27:36 +0200 Subject: [PATCH 0070/1949] Refactored PasswordField. Fixes issues with validations, setFromAny and some other setting methods. --- .../liftweb/record/field/PasswordField.scala | 83 ++++++++++++------- .../scala/net/liftweb/record/FieldSpec.scala | 8 +- 2 files changed, 56 insertions(+), 35 deletions(-) diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/PasswordField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/PasswordField.scala index 95c86882fe..5a0d8bdee2 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/PasswordField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/PasswordField.scala @@ -37,8 +37,8 @@ object PasswordField { } trait PasswordTypedField extends TypedField[String] { - private var invalidMsg : String = "" - private[record] var validatedValue: Box[String] = valueBox + private var invalidPw = false + private var invalidMsg = "" def match_?(toTest: String): Boolean = valueBox.filter(_.length > 0) @@ -46,32 +46,45 @@ trait PasswordTypedField extends TypedField[String] { .openOr(false) override def set_!(in: Box[String]): Box[String] = { - // can't be hashed here, because this get's called when setting value from database + // can't be hashed here, because this get's called when setting value from database (Squeryl) in } def setPlain(in: String): String = setBoxPlain(Full(in)) openOr defaultValue def setBoxPlain(in: Box[String]): Box[String] = { - validatedValue = in - val hashed = in.map(s => PasswordField.hashpw(s) openOr s) - setBox(hashed) + if(!validatePassword(in)) { + val hashed = in.map(s => PasswordField.hashpw(s) openOr s) + setBox(hashed) + } + else setBox(defaultValueBox) } + /** + * If passed value is an Array[String] or a List[String] containing 2 items with equal value, it it hashes this value and sets it as new password. + * If passed value is a String or a Full[String] that starts with "$2a$", it assumes that it's a hashed version, thus sets it as it is, without hashing. + * In any other case, it fails the validation with "Passwords do not match" error + */ def setFromAny(in: Any): Box[String] = { in match { - case (a: Array[String]) if (a.length == 2 && a(0) == a(1)) => setBox(Full(a(0))) - case (h1: String) :: (h2: String) :: Nil if h1 == h2 => setBox(Full(h1)) - case _ => genericSetFromAny(in) + case (a: Array[String]) if (a.length == 2 && a(0) == a(1)) => setBoxPlain(Full(a(0))) + case (h1: String) :: (h2: String) :: Nil if h1 == h2 => setBoxPlain(Full(h1)) + case (hash: String) if(hash.startsWith("$2a$")) => setBox(Full(hash)) + case Full(hash: String) if(hash.startsWith("$2a$")) => setBox(Full(hash)) + case _ => {invalidPw = true; invalidMsg = S.??("passwords.do.not.match"); Failure(invalidMsg)} } } def setFromString(s: String): Box[String] = s match { - case "" if optional_? => setBox(Empty) - case _ => setBox(Full(s)) + case "" if optional_? => setBoxPlain(Empty) + case _ => setBoxPlain(Full(s)) } - override def validate: List[FieldError] = runValidation(validatedValue) + override def validate: List[FieldError] = { + if (!invalidPw && valueBox != defaultValueBox) Nil + else if (invalidPw) List(FieldError(this, Text(invalidMsg))) + else List(FieldError(this, Text(notOptionalErrorMessage))) + } override def notOptionalErrorMessage = S.??("password.must.be.set") @@ -87,26 +100,29 @@ trait PasswordTypedField extends TypedField[String] { case _ => Full(elem) } - protected def validatePassword(pwdValue: ValueType): List[FieldError] = - toBoxMyType(pwdValue) match { - case Empty|Full(""|null) if !optional_? => Text(notOptionalErrorMessage) - case Full(s) if s == "*" || s == PasswordField.blankPw || s.length < PasswordField.minPasswordLength => - Text(S.??("password.too.short")) - case _ => Nil + protected def validatePassword(pwdValue: Box[String]): Boolean = { + pwdValue match { + case Empty|Full(""|null) if !optional_? => { invalidPw = true ; invalidMsg = notOptionalErrorMessage } + case Full(s) if s == "" || s == PasswordField.blankPw || s.length < PasswordField.minPasswordLength => + { invalidPw = true ; invalidMsg = S.??("password.too.short") } + case _ => { invalidPw = false; invalidMsg = "" } } - - override def validations = validatePassword _ :: Nil + invalidPw + } def defaultValue = "" def asJs = valueBox.map(Str) openOr JsNull def asJValue: JValue = valueBox.map(v => JString(v)) openOr (JNothing: JValue) + def setFromJValue(jvalue: JValue): Box[MyType] = jvalue match { - case JNothing|JNull if optional_? => setBox(Empty) + case JNothing|JNull if optional_? => setBoxPlain(Empty) case JString(s) => setFromString(s) - case other => setBox(FieldHelpers.expectedA("JString", other)) + case other => setBoxPlain(FieldHelpers.expectedA("JString", other)) } + + } class PasswordField[OwnerType <: Record[OwnerType]](rec: OwnerType) @@ -120,11 +136,13 @@ class PasswordField[OwnerType <: Record[OwnerType]](rec: OwnerType) def owner = rec override def apply(in: Box[String]): OwnerType = - { - validatedValue = in - val hashed = in.map(s => PasswordField.hashpw(s) openOr s) - super.apply(hashed) - } + if(owner.meta.mutable_?) { + this.setBoxPlain(in) + owner + } else { + owner.meta.createWithMutableField(owner, this, in) + } + } class OptionalPasswordField[OwnerType <: Record[OwnerType]](rec: OwnerType) @@ -138,10 +156,11 @@ class OptionalPasswordField[OwnerType <: Record[OwnerType]](rec: OwnerType) def owner = rec override def apply(in: Box[String]): OwnerType = - { - validatedValue = in - val hashed = in.map(s => PasswordField.hashpw(s) openOr s) - super.apply(hashed) - } + if(owner.meta.mutable_?) { + this.setBoxPlain(in) + owner + } else { + owner.meta.createWithMutableField(owner, this, in) + } } diff --git a/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala b/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala index b96b3fccee..9576536def 100644 --- a/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala +++ b/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala @@ -408,11 +408,13 @@ object FieldSpec extends Specification("Record Field Specification") { ) } - "validate the unencrypted value" in { + "correctly validate the unencrypted value" in { val rec = PasswordTestRecord.createRecord.password("testvalue") - + rec.validate must_== Nil + + rec.password("1234") rec.validate must_== ( - FieldError(rec.password, Text("no way!")) :: + FieldError(rec.password, Text(S.??("password.too.short"))) :: Nil ) } From 3f810a88a3b72824f1980f83e0a651ae5713c297 Mon Sep 17 00:00:00 2001 From: Indrajit Raychaudhuri Date: Sun, 3 Jun 2012 23:22:37 -0500 Subject: [PATCH 0071/1949] fix dependencies for squeryl --- project/Dependencies.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 2113a1f074..982726cde4 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -39,17 +39,17 @@ object Dependencies { lazy val commons_codec = "commons-codec" % "commons-codec" % "1.6" lazy val commons_fileupload = "commons-fileupload" % "commons-fileupload" % "1.2.2" lazy val commons_httpclient = "commons-httpclient" % "commons-httpclient" % "3.1" - lazy val dispatch_http = "net.databinder" % "dispatch-http" % "0.7.8" cross CVMapping2911 + lazy val dispatch_http = "net.databinder" % "dispatch-http" % "0.7.8" cross CVMapping2911 lazy val javamail = "javax.mail" % "mail" % "1.4.4" lazy val joda_time = "joda-time" % "joda-time" % "1.6.2" // TODO: 2.1 lazy val htmlparser = "nu.validator.htmlparser" % "htmlparser" % "1.2.1" lazy val mongo_java_driver = "org.mongodb" % "mongo-java-driver" % "2.7.3" lazy val paranamer = "com.thoughtworks.paranamer" % "paranamer" % "2.4.1" - lazy val scalajpa = "org.scala-libs" % "scalajpa" % "1.4" cross CVMappingAll + lazy val scalajpa = "org.scala-libs" % "scalajpa" % "1.4" cross CVMappingAll lazy val scalap: ModuleMap = "org.scala-lang" % "scalap" % _ lazy val scalaz_core: ModuleMap = sv => scalazGroup(sv) % "scalaz-core" % scalazVersion(sv) cross CVMappingAll lazy val slf4j_api = "org.slf4j" % "slf4j-api" % slf4jVersion - lazy val squeryl = "org.squeryl" % "squeryl" % "0.9.5" cross CVMappingAll // TODO: 0.9.5 + lazy val squeryl = "org.squeryl" % "squeryl" % "0.9.5-1" cross crossMapped("2.9.1-1" -> "2.9.1", "2.8.2" -> "2.8.1") // Aliases lazy val mongo_driver = mongo_java_driver From 4f5913189b2976ce4ad31c43f0899d390c51faeb Mon Sep 17 00:00:00 2001 From: Indrajit Raychaudhuri Date: Mon, 18 Jun 2012 23:35:41 -0500 Subject: [PATCH 0072/1949] Move to SBT 0.12.0-RC2 --- liftsh | 2 +- liftsh.cmd | 2 +- project/build.properties | 2 +- project/sbt-launch-0.12.0-M2.jar | Bin 1055107 -> 0 bytes project/sbt-launch-0.12.0-RC2.jar | Bin 0 -> 1103469 bytes 5 files changed, 3 insertions(+), 3 deletions(-) delete mode 100644 project/sbt-launch-0.12.0-M2.jar create mode 100644 project/sbt-launch-0.12.0-RC2.jar diff --git a/liftsh b/liftsh index 3d096a3fae..8017a1f68a 100755 --- a/liftsh +++ b/liftsh @@ -17,4 +17,4 @@ DEFAULT_OPTS="" cd `dirname $0` # Call with INTERNAL_OPTS followed by LIFTSH_OPTS (or DEFAULT_OPTS). java always takes the last option when duplicate. -exec java ${INTERNAL_OPTS} ${LIFTSH_OPTS:-${DEFAULT_OPTS}} -jar project/sbt-launch-0.12.0-M2.jar "$@" +exec java ${INTERNAL_OPTS} ${LIFTSH_OPTS:-${DEFAULT_OPTS}} -jar project/sbt-launch-0.12.0-RC2.jar "$@" diff --git a/liftsh.cmd b/liftsh.cmd index 379b95822a..5ea476dce0 100644 --- a/liftsh.cmd +++ b/liftsh.cmd @@ -17,4 +17,4 @@ if "%LIFTSH_OPTS%"=="" ( ) @REM Call with INTERNAL_OPTS followed by LIFTSH_OPTS (or DEFAULT_OPTS). java always takes the last option when duplicate. -java %INTERNAL_OPTS% %LIFTSH_OPTS% -jar "%~dp0\project\sbt-launch-0.12.0-M2.jar" %* +java %INTERNAL_OPTS% %LIFTSH_OPTS% -jar "%~dp0\project\sbt-launch-0.12.0-RC2.jar" %* diff --git a/project/build.properties b/project/build.properties index c279119e64..a1353b2d0a 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1,2 +1,2 @@ # Deprecate using build.properties, use -Dsbt.version=... in launcher arg instead -sbt.version=0.12.0-M2 +sbt.version=0.12.0-RC2 diff --git a/project/sbt-launch-0.12.0-M2.jar b/project/sbt-launch-0.12.0-M2.jar deleted file mode 100644 index 2c4673b44b58697e13520d4c7a7078c6440afd51..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1055107 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%0DCwHr@+MDQ=Wj=aChX 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

    n|adYJQbFd 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

    JBpbw)KfF_nu!bIgkc2>`3#bub(Ua_$q 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ⅅr1ORt;`Fnr(id!*Ns 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

    aO*uUDq(!q8iB2! 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 z zra*G~TVJ=0CKf>oW}bAU0i%Z$BBio)#chitF_{BDf@N_HhOV4L#&8Y#uVg|r*}6iu zn~`cwfC6BAoXsDQo;fbIu4WWG#wsyFgTAlxB9a{_E{@`a2S!yWx7W0Unmfb1H6RbS zu1|(@*YNhnZPD9|gns?GZu}9sr$D4u*7eCNal+VLK|;l=C8}#rNNwE{mv6}~jkkhk zU#@F0PK6^On8|IQgtfoaRXtGGb$>w8wRoFUe__W+f5O;to3EpSJTxih8}88w!K;j> zi<6$_?Ul6t#IS{%daT2V_4}TJ$`9W9nqyC=&fdxXFdAFmkc5n$%4;-sWIL<}P~Dob z4X4%N?5EXH6;I6+xpuwu?fRKq-6(siDyyG*Aefs000`|KIds9b1%dRv2-kalIxlwlGVzQC?mT1WW(!|?_3Ic{quZr)#v5Ax%+3#I==nF(D;2h=vHb2wi zSp)9iy&}u2ntfS4r+~IafZHa@e#VVnbwEl@=jP~ByLVPDYl1Xe6Gt*@%!4y_(8`PK zGELNAKzA+`eLSO<<|3lzUZ#CT7|qmq!hv~XoeRJHexAI-QxcU)tzSJw76TexeVFlS z213{E*yg=VSHh{g1wdwyH!Tc5UuxRgez5Uz>;QUEjEP-W)+}$?U^Q5f+@4_~yZnnB z3JvkEdFM$t(aPw0zA-bRQ~^T+^&4jme*yzYM2Hve_y z!g13G8oh&GjU9`qa(;#BOl;*j2vcHMpN-qYpKBO6l6Jyj4~N(kjS}?aI%$>4Z28(9 z&1JA0xUbWXgc6}TyKeB`E;Fv#?@d29C@JdFWoW%M8xN1ON5ovrw&)d!br*I*(6-pl zRaA+Fd(@hb_3Hkl`3$b~#1r+h=E(Udr}yOf#FX5iGiB?`IC~qyK2`{=2d7y``u1rg z-tiqtcXY^vYq%y=ISj(4rPc1T#ZZDWxvH;CJY?T$ozS}vi1c_^2yow?>nb2P{PR-v zDqs2rz~&-Mv;CPO^+i7L5wtqdZwVk8u~l}oZxQ1Dg+Z!nbRi6|H~*RcSWk{{pKy0# z=1)52MT1a_FSO_A5Vny@!v9|MZ|!R~d9+hyQd4%ZJ)*BH=~3IW>n>e` zr{{D#>+RZn{bk*~1=7NCk1IVIa2!b>cp@t?ELjfkElr-xsoJBJ>ClPGIaHYLf>D2N zK3kN{OT^F1J^Co0E{&m^#A$qh;#FqOpZM^!#tX`RQ_{`vF66^u=7Um$x2Xo*^Xp#{ zy}UcWE*ef$lSw&sK}1nkQ_>#ep=U~fnXojBR@a!s+I_?_rWO%$X-<9 z&6$fS3lst(7ei`&R=05h3f$!!+_UjxaxFUI^2um11o_qqv9U!H>??~zD6X)axa)7M;ia+F zIek)+nKO$-^i3Z2NVaDv&8n}A4Trkl*%G%@Y2EteKwiep>^i*?dxAQV8ho~2hUHPl z-K+p?DLy>@#E8m)wBsOekk=a*)^`cB=(Qhj-_~J*g{E6qe!P|1H-@-eD|Y?tY08qw zKpZ~Ta?YZ8BF~>|(EskTfU^FT4gZMKw)#e#|%K%J7Ns)W!FgI^Py%= zw{-dH8qI`*8h)pSgYRFeL6AkFdc+CKy1nYsGL55WW+*L6D2HO3KZ)+H*Z!(BYm2F8(K z18=Pe+t9nKx5QL^D<+Q8B^H0|@E5>EU3=wLo>1Q6T9@}A4Cgu}u>QlNZ2!G<|M>u1 z@Kz2v&vmRf2mADo1_<(Yj%i!xEAp{ow^YFk{AE~vqX&X*@!+Oen6#HYBR zPd`mTe1lYQ%=jCGuZBLt&-;e2#(pcpM3Pw#eimH)IyQPg zOZ~8Br*hbaK{X(G8h>G^89)$6duI65jH%UF*JH`yTcrhjhUuC$UJv*w1LC8IZQX?y zrJPJDTf4IVTQgYGH?nguGt2AWX!;PecbU$rxw_CI_3VS$D`J0Io-br`YGvi7I;?9r zo?F{-vnJgGv__!Y5#?nQh!8P}HRf<-B?m!N0mcwA&Z!N7fxZ~|?|Hp$$?o6YJZx~bRaf*1Y zt9#84*(Y5SRQ0V96~!X7(I>Ao=F!y`WcQ@vx{0!az#hdb^V}ok_}bd6_4G$mFV@YF z*wwvIE2E`ZU~a4D)K?osv-=gg(Dui#{mjxGieK-Y7Tq5$!2$}cDRb@zP1!9iiOn3k zYgRUV+PaO+Ru7ox#Z?SMl$$xR^^w?QkAV^q@rz^nT4Kek&aXuD4VcUIi=VV~=?kvl zFPg*`Re2myvrg@LKFM}7c!16Y{LKctm9{UtqOM#2%kLElh(of1;CFP0HT3_wApbWc zO8ei!w$qiHu^eaj0zeG??($7Uw#W`W90a`!XQ!lC9FJgIiYJJP%32*4e4Ck_PMMue zNSWn~LW4sHV$^{|L`5=oAT|kqo19BDmX&NUwoGLDlRWwR^3UX7%fzKBk?*gZm-pYI#a2&DqU~2+*qYsR3Mkpw4Vf=d_e*UT8=eDqI znk92z;l*H5j-uhE%s1fE+pgf$xsU z;D6!!-<>sH$P!Ce&NecVgDLP6I1_ghbk(9`2^3+lz7`G1*Ezze9(3($!De&PW-#Ag z;H?2qx$4CdA8|)G904&QfPI3wWY^)TGG_RS4g%b{!P!WLi`Os;r@(|>!C)rlS2ale zw5L5WYZm>5t=$gi?Z}RbK@YcLHaYO48%K7R z3ft8?==(_!c}GSxpDPDk?&5+Tgaq7P*+Ibkl4~=}&-n$Fw+nQ>P`tBKrBl|^SRSr)K zz0uTb!qcD`50ihwndCgljx&^|?E?K2{CwQ>P+CJP%b%2id{dAQ^!33|2{bqpbb z=$I1irgEmN9qKCjiP0eIdq9!vEPKJAd4ic8KC)y0<*=D2jSl5q()-WaK;eGnpJT_& zv3NAZUH7|H0^}02@Y5KspH;MN&Ve_ZGe=6w2N_=di~_yN0PLyGi<_qvpk5X`#z(d` zj&$}IR~JlEk3+-kcRooIjXj+`vy8eN z_XRY>5h8!B7;#Kp{oF(S#xwoa0lJPgj}Y>to!{4K=tyu?30mC0$X2}jWb(ZmoBMwu zoDb?Bu^2CgY^Fy^?$hj-2s(W-JuOv#UCth4jpai0U}9R$RwfzNl~LV8cB$c~?!8D^ z9Z5~0x|Rz#`b@;a5-?kKo)}A8n$@a6N&b27VI%~LPAXABL+((TO4k5S2KBJ9><+zG zn;@AHdk@rDXJZYuf+!Q-&t9q&{$}A;OPuy9qWaTZMR5OT!KlK33bc#5E-iI1^sRlS z?xb;CmR#qk67?l_yQ#H|88LT#IRwYUbe6orylKJKf<7v}La((=!=%c_&RGT7cR=hgq<_;0`D+?>ru z>5!5xZD)~K3d^aCi&TE!q2RNsiW{^QVgC4rS`ui|i!1*n%JsNVd~}|_-03YPfu4Uw z6Mpr%($kJ+@{dP##_5zV#laFWj>AvvSV_6SDbPu@zZCcXBkdi-E77(t;Yw1mZQHhO z+qUhbV%xS+vAtv4b}FbCRjH)+J?FmN{d~{Ur{CM(kG=Q*y~dhzt}*8v14F@4!^xpV zkjv*#Sk+j+%i=gHmW$Mt#fun z)fMU%c2m4lw%#dVm+Q%fSmrDs#F7xmo%{c9+<7n5SBY_GpjzdI$Xpxz`%x%p3Pc~|uMy6?9nrq@mOxv3C?v9;Ok_xFZ3jAg>h zLzCpPCd#d3mpoX_VkH*O#!9wkLZvA^=$uLeGYfLqP{}wL&BbQ$TPiK+^6uH%Tf#PT zLg?R~+h)J***&O=)Hb24yHfn*e-__7l8&RGDx+JoJl3zLYGg@&NU@lQTS(8$dlL|v zG7}rpv}oH5CMi=gR*vF4uT?Gkt^spX{L}ccTIC*JzNr3uB=dC93FFDY*!p6!sKG9I zkz~e`B;=ztb>u!{q}cogoqn0uTBF zThEqP-jE~vI^w{Hz>q<6CVW9x|EJf8O`X{ERiVCK|K31<%Z9Y#V-nx`uUtR|y-o#P zZmbTyrhN66I?3;a&6Z7KQWl&0VLi>zCDN=6G&WO)YNa5YX%TXZhrp>X%NmD&?^*NK8z>#X(wm;6Ii6s073TZ! zC0$B82I6Lq`EdYwYj*n}XZQ??H@JXVO2)c_hWA*5eDU)>0Mxx*OkZO-OdeRz4_n&b zt7L0}zj))0HfW&aCnP`#xb09Vb>BJUbd8s5%xWZ`0@b*BEc$`rT#p0!58kwQ+_ zB0|vA7Snnl45pfpizlf7`9lHeLu>9wXeB$)-mOE}sav%05ZXtp@2Z%&HpQ%czZ`U9 zDU$-+0fHd0$3DeOpT)CxI4{N%n)NbOZ#_0ahINbDSzW=Ss&^&VUzq;P+Fq;23~qM|{AA^|l&$l6@z}>%E>o}Ic(~tXLrIc;N`s4RLi^CeF~ekkVx-Wl zbMVPaYWvcbY{6O_Cd3d}&!ei;W|P-5xayMK@bySS4M9Y8N&m&9M@e0$SIUY*0-5wY zuL7YRQsv)H1!%#&D4xRg@v9q6E`7CVm-Y#2-1PuzXKPK_xdMaGZ5s3SrPn$(=7NTP zE6!bYsva*m_(hBD=snq?Zn6Z=gAft2*_2A&TZ5oYTIGzR-J!z+TReb9mVd(W&rYE4 z8?d0i^M~sfeLk-$?q6LvvUO7(sbM^xQ_=4{BFo^LeUIgm@~oh27LS5MucWvnh2QON zaQbN!ctj5*)8~KqLg}0^zxp8#IioH4R~!^hF!F};G^tt8rI~5_z^(L+b~heFY)|9d z)K?EOM0C@THcwoSpSN`3r_al^hoo++r!TW`Xl#zka3p6>$?v4^ez{z6w+_(<*2ycb z7Eift`6*9qx^vc-+}euGPgZOf_~L0tZIka-rBpVsuDqQo7}<4Gc`rpa4auS`Oy~RB z@NgQ9tYD^E0^#Uyjs4MT7KZ`4Wm5`4X&Q2h#)jYwZ6dCxruwI*0Y41oFPs^63$`e; z*q3`MmDQbq+|pvZ}M9fH9V7k1%xd*O=xcAfIBjv^%CF9 zp8(la21S-);vKS9FL%vy%kNIjlDHrDK#nH zkN)z!*auS~X$HOo*nrb8|9@Ws|0Q?y_oz&Mt2zJM#Yuzfyd88!zLx>Omw7UVwn3nuwG2uPd7}i1Ex9sdl!UY%5p)6>hitJl z%1rXDOtCmhU9we*ST`9D*#dPGgS4Bhi7Yu?Fs8;33RF@@@~}GD5KHQyJd4@@tkkes z!w?7xG`8KX2{y(+2b}soX;9*bQ)esyl!Y$rENNZw@QutsY)Dar6q$@!*d$pjRV-I5 zSu9#CdsrAnGlfg;5C?^k_)Z0dk@U_5J?%eU8^8(4WDM)VE~YqK6>hA0UC6`_`{ z@dE0gVEvSlHC}T_$WS;?(#Xs@@`it+70ok&3)Dno1(Zd?-?EF_g%(g31DgPmbxs*1K2}cyGS4oD5KZm_f&_G z-qnyvfwSAOL!M}jkRP>?y|-W>O{_>f=Z|AUjg{ZPXQimJ6?l5<B)m;NuZe~8KeD0UrwPAiH_&kEJhD+#jmd5GloaZ7`w7vo;S$KgH_a~qCkL17Q| z-E4jo1R3PZUV5-sQe?}39W?OWowC?~M3au#%Xwd(tb7$VHjfv)v3ons)X{WI(#f>q ztWH^Z1sr9^GdV0$$VFv;J=Xp{R+K?HPCM|fNRaemnr;GgjjWK;zDh;3^P#U2G%dVDzAyDpMrG}!jIe2Wr zNA)E;cn1QEXIBqz=w}P;n{X$1w-$6qFTFm0!T2dM;rr+e(*=W*>%6|L_A3-fXKKVY(wz|J7j?N~#!`g;3W_M25nHc+7 zjgccz?iIr?`da^Nw3s)A+y9iO6XgOtHtohv1Qumz30EU!!r1Q}qSY)Y11AHN8_|l7 z>)ATVOG_SIP^Uv~rYQT=k$Lhx#h)IK*PYFY1^~|2R9AgmX_{F7`MCI%uSHi|<%Y4@ z_6jJnqH2}@<{tAlxQGjD1&RR|R-AojwkH`6Re4&uHhb?Hj>^1vYqdu%`bPoeq!L*v z>K@cT^NTlKy22|vU&p*i84eWI1G-Ys&)ZvLkcY=r>^npRo)l-djqeh_5ldH?hW=#j z8A{>DlrJl>Q4icCj3EAuxlr`-6UW~3SsW}(cjAci<<-#vJL>%9(aJuL@yK!X2o{Bv zoh2F9+&YpV6A0T?64ok}vcZv4w`TYBA;lMVdU{#c%#USfV?*c&=&(7a>uQ~U6%x$C zS^!|-(!{z*n64lgze%>RR}!9ZNgn~E;C#!7%AXo!l%Yn4f{5v*M1vbcaS&7;;RwHe zNV^Sf<65j1+N%opJ7hXH${(8UXxU7amP0sES2EX2OJT@7p-wnYiPuY;K;W*{2PO@AoS5uck6{#4 z0`_?2dA_2M2>v@KAJCK`RG>Bi{XrC)@2{4^Y=nyEVG@5GJwOWY4?#XYVGmRtyoq$O zmB`hE7+_0S+EUfkwxN!CKDWm4`qsBqgOTB#hd(LL@_x_SpfYdPj3>UTthiZKZjcn1PBF`Spw*I;`e~LF+rDlge?c0b5=G&1`1B^!2wd z;3qAUM$+N+3sN;_Y!o6)Zcr)QSpf2vnE zn0KRdIMb>E)T`ve9z1-ua(eNDF1peS+jq9ARM_Q4X17i9cJEd#T02JyY86eS%2ULjXyn@gUBD9SX0q3yUyYm|tAy z46jUJy^$e2upJF~AOPM2Xw)8QgZc41#M|OmGh6)n?949h1vR-H zf+E(-cE&dh)xKDKl%IAk3<%~Rk(tc<7SisvwLlM0oprh3c~o+x6#P&Z8eK)=B{cM zuXfl1ITMzoeKIL?7WW&G-xrSo3*(+&Y3MvN&LmzSVs9qx%-7?iL^tCWMU@U{E07Z2 z=+MfT4Z-CdrY*?}M$3(^(UwmbF5;xxz6uy8KjmbtR5Gquuj@i&Tp$ZRG@ee6=^UBf zoOVH@HW1!S--waQlC4>Hh1g6k(H(o00mBDj z_HpkQdw0sRiu;w29>0hFa^lL!3o@6feVD&v0nN_QgFP0Gvx|B4jJ7Ocpb65r5s@1I zRWl^8wZiC+bJGErp>@FM=)E2;`x)a?#g>y?yYiO#i~9tE?`U{#BXJIgS(7oR{dhTi zmfgBME?0J|vM9&)_U2BuHg5+OS+9oY!KN`iFEp-GPFI2;lzJafjVDq2Y3F?Pr64SK zbZ1@4_m-@7>J0OijL+(x^9Z4kMeoRl3uH?F26f+wJ%8Xk!W49+dtPwbTSeBlKvm-K zz(1J(THA#E@%tDbD6tw$^G|P%|Ld-^+uzHYaY-{WqRdEPb1khQhl+pfYYGb)&<2&< z&kT@ZV4lDinNpXxeV;ho4@x?3LHwwk>lwgK7?N2J{4B$X_XG%la;0?!;?V_ZsGkj7FO;%=RDBSoV ziCEg~)j@LXU=f^P%lIFz$uDl(+GdB!87 z@^dJV1UfQ9%%Am65_`@eM`(!R+QDUZI?J{1;RLuH{rdw|2tPh(3*rWg3bzug5~mVd zi_CD=F_m^oEO;1y_-kOpnh=I_+o&?J^U`Gme@!#mZgFikXr3gozIo&+MXLIb z#J=xw4!x!Ua}R?df)~Z3!$5-A{E0oeM++@SxR^Zo6CZilV-HpALY@W|)J++glrb_Y zgI$-54Yok>qTMrMCw-tHQ!INK##YiA#-IZppn(UC4ls!XR|Q5d2&1=<$lS8R`kgWe zj(0dnL!)Y+7a*M$Qga$F*_2d% z0QB&^@f8K7cbH398MimQ2$LYQe74)DV?2vrP%qR1vOeX{kY$8MzLrdZ>b4bBYHJG0 zwfV-6T;N9B#@+XX|GK*4a=ni5K+N6$j)!ii(Xj0R|AxwPuN-^`t?uL3~XRZ4C~Eu_VHcyXSG|r0n2nh^H$?!*t)} zk-l@@K+!zy4i^V!*E8}vG;;?4!mqz5c+w4$zsy3Zm|r~Tx@}z?k)x? zWet&J-7kK8_1U}Ak8$@_vVY{yQ2tF;ib{6jMVFt7gF3Ad(qVT9`+Ze~MHwLA%8mZc zy||l@oK6~QGly^Ovi02ig{@@t>)5A@4Lc1!_oI2C>L1?MH>1PO<@>HaxEMuf8QH$_ zbTrKxqJubaU8R^9(h$5Rn&$H2+My89@}h1D=utc#xT$Yxr*Ku+EsDbipcF8Z<)D#% zv^irxdiI5^hf~*bB)?0s?^oLch0hrN>kRYI2gc2STl13t7(2NCvokbyax!-? zBm1wop`x$2EQH1nxLMqB00}}@PDP~N++kM}DMmy>H9fWtNhGJ>1FpuwDOBWaP%8(f zkr64QKFtghn)ldq{%W9@r+88L*O^|-ygmJ%;{1YZ#iQli@MFNU;dDPJB!XRyDJcw> z0gFOEmHEU3FU38T>Dm~=lYhG8z!;j7e|s!A;!88!Q9!beF&2(+v+IzecV&kFg5i^b zm{~iPjPL_HAf=Esvg7bTp_-Hy^^aE#cTW^bnOFekB$opHPikb3I%GbI*QI?N`;u+8 zrY?Nysxp5Goc4%_`vZnQL;Dr5l637%{;k%1~smOGl<@wC3(1srusxk(2w#LNI`0 z7`E9-yq_#Rt)`8|dQa2XOQ*tPj@s`LLbK>}m{jT3v_`0c7l_r{@sAxW`!$!UaRc;w zc!1VNvg5dmX_hfnh7y3DtJVkd{d%RGJGPC~Ej^BM685aP+#if@Q2gAx()Vr2$1TX_66LA+6KQ+Oo#Epz z$vl~(3BCm&Zpi=f?#umWxG}SKb+dLbbt7Z`UwH_KB|Go`f>K_}Ur-9)yTO=RJsc`N zrwKL*GdIS@Fl7^wLmNUT#DR+1o=G>xX34AHN&or@9XQaNHvHYo_;FI~Gv&^$TP*b? zoPwYAVVe6VH-FyS+wUpOFY0^^Ox8tq#*;%WVbNjOu+u}eVKxZ89X5agc3cVwyT`)B zOkrH^M_#PK#E{<|Cq{T{_;UNn>%n=12E9iwlhAvntf3`|w5wdj5h3t5(xD<+lEQ$$SY{JwZVMqgvMm!!$E-1H z%7gv6AiL0_5wcbhi&BtM5ZlgkuW?uz7|1Gyhx4%Jm;T&43Y?F6J|e1R-QC~KH@e?X zWLMM6=$gRcvx`oAbG4Jcy4MnC1PzpC3*ANDbG_ucKFfcVTxL`phgwzPUb(Dzml!Im~^7m zZax9v*{8v{>~^E~xd>g_E96YUv+knNCTV9DGfUO*cOy<7<8nY`JSbk@P(fQ1J+kn& zdE&pipggTvJP(x+DtXHTwhX38zQN;cypWR^P?3UAgAAD z!g$pZ+E3WYTq}>J>*!VVGcr3?FE*$H7!FIl+iL_=*4wVYlWmF{AXP^4#D~(<*kA20RZU?@*EO?-50X-;!(IFA=qtHBRTJXqMJYy_7P>UcC4RkiGAYn>{Ta zViD-sm}YN3ZHLUWGs>{lz_otf<4zp5i3`z!5ed41Z6H6VXfWP(qUueLakMh;t1J+h zSyf}B+$n;|cPEKq&}xxQ(Cm~mzpgt^8<>K<75!kY6Hb1$sM7unzT(s~e+_fv7MNq| z;R;*b{$cv->nOBK0V&!xaW*l+bB-0rlxPFYh$509RROUl%<7fsr#4NXc-!(=+EL(Z zwwtWcpTFdZ67Q?c!GKs>{KqKye-YLE|5oM})^={@F6L%x#z1Ad|CM8H|CM8#FF3ll zmRq+rf9-s4nJHiF5TIHlkt$LYPSS@Fm@QY$;>n_$C{+TQyJ;v3`#noWaZdbd8R7tY zQ8>+ay)i!h_;~n3`HjF2_mYFd&TiAm-fm;Pt#L^V|C(gmVQ{Dp!)|)6C>$J;R?Dp+ znjF%$E4|0TYjn=Lu;T-%2R}b*{EAh};3e~*6Vn1&laFiC-OQ}m_xH}7 zbSf43WV6tyv%{-WRH38FplNact;3g` zkhZ8Z5Y&R0)*7jzZ%IU#s(+a5QVi(ZQGj|6d?4fhu)vvZj6gr!&hK3 zPdCT4zJ8C$lJ++H*tu2pDXJY3EQw$yQ#{hDkTz|xX|Zat60;r2Rc@OfX2GcP$oM#A z4mmW-UW-|2)Ql|D-)-_!RZg^6`EA>lx?r0Ruh&OsUfU2o|3IEHGKv8M^^B9segz)VEwv zC6V|^$>JDf?OdTZkQlHn*`1$?|_Zu^v^E6P5wPQ~C&}-lh zeQXU6k(<*Ed~-*wF#=j ziXDFL2|#Nb&t%^!57d~Mpp%yDO3%tdn|&%bXFT1}No%MGZqR7md=%SA&niBXDRS~v z5rysZ8*t)T$zx9(FB3A-?3ytXV7y2^8YCJ67c|=9-E;HXYZkr(J?E`*Snr|DmuWBh z27bxQ{h@H)c&CrS>~!G@0^RL(ow{JpldqnZXXR-&V(4c#)IvXYU(X^&$-{Usu~}?( zPOBB4YppT0n5PMi^$ebbb1pNUcr&v!u70`e8Zz)J{wwzD<5gUDSngmV)oOM<^+P&I zajR+W9ez}2Lhi!MsGpy3K|`;zjMrME|%DG<;I=sCD=jmRp$!8C@(v~N@6t~(BpP~ek&%p`I@~* zs@ukqq<*!B5cqKR327McaKr&w+#WAFg|!U8l|m9e=?g~;7#uXW`mnUa=NSxCaUUX^ zpt9a~Om;)|pz+S9skQO;dSy?>H7aArd*$bEivD;!r-4|seZ4go`bHgS+xPlcJec>P zim3#~f(OWdDpWZBvv^?tzr_MIUDYKeqz{Fld=B_S{SuN$WLXff-FXaUp&xbMCe6$H z@Q0L3tQVzGCI4Xey}|ma;o*r{^zd;XCAe%aVYu7nB}eS!@^}BfzP?H*C=dX{8etiO z8Fx~Wo6~@?OXFq$PP|CVxlDZ|MYx_@ zWQT#!TfHVJ7iL_Rvr89lm=#+sbt?El?ZcKUd&_uHUKPg{^gOkVRSOD?S|%=%W_h^d znkOGsLCJLVQt*1|quT78hATfeMuwI}ODzEFnXSWUyhVb;Qs(3~OnQ>i6Nv(>Ag{kq zEt46$1zZruF3S_G%RrS&)qQZDc+&g@JnFiCQWS7Ik5`69AloRDpDTl&bMRVFJtJQ4 zyXMJXR#*e&2m*IjZsNFwUOZ^Xicah3kGTZn^L|FQ zpV7DFm3;LIRo30bB8`vB%OT^idC4slnl#2lSGO;sWd;#L&enMp)-TGR)^8)r(Ix1( zstdd8dxia@p~!o~$l+^JePj8YSgak%STbi$aJo_$aUFx44T4)q z_Itq^Y@2cM@OWNe`ubQ=30zW2Kh=ynfZ-Z*cFf4Xs;|JjXmGX@Hx{}-#JG$jK7 zmT9JhRm}FStCZ1H$U2y#_4L6|`sm^`Hp>a#$w*Vc{~|< zhNXGT33-l5ql98M=wTs?s1#cKxet@dTP}^cP?A`9W7<7qGL(rlzX|Z?Glo$ori>$& z$I}$OdQ$%ao3&fXgJ-Pg`z*2*iSQxb7fOnQAna0~awm}p#-%uH?52hwiQ@ZfrY=)l^f*xvRBq=-$o0@9R_RhdI8xVqWVR4Aa#EcRs2S#H~-Rc zhWtmO>i^|z{=IV?r=o8Qq__D~5*(T~+BPNP+MWUR*AyV_3z9KJCd5e9$tveZX1tSZ z&CSNFy32w4{f+ZFYD#-w15>>$QE!EdcEWr;Uh-Yfrmz0ysqoqGYq>kli~vDD_xnO- zXeKjT&3C~8xiKj9-I;@W@Zx$84d2WW;g|VqkK!Zf>3pD^LX1ZhSx8mFede$5A5aNs z>i0q!A;w5p60oC%AHEs0o^$vfMbZ*TM5!z%M(%+49T_2b=QrizH2N;sq7c_A_%`@hwVftZgSnw7Ayr zM@o?^^O-5r>tc?ChznXrba8btu0E2$gTy!*V0JHi?AOfS(pIw^*fHi$V$zqq#^`juz&8dRs2e1hO z2{rh3eK-Zg%k4kn<^Q6F{_m&Pzu)w!!2X9aP#iV_qm3S1>^6Gbey~k1U8Zy6M@Se> z3WiA~MNej~17b(VPTdp+pdH2Esv(MS0MR_RSs$uR9Ov11c6MWL`rY#R`2FMI0rMM~ z0gV*3mU=_Ek^W$Kusk>;S}6c(KLE?$3rQGJg5)?E`q1EOFIJpTk^>a!A0scSnGz}s zX5yJrHdbXaq!bE*5m&x=k`U+c1uee7VZ{y$mrJEBdVJ^|dDda;-&$uBYi?U?)^iQ? z*GgJL%x?5kb!+SZx--0Pk*i;y>zp&K=eMW_cn`_c(Zwq4owQUgZK|cpRm*zNHoibx zzS=^MLk23vXeTJx;X^{KQ

    w}sB^lM#jra;?Z#${ke0#rEq{(+EHNnHDN(kLp#N zQVaXbb==1~7^Rw`SO=W+gFchx+nmv~l0`A!&#O3FFtR=wJe zDl~1_PTk9!eHw9-a}BL8MC(E`|HE^eu~e)3D~J6H{z^l)o&pitX;JoQy^NpEEZy7d=n= z2pYw`Z5F`kOyVT{7Eh)25A_3^WCTagh#=zC>s#!WoP|>t?pn2Wl z^W@;3u;V&WN8->TlVFe2aoi$x0U59$o?^><)KQdCKchw1iUtUg-|pLQr{um8ZHnQ~ zbVgXTUOr(K@;bymm1(O~?cvS^0f(4~0g|O@+axSe)&+V+HSAKCT8DT5IQXwGs1NRN zS7X@P$3O%-P%nB5W?S5D6^eyRf43d;)x zuR*ukxU)=(28b9Rp#K8AeeHT-4iM}*|Cqe!{#(gQ)X~B9-wjn2c_8S~_#J;Sx3Fq$ zfx^Im($6c@tgsJ*Q`JbA%1F7rY{s)3yKlCrl8}8M2ZF&$(Me$1kU1YDkmRo^Xtj`W z#rWQAbH7Y?zV!BM?gf7F3Lm3bm8;EJXHu3Tist1hbsr}~4$3o2Up%M>53Raa`HC$^S?ZI;HTvfYuM%4#ZYi-TdPK*m-JcJSjtzXdM}xB_)k(r$kS4mTTbgeW}^mQ2ue z!&~BLWk-k_H=#i=qLJi4`|hq)=L#m%R8( zKif(QD7*bmbwuAFf}%IwnZSJ};L;x`(Dy}eUkXqk&JX_^2jO0+=+a1QDM@%&2w4Iq z2LhpS#wZ=qkB_Rmh;R~{#jN%RKA`Lb?v1tGpP}F?=R>K(x%o$|{*7x+q`-&)l|3U= za^{P_@ynsqZQ+EHb}>-pFDiF(Hpe<9xs@%i`@R&@khQYzR+$M6Pchm7u4~lxZUKlH_oc5vKWYPN^ZrA=0H4U&f1-&+B0ClkjDHGKaGiKl|Vu8`_)d-Yq z;}E`a5BiCeUViNn@K;ABmpx*D2bl2K{xRV({I`zgFA3~_>tDwu%R4UpOTS+}IIkl- zy#-vSFBe5D8w}$NB2)}O%_dSLb!~rSw^g@F=&}m+ftnF5N=7r7M;MmrOsdSDHjwOT zVM*A}dbDBK-~S0r$BLI;N@J*)33_}U$Q@Sf`}^q;G!8zMcP?OklFWN!I2vBozd9ni zL2dJ{2dm!6L`hSQoVWzklXj3RJ=z4*FN+3z&QO?}Iu+tQUt4x#Vij8a$@d>m6|UuZ zltHJ!@+x~t2x{I#@NlEuAeW?YuqCRPQ$;GID~~!F{3eTKMy~^MHyG_FZ~Aa=ZV-Rj zgE*#Qc$e?xLurZVI*`VH(m5}gU;G)+I8;Iz9+KCoE^w+5%YS@VKH)vlOM*+HjL||68E=yBHF2x3)7gcllopIu~QZ! zq-DXF(Z)Wr7?$EyU;GUvtkLm&Qlkq+deLIkdMrn&lH?>7V+#vp6pJD6t|Cxa^A5{I zn1kBM`dCQ-iA_ z{D%o6I4pZ?@k}|@3J2=cv&DqX2I;uEl{Prf<}zMD$xzArvOR{ z9;OSHQo!t8bq=PwlWH|JuXG8v{H2Nc5gO{fC^n-rB{asi94h`(`@mam^qwLq-(*M3 z>bpWix^<&?3}(4 zM9H5T`6K$Hyk5oQT_p-H1z|ZHUAeMaFw?QXGL2vGFx}s&df$T-vZjX~5&xC%(`;%9 zEr3TG|Bv^b{Xak2zk_j_`j*C$5ZVq{i|A5h^jdYL)qL=Bq-T{T!;^*(_`;7RrE(nk zrFyo7u)t!$fqQv<8X|@afgg}>mHE0)?I_em7PC3FD;~M)qW*;vd&x_}2YBlnKwdo`ziWUwL?~h}0zEEiVJ-ma32Z6>_ z3&6u}Cu)fYSH+c`s8-G!Z-J*$&KVEY<@R_~$wHaRU2+6Edx+*9nNija}dc=fz3hL-JCFtYP>n*Z4@uhpX|*zGcUfB*3%G{#kj#S zqfI{65-q-ms@ldj#;+Moa)1ziU{hJEeh2X`BHR^fr{JLo@k{`iF#D5Va|`#H&X1pH z2MwDG2UW9xy$5ZQ5PBDO?wS150IkWF!EndVgzSP%<06R|7;bfHG474g#z=8uZR9++(8FhfmCj)pr}M!-YZ_Z)Y7XEmZV8jRp0WJ*O* zWt6eb0LftAa5QOM4C*c;SVyr`>%$_nmf1QPA^Gtpr(PKPj;1S=5L(*4Yv!x(sGiz9 zQ$GYwkF7uBd6v}`yIRAI*0(AxafGV7-Ri31uy}AWQV+b2igAaixxx3C)2l5WnaS3E z)nQGK&R6UH(#2-@V>wJ1w(`B~Yd!Jz>fhaFy|nq{*fJl{m3(kI>*HI+BKjt^exXs8 zto^8)Z9_jr>`$SmEUcbn)Y;u+htJtG6;T}yapZUwmcH1Q*nV5OddYuTXG^e;nRjtr z?}_ByPG8}iib)gIZVS8R?DwSBH@ z_ZoW+>cQd%G3Y@x2P&9cO|NnV8~ef8=ccdp(@Lmb!TZ_Ek1YpIf>t07Ea43pnP8Hsp|i*OhM3+T3hv9T3pp2cox6 zH4yME6J-D;fkOyamSSvHs|2sA0TKa!2zx=3cU zJZut+fo(Hh7wxAGjrJZ=3o1YA*IqfAyX90><52}!q|f#=^NnQ(S;JM(ny-ld3SNOt~MqBfA{rA zz}WLqf$=a0`1wCiL2~^E@ley+%}V00X6wJ}(|->@LcqXYgBI`Of7Nbn2}4hfS$!=O zGEt#{7E5iv@SqB5=FPcg2r3w2yv-5|wkqz+%6#?oQ=4NTTHrXG;&z?Qb)2>D`!2W} z_?0Or0>Tc=6J8Fh!*ahbh#G7O!h&ZZ=%KMA7XlE*rsKpOr}1c>C=v)^-+>h)i`{Y* z9c4S7-N1wtlS6UeVQd1oCLe1$KTLL$nHe_&iDhEd?uHiqt%#9VXY*KhTVuOsV~#dw z2s#F?Zesy6O3$<-&py@)=7%Fj2gVuI3VwY@FOs(cRV$}*tYLwGGp`PHlHNKk$R3Jt zo@Of5#9Hw>8rRZSDt%5E)68Bxh~Vy-q-!954j6;0s_3G@@{w6YJsGcyG$My!edO53 zlK3FS7xmGW3?juadyn-#CQ$qaXTSZjf)LVJKXGlR1cDo^IYE5+P*1oB6dO99Oj426 z@o6c1vzL5zmI&9zU$(nJ)meA39eY++OEK7a_Ey3Sx>ne9HOZ5t(ObO9y$#bu&u?zl zg2j&h40q_B47jKk*N;ohM0KZ345$oQ5uz|Oqp=?1x!(P?)9b!`+NhIu0MV*QG<9#? z5h$N*Txc9WNA+s90Vitt2QvJCRdbNZ+d@usuq823c4Qvk8@;!;xWKCJ>q}$-z8E*s zi;f8bIi-nCv9Iy~lkx+G_LNzL6sD`=f;Iu~Fn73q>GSzAph0Ta?EqJhNVaEw;S+T zMS@aj%poz74W_+; zSEWxTTIB3iKt@hdVM*{7#wv$?NqZcXrt@CM5%bwK?O3lQfadit0>vlu1ai@LjSvfB zENqfN>4?@q{%AsqspE($E({L>V)+y8`$IJxx_ z7*(sq{lV&bJ-kymWj+A8HKT!LJqB-?sz)7Zmby`FSHJjuv3FAG50O|(QeF1(VyfLy z|30<1YSZG3l6I{UrI{ymTEoj+sZaJqpf8eVn9oh#mace4dsyeRfKZNzA`}X(I3#G z^hplA$avL+wo7|;hyF~t{e!Yge2|B-OMU=J)}uS*OV*<~gec=x5c(qf`Yn_&`BoTO zknTW|?7cFSFy;0Lg@7?+YCpuh0p^A_gvVHI@71A0|m(1Z!g?N z&(QRM_Cj*Tf2nBUk^ROJMwj-4qKnY@!H zuqhN-u}xjlCk&b2@KRjmC^G$mCcr5I0mm_NAL$dWav%8HY!k?-bjea zRF&oo>0`bf6=S{){(aI5{#|2~ze9P{_U*_Zm-2{+A@z=@Wr{0w%6vOQp2Z*`W6}$( zKG9jQzZV(TOid}>Ig|eONCg}0wkx*el>=M!{qD=V&}fKPRID*Z%z$BLkMvi57Mofj zOK5z?j60tYV?=D5Z02tmS%p#lAUqLAc<=E8IH*2om0n(0Rkw_Xv0R zq9Ok3VaXXq>U;n`JmnFD@PJO};bxaXsI1N9F9fOxbVIEL2|wO&#LAE1F1hC$F}l(? zBwvumqX6>SR}V2%VtvxpCbk#~plDa4f3J=Ze>H?hO$1b0?0E zxxb;5YFZ}upgGC6w@G@RR?2cb4O8d*K1Mes`Y&yw53IGqUvZV$*)i^aw`H4V5Jb^r zW9O)o_)(!qQ)p4YA)RS`pH5)G&q5x~)|mSuGpt6QJhg*nJ_?@@7;HoZ$0? z??N2Z*qXvtmkyySv&ky(#pf;}Dau*?B5WI~6hxZVy208A&rZ@ePL+U^Nt(5a27$4@ zVUTI|j8a>WSJ8V^{V=|Hw6kS&KS#yAIu33Vb~&*?#M4ab6dN>q>1(l zXXFZ^s%0*xwXq5t1P=MjCdi8zuZTXCUR#-pYK8$B>IS_bCsw{mX8>cwDGVxO0etmj z6iUWikxK5Q*KjRWXq?eSi%LHdAkU4zYiZ_Bu~n(%zwy4^=1HC1c*W1mPZF=mQ?FWD z#a+sizu>Z-3&9+3EAQd*^T;I^*0GvQG5bMuIH%7Vf>`-xn<{<6F3XWT_Bxole1u6; ze3PZbNO-DQxR7F}PEPH^bYjyFhGHvk{;g}sEbj4|4vjXuvV!R#i&iLT7lzX!h#=DQ z=Snk5zVX5uRO*TL4RwW@A%XN=-&VGwiAI`mi#iKp=g%yZ(^Ix_@2TX9|J?x$ZHKBB+VSk?PtgE zPBQY3i&)`r{+#;pX3fz?uMKOUK}&A*gL3P|{0k8&3_lBA4V@D3E1ADP8r2}#TzMG? z!qpQ3Pbl3i`Z~O?Z51buF!>Fo=3QP9GAWzy(FCi>g{C05nLTi^PAu+fo^8YsYC~gI69G6FZKF{AGU`-)q2LDhM7%(4ZUUmEfl| z_=fbrobU1HVM^C`<0=gr_*N+`eH1|)snRqjM(uut)OB38DyR^sz~N3YVM&DUZG3!&8Ki7^q-L#FN}lfgfniqEG8$GgQZ6OV^YM zDaA==R-9#;h=<`~-BBnwiamzF;oXZ2O#n1`@G2sz81#zQ+eBa~C+rQ&aMueRCCfAa zs{e%;qm|?$cW$0#96(hnD%OguxI`uEeO$_Vs${GcUb59BQ;{0pl9{#{cq-+0BJE>Y zYiK2EFJ(yDS=qBw_^oMPQ^$z7!qWl1xy}aL#+4hbD0-Cc=qrjgzRzspAa1GhlLcTP zvF$}SSh$Hs5?u0X*F}icm?PFsy_y4N@G4Dqf`MO}R0Yk+PSiZL_k$pEu-R_E;%rJC z44^urOc7DXr3GA&4{LZngag6gQ+V$?9I z5iTN~P^uA5oE&tXZESwCjO@B1Xig5pj($L{q*6)KTtxU{8k#B8xx|V_$$%~y{iKdl zX)9VVk(rqjLSYO~bHREpHd--sb)pnY9y#cQI5>z6j(g7H;1WD(QF)6t<{`APuk2n< zF$~MX6|=&>89c9+!!1u#Ac#Vru5J7asi?qRZ37|Gk#neLw65HWEA)4H1+n5-c%<-o z`P{}DqWbUcV%Y;wt(E3#l~Z*BvWJRFDx|l=mfu^9n=94KZEsAkS1~j(H~}lDmTALs zVeP~P_cU2>XplLz6H9y+&6d%x=FMw=idn1iN{XUu!&E~4u2?a6vRwZbTY$FK`Ux!% zXh-TR=+ez zThx!XNbb=cMTUe65bUGCmdljHDW7BbhWMQ2_ihz zZRp_=A$^Ec+Oq_-;(bjH(t=}s0+W|0t26%eF#|oVT2!TOL~S1@U8o$O1C6~xqZy77 zJkVs%FN5fJ%T64nqDtoRU~9t>u~M&wg=^OC%! zGrf6YvZ)+#fylqmerTj`Xo=jz>t~VI0vAf3h5bcNP4@-7FlZAgVk2YRfqYc;cK{Qf zn(gI*&M5#Mm~JzFa1sXcLiQo>!inFf$CTb*vty5{%T!|_j3}FL;0v;k}~94 zi@918Ji4YIbg=iB1;(Mt1%Bn0hsGiDMce~YhWF?`(f{QC3#!hbykif?Y9?Es#ET=I zmU%UN@TjiZy}hVsN71I1*MXD3yEVl`eaYu@*V3FQQVfPKeSt_2#heb#kPgq;jxULt z7of(J#5g&ml@Fvl_=-v^n!9dsSwV)j9bu2VtphR}^v=#)>h0oQ_O5R7vbJu4rB8-c zX?+rN`wmLuGC zqg$nQR)B|Qx>%+{8a>@xB#}(tEU3iLD#hkcN=JQO*xT7@6y4#X_uGDjg*~Gc5a%c+ zZ9gIu_?Z3>J9DPN8WTpS>o0)6odSBVjKK@;(tuPm)K1uEa>g4?5-8F>g_CHjS7@PuS2G8#`n!${;Vdi*#W4Q9WD*=K*w5DgJ(Ia#R$8pLgM2n zI^~sx!YAnI%B}#{S4``H#k_bH1zj3#4~3bc%z7QPd~fdA_>hrC2ecPhiu#4MO1DF4 z(v*vf#RiOr!l*T`_hqq}6z#@Z8`7j52)i8$wJ@u>mq#bc>y+nRH)+B0Iktplxr{6v zO$hJo(?QdYL&^a7tQwq5SAf&2erv&ljwCy1G`!6hyIBCuvpab>$0VmDN6}28tfz7~ zv#MLNlG140Vof^E)$`De`G8#*tV(jUAO}cK<(Kp#A?kNOcmSm#u9=9VV|E*1l;Q*; zp6`P*$d-!i(xqBZP3XWC4jl;AL5~&CFr9|MeoUUlDjMg2U~FbW@*4W@H213|ICeD% z<}_nfOFP~rnRH)0jI2@wV3F=#C1qk;Bj5&&xFb2_O3H1CZn&S5^%^Jw@QU-pJ= zectT0@j`Ot@ZOwhN4ko`e3G*})Rmu?8}$-4zVd-plk~f>;{G>J5~M4aZgI6IiP710 zfMYk-@j*|qyISY5n^s1rXD8v%+(@@y?d6RTRnOz1HM2^uxWt=^{i1q6o^)9Ih9%KR zZQ>OF*E)MhPo^vu(y6!>ZLj%+WHIP4;56!>gw(P78^IhNbK$oeF$c z%hwE8laBm_CbO_dS1hCZ5kk!uYACu$u~qrvrcaQve>=hYqF2JN@!)=coxr4LcIF7{ zt(;KWmP+A>unpxV2rt+V()ZkCU@yf7nj!XTfWg?Dsex5BmrBRjq?SiZLrRwj<7&h; z^}$?XjL4TBoW>qIIP5x^)i5n)RQ>zkf2xBndDF`x;D7wk{-&z`&tT#I2&a|!UdAwR zw)_7vV*e79r6}4+VF@7kl16lNXUk-1YW;o#+Jg58$d>G*2vMYj5TF4R;IW-(sy;mp zn#fi6NeTco&G}H@iiSI+%8{WqLble|Gfq5A*Is?@ufY9Ts@>hs_FBS3;H7X=tH=lI z3WGIagFRuH%HPvRy5mSOd3E_k%}atqQC&DHsGfEquvngRE(>S^#zx#^>^?FROn?O+*NoIaKsq|#ZL8RrJkR_+yUu0n(Leo&= zOuMI3=$%{g4eU8S>5k8k;>tC7QOoOd?R2Cg0 zE5o}RW{~y`?)>_<;LiVOFXw+RzpaV;|Dr=<6ecCV=}?}GR#~kvH6Ol;hYINvt)bt%lkTmTZ#D^bQY4zT{j(yEZhn21tWCGMJkf;~#EGOX|z|LAd5qryB5QYV$n zIu>F%8f?#IvQ0GRToFI;1_OmLnlqL>X7;DP#D|w)A1Ib6DOnCS#eWoQrz1z`CUUZC zKxe614lx=aLVqiz&Z3Qt7n8Ci2Cb(|3I%|80GR{wLtw z|6X5byMM_7|9ki9Uo}3n+HlygtdT<&5H;6fceOzgCWjD7hnRD=;jQ@3`u4N!hh*iFo0 z3Y6q?lSDhu1u%zcH&zkDW%SBuL2f2UIuCZ91|ZcJcF-jsKjB2#N=NjQb1w#4WqY-MZk zdIvGR2Xz`20crC6gUSBebP>+;AEDO&5p(muswHjU=^573gI2QT2Obhd4# zSp$DInaP>xU^xP&5a;7s6e>pw%rO}jKv1wq|NIbf*6ZMIl@8*gFXA4 zq~4t3tm!};GTSX((<`-B&Lg`WYeKwFZ@H<|mWWSM%j_Js*@@9O5f8GTb;um1A3mF`~&QXAf*Spz_A{8gcT znI5m3!3Gy59a* z#XV{Per_8hGVKXNa_8&m7sDVuJFgrb{VUc#nngPX9(C-yS&09wqJ{4N=F$GI9_dMs zP(xBf9_@?h=SN3VZZ7nnG&hnTB#kt$DO9zN*IYvFf|~#l4TB*CR_VHNZOO8^Dy(s- zt1(vzY^D89-guHbKkFg*2}?&%eDZD7L2|!+v;Dl?V15;hKsoFqe(h+ZxGxc4gn_7k%OFdHq*LZ!ZG31CKRW-GS zIK2qN(?V35#qpheHBnY z!>mB9wUS*r>!e-fPtmyZCTS{igJ>(?qTq?z*KHfkxMPME{jRbOo2=`m3#2Tq%Xolw z*|DM}+&i<*|pug8-fBgu{BTex@!2Fio)O0_?4otLCvfRHgF$K z11I71`So=hro%f-gWRLbCFQ7@xlI5*SFu&j@HDc6P_4TIawCnx7G=Tka=K?pN$n^g zoT7HG!0r%G8p&D669vlCz_8*9UPPq449&zj%fekSh+T)~f7T3R}@-3ccV$T4^)RKs#lMLt~~!>Vo<31GA|y zZLZ%hZHOdJ$*3CdzPRpz+ZnYiI5Nt(6bp>fZBjL}VyBQvkjDTz}OpS6Z#|OFo@?Zgy#mS{X&LN&&7scJ0L2Z zqIh4~CZcooHwp2}`mZjK2R)+lJ9As>rD3dh_=u*&JHslxS0yg|RT6wCm1@6xssz|% z7?ZLx)$E>X70zCM8@hyfzi9@Q6~m|}i}^^7FMfOlsEdz3Yv^>bYETIg*rWvy22`nR z1^DACQvI$>TcZBcM$$^Rg8FI&ysUx@;x~St7+>S&x^A3XbW=QJnZ5xmIw!XXnVz{- z3`oGLm+n)8UjqTHjyKKRw3@H>#(Ag`g0`D;Q#4Zzt8FL%=rZj(76N*&ay8a#F`TpW z;?vPN%Uwmo&NgxN)K%%v>DkOdjcU2)kE(9)qdvmiEW@0Psw%tDA!z|LscpA2k8u=G^wKnW{R!Uq%iORYi(tj8+uYHsk@{d+b|zDN*DrOnj@mhK z_l}BXQO>)|ANCWCQacwQapdPa71;^^@+AX1Me6}te8#puL^D4BOs|bB*V)%~qpJv( z6JJlwj3`pOL07|m1UK#?N_Ud<_hk!59g!bH2e`owpsNYO!d*Mb#pO#ge-JQm2@UIp zpcqM6tQ(zbxyt1M0_=6l=9#7(G(alDc_>Y#L@pt@Nes>M0QF-HO2adVpjY+jG^hV)Qc12Yd_FnOe8dm8F8gx&m20a_BTyH4JDvh08NUx=5C)Xzkz+{$%p!N|ZUe~M7e)3s!)y0qD+r^6Z(Y2J9lQCNBX$a)2^DAs{YG4W zcFG5D2NUq~5H_Pd-8O2}(+75qIIA}|dl;%Jb2^Ef{`5u{G9$|3^-m&WprH!v3cUwq z=TPT!GO8WxoHf`4l|CCIgp6<@PMW}YF(k!_FS^h^z6R&{cryCXAV;Gwz-yLx9{p(c zd>4=_BYh@e*OGA5ntY?LfDfs`nlak6sh}=o*c}%tAi^7g1FbZoPURde$lZW?@k?k> zP(v7VQNqOP$9m3Q-3XhwY>&X+X?2M;+Enn@9us!tj>z81uNW(0uD2|E2ZDN2kh+&u zcQkmInB!G%2JV)Ys>Oi2U*^4x8IYpj&0a@zTla{x!W$hcqikDP7S5!ab}qlokzUlx z4eIe3(1Ps!_~+QGG;0Ii_bvaIM*DZSTB`qeHd@%4Nn2Q%{A+ennEqB}qi4ehsC)<^ z`vh428VOATx6MqCkJleWqP_yBwry)^j^xb*$LkMc^hJgR=T4V#O`Pz)pT6|b=akfrPwU9jioJS4Os+~(Nv7PV6f5u>1JOt6DB8GM|Hn=aN%%Y{EbD4A4)>4SEcSOnmMUECZ#jv*R6N}U?+^I`LVVEen^k?R_f zg*WOnvd^1z-7a>CnkiWe^e>-#vT_F{?by zJ-7#Pb{?T*ZCqzyfbAbM$%|A#8s7Izvikk{KcflX26sQ4j0~&|=#1>Ftxb%aE$nRR zENpCCoDB@EP3XQa72p56oc+HRFM2c~)v?A8$A; zu1FbdHmDb%lEtY!+>Y4Ka&*sp$Sg8TWiD84GFv6G*x4->A8Z%DBj(?E7pW{ZJgR`> zaSaFb-tX>aEsy=|w!Q9;r>Rf(#%}x&_~o%H1~0k|L)5AtBB8qrm#JFaMY7baA0zYC zn!QBEsW>YYLd;&|LswtqV-I-U?J)#0cVBr~JSo0EML_5t3ozv#2Exb?u!-7(cO^jN zNZYe_DL~{%+5`EC&`BVWgdv;*$p_E}*af0@B?GG<_CX+lg778m6Z?tlA*=(HK-7@3 zN!vqrEkNYR+(`$TAaZ4Hu>&t4a;0v$1JMw=<99s+(U7`>cTGU($lO5(s33JkZZ+{N z?QLKw<0}P77C`E11JH7f>|@WE+hQ(EncIc~Uw*sUgI`}`@pyo$MM+ajn`v|-RIF}k=o@IdHfqIS z8dZKKT(${mX%SlYH(4?in%Hk`&&XO zrtWZ*sq%Ak&OwQ*Bn#C7FIuOhD<2lipwMR5yg``I2C2e^-c-SSCCwCPF{0T4HPPl= zQ}9kx@}8EZMOku$0nKkE0oy~46E*G>xuK=RJYyVGQZf6L^sz#@V|W0d8S_<9G0#_< zp}FR2o?99){h1+ZqMBpbzNER$ylabpbGG}YO^yFYLW?9Pmg~_bNtkPacvmpFvhV8_;ZG^cvBkI;;5k{z#bTz zhLvGUEBTv4HzCfbvmmR%{`Z%jzqi=n?pDEHC%h0<1 z`?PVO8^z5V@)>YvLRGe`4UvIC&~q#=+eO`8mk9w(0g!twfiVgXxqPCC-}o@B(%mMn43C3 z+a-4elP;tJ8Sp+&oE&vddzg^>ob-8DkA*>IMioQ{2v21Rj=*AnWV`+X*}3#I-C)lV@Lk#of=9Y*1(CedWtVaRu)OR8_&mWiO67XanBY_ zI>$xBRVzBkWXXlBGSglqq_kc#rbUWGZ+aq8Z2e;;F_qa^@IIP+BgvWC!wf-f^7Tbr zUvAG)=5jQ6qrj0b>b-PmS{r>anqEU)JS0?{y~mVa87VJmm_SCD+sFB1jGTvKN1*+j!I6|5iF z43(ZQE<3JhzM+qBrgmkd@^#-PN7s8G>}Esw}%R!Z{YDRsy#t9mZ|x{8xN zR1s>>(2pCd{^~XWqAGQJ=^D!1{>`*ek+?OJQX;ZW=2u_T)W*eK+34JmQ>W-DN6%gf zUiRc7EVNaiE2BbdK~^@SYDPwewuy&%Wxcw=H5l@#qa~!}MTkSt!lUBn8>X@$(aBbc zz9!*vr}VDUGQ(wZJ@2sQB^g!rxL2Oo%V`>CX8Q?I2z4Z1blw*I6#%?T9~&vUS?&2% z%F9xzTE2qC9%c~G#sAm#wQEqatP3D^wxg`^2Q#?>j|J5{m;W+X9Qa{Iy#ni93 z!fwI9JJ_#JVsgc})?aFMSQ2p$SHfz~+M^u~~_ z->(mb)qob;8c=*^Kc`H^JS^wB%O#vWC!3pfsQP0WL4{pw26Vx39r6KmvrDKN+N5!< z>AnOnk791C&J(b_*;zB%#Sz8DKArO-ey7PSt&b-5)THox)T0g1#<+Ba?ZP3o^-JO1 zflFv>OrgsGNhv=T!nF;0bErlHc&x@as6Mu-00_?v$%m%QjfZ!9eQweL?Ib75Qiw^c z=2+)zO=ym5#qNNJHPl$mSw$i2#~)opaP~{v9~|5%omoY2dqUZIVH~zPU&0zGDwDKz z>SMXwJldq2^g_#e6VrOJd`_*bjz_gH&y6J3uwuO7ZS-Jxht_LxTIlD#v7G+jKkH}g zMmT(ad2(B`Y<`wmQ(FP8trTx$AHLg2g*@L3N?FMxkjsx6%Hycei2 z#vc<(@|4ORa{J;hRQE}{u(ICCMfnqspb(ebmFR%B0>_ANceqFD)SQ4gOcATo%KSY} zIh_#=>OIuwh7THJ##G99DYANhCQF*_+!8ip9(fJ=afavpPbEQ#$SQJHB=wo59X9YM z+wKl?ksBc#v8@oc&a&PKo)&lLS^lkl>Eo(2xnfR{4M z$=kaB3X`<$?@w!j`td{e-$uy%2kEl1qlL+TBf0+q=hPw9uvSsNMj^ls8(^2D`oW@U zzzHCM5vv>)^@E@x(@W*c)@;rbm;)O^g2jTY%HDIY-}j4c5=&ttE}GdHTE+K6sAaQi zH9Aes@J{{2y>85d89WgKvloZVBAy_L)dex~q;#G5V4n zJTvO1-hpHICEt-@_@&;ZA+k5*?FqqzpJiY){%_4ZWk=nw=V)ny~lZ`iAm``i2UG5`@CjZ;spwgaSh2=)IHoa6{u5 zuubGK0$~V3>4REP;jp!%1Yzi5n6MQP2H;V+!;xUhkhx=-pl0@YL-EMj!}7@8f%H^w zv53*7LHkCU12MdX20Xk~29jPf1IyNaf#PP3tfFWSkD+J}o}dsLkf#r&A#(>EK+!V2 zLiLW^5{2SXwMRODuHO29-dn$NaF_0Sa98hwhN9h-2VS@f59qD;Q@xZ1s@Yg7p^d%6O{{XuT8$w%vh3%BrdJok5Kh-K7Tux%u=3$6UV!7@bSB zFjS6?Vul5dHOH^FIJ>VgKdhm{uFlyL7K<*6G9r>x;>NK&jSX&4&~R7h;ESfj8(4-~ zj>0B(3I72I@l;?yvY;=A8WO~qbBQqf7#jXERy^>})u(eiG=;U2-e`fDU}km+bpI{V zjaB*&2u}zVH&ywNlNx8aP|_yZ%=IO< z!!8>XGF(I$g%{_RMzn&UbHsv)0C@pJ7A+T{_U%;%%O4J$We%A`O#o7=#0C$COPa#d ziQ-IZqn8sUO%~2)l_J`Eo)cFHM~h(0kT!4{U8grz^U)BJiMpF^WJg`>_VW68JBsIa zO^!0db{i)X+YY)1XfA6x5xj0{CwC|1^2fQ;&M)f|%FcO}3{97qoxE9R4z@TWucs}I zhV1ok)z?sF!}-XO#dq@P*9@j+FhiC~rd8+zOX6fgn@4+i*?o#&^ z(Q31UR$EtpUYDmdt4+fGGA@h-5^?z#EU>3E7ds1JDH=d3xt7^9!xeU*Br498&(z{Q zJlE1}-&R6uFb!brqIo{jb0Kc)7KRN|Vo0cGMb6{qLA+swd1UUnE%AIWB?L^{vw(lg zcWU-_7w*1eL^e?&Uo0vNh&VzNfQVigknTBFw58?=Gb{+EKKRv6Xi|$NVEf<SRCZJ zL@FC|Pjk|lC+;128pz|i#*}k>dyy|(ABdA_x0F1^^6IfO*mGk;h)gkP{t*bt@t6H7+ltiqA1VC9GV{Vx6eMEL47w4kD^LA!ax$$l$)R{AgZK z4zX(nvRsZ%p33Gt69{m_BE8;indu2T$*q-Sjv(_lI9-cozc(_8wG9mP0en2S=6B+4 z#CY0W2F%blu`P;29aa@=nmygF6XCV2!kp>wCMMvSo0n$a7VXsw^?76S z%6<+{{_@VYO46`u{J`%hgT;=3c07}xv<1sf2u)r~SXFNz-TxdVWH*!EhKJA+ByU_C ztBx)LDgj!~gy04t!7m26 zVoNBS-r^wo1)0M6g|qU*e6N3BqKg5H($t)=5<_2a6fVW;z5)DLVR`qiG|BwaG)(Z-4pgu zYsg(f_89NP*wR+N?P7gm3U`GFrYL8FcwJU6)kOY0<8E$^^O@Gl0S9O6$gJ%Uj3<~k zE9S&8*tE9WMEZE!@9FzAYl!3atG&q!wZtNTnL9=@Z+IUFGgo$A5=X3Jv3_C?Z%c{2 zv&JPK6lA_#auKN#2rvpK!DK{(_HyG}P)r^6 z`ih)*9;egz_zWdP+~HXp$2nP*zA-N75huu-yLcfi?h=tSfjtd5_*qUfh0z4Hah=pu z%`S%`1m%0M#4SWl(hG$snnP7sSH-~g-6G+HTd7-{zg7R`PjN2rx=al5ohE_#?~u6v zzz(W@V{`wl=lSow_MY_Df3SmNeZT?r(if4csww6|fsMf`=2ZB~$s(`|hN}b_{ipOn z6TyXBZdbZiRytZ;?aQt02HG26Yff)sj}O}j{1Z~)?y_<-ud>Izvt2hcK5TBLe`fpI z{({Jd@Lj&l$L3wO8;{YsYPTM!arsV$!m6x~3xiH7am34K1TmvxVLHbDgqMcCDUMSYHn<9ur{zT^T;eoGQpa3O{Gn>O~p;sO~s++ z(r`T@#KED~GI75vjpp?xJ)qZ3yyM_aaR9mb^#TltKO>}@RYT_OB6Jv~l z^Z-PVQFbuVA+jgi^fyMZiFaI_Np=Xuf!SrMLu|r5x|v7Rkqrjg9Nht-ZrUB!dsaUc zW37H)bXE5#OE=j8r*0BM1TdrRUVij%ca>2bbjE8#VZqp;x)=`cQd>+!MjX>M#UR9m zZvh`-D%WJ^6Qz-4NG-Y$N>7lNO5QT_>Saq{CWG)XB4e0$WUX>MTaNrRGbJZ%A7|}b z&$%yQWH1O#xw>imC{E#BZ$4uwn9#5M+9yojlzK%#X-sr5O(c1bl~r5Eub2Ukc2m8n-FOfoyy34tp60gl_BUsNK%^9h~<;?^;- z^#GlMP6_lIPqPnE=x5)oAJQX)&aI;yWSy0zXW0+2e>fPhkeK$nY0#uk(XuRnsj?-q zA%nKFy+tWSRI`^rMV8ZH(#vdFNpiSm56_L0kWzG<79{Sk?N0(rqYmUca|S!z`!T%Q zQ>J#h+ACH{1|uk|NlGFnxa{8ty@y#=Y2Yu+DnDCkr=~w+*h+>M!B~p>Nlt5kp))fz z32n=huw zmuoRJ`%CiW2)C^h^QHJU-oobp&E+ym^v0DO{pee-bmVoXWz1p`YRXO4w_^IN1c}f* zE~K57_ZlP$vaF9oOO#J<=i+*SWC-z=tap31UEf;1&JWYC!+?8Rf{XE)RO%lshlX5_ z+~IREKx9mwSNQY~*3RSb%%8P}1ViDR%;L0y1qZU6Q(!r@D6e>gpJ`d8YWeE)FUzc* z!ebVj$lzqOoPUsZ$HdInDN zLY0IWn}#4PPAY+xrue$-0@4E9;)F@C^SG#JTJdRqz*gqw7SYNR&5DxeYqXQ{kyoo< z>Bd6YeT>HG$@StEW<`~1nWeYmsZ&zUB&csx$VKcagV{urbVr8*X0rG|;H-gNwvii%2;oz4x{vg5?f@@9%U+~EHJKg4QWpJQc|H?tYCC) zxqcpe+_3{Xicp<0l0DIId2;|duo6({b{GA_C29`x5;fmSgrc3C`Zo*VQWU@Tdix}i zIyKauGLU-;#1|%B_>(~gd6U>)oSXDWmo$jp5JAF$OCi4P9)|#FHsJ$lE=qn})Et(( zf`2VqLEByT4;rL`69#O(=p!sbb(+#JVGVLXQz0c4YJu3T0gO%*%-atR>|shR5UKrF zk{8?3B}t=NC!N&Bcs5@F$5_qBF2?s~#JD-_ zk<}91j?hPlrbOz1n3}Y|R>O>uT_9b%g-+L3UZ$27l!RRusH~e@9-Q8gVRybT+sKoK z4exe`+X2k>NF9D1n{4EhLf)FV;=?5AmOcm4sf@$8;F$ydhuFWo1#Dhj9 z2E+9a6I?SeE~nWd%e%Qwx_&>ohwkTq1G)rTgRb8Z0TzG|tdYc{B`DxOA~L+ zy$U5^$aNZz^Ri>Tf99XYopyY#rBL7GTl~Kz;QoWz-@lIif3coQH~%p3=ds&bsZAyr zM=%Z=fO38B4-6a)_l?)nU}P{23XUt`o|fuFl>{H?lMF15ka_|BD2je|Nr4Ab+&Jv@ zvXh(TxH;Q&vn(B8V#tut+ve zXOthP4&X?}*j9gh$}sA3UA)st<0`oAIcB@Q<7c1V+S`D=bi&PE7o)xTvmId-_JK3B z;NusmTZbp9fA7!f%g=t#yUDjS)@vWv5lo@_=}%zSqDhlsjulJA4e-t17ELr}`!t%N zUqQv)xp4A#WIDy^!(;ar6DKi((29sak95Y~ zE9rRhblLKFe4+~A@lCjlFfb%vEnsTax$`+~dD^vbdpV1xS*OE5Rm@?eF15=n$c1CH znSGv0gHfeqUB~=3-x`gc&l-r%Ce56LLdQ4#Qu*gE;IlFwFEd}H=R|zOsrR1~ zWB*~T`@cHnzZzvk6Vh9G`H|O*F^MgM8y^G=f}k2WR6+telvYBdpR8&NBQ+LhOJq9)Shi<8HVr7E$+8hgdah_GT4Yf&DOa; zcKiBIqdigZ=&%d)TC?^T1Gb@WlnnjapgnDmH?#*vj*)xv7JKL(hK{~_{1$O&5~hy+ zBYlr0G>^V}=9Vc`kFk5=7Io-1%ng0_z%BAn9r{-? zT+wciGdgTu$q;*c-kw0H4@?|=Gb6UXx!I{c`}nO;=r`ntk*n{XvR4Mvr|%xLw+fS2 zWj@BFEIp*80<(u6N&z!Qf5?-38EAyDQqi80*ZOCX zRl8>2b;kDOayffzU>zsBC$x!+qbsRt)zKBr%+2A6b4E9gtil~w);PU*e|qzf#yv2y zZQl^ZZpaeF&dhwEg!0<-0*4#Zt?`joyLca$TWqAufiXyI#H(I)#0%s4z@RfO@9B^F z*6gSI54DOHd^0@Zx=46~*vR)=cNDw{VC)! z3T=f>jfc?~DH>gr2v7khZzm|Jp z0xbj^v!|fwDw)IwzmSkpvz=SuTkTg_SL+Tst7ynL#bsP%~#cx~Ypm!HzoZnT4-3 z9HgM{asX-zxPt@>D?DDZ;%j&6O5%W5I^h?|2w}q7v!erib*um?&*k)e%I0(Kk*AuN zB-j?=tI|A{M=3w6kKQKo0uNnkSmp2ZOsv>;a1Ihe7N16W-gv}@guM<4U9>Yg-$r?A zmt*ya)r*xzc7)<}Z4@{U@1J&bF?I1#kB=)w>U!oWJd<8-1_5NnbT1WwAsDoEfw zT9^;;XI?Ow->q%GPb)i{?YAWsU2Xn20;Ixju7@9+iS7mFlVR2rP8R4zJvrjVm#Qs* zHc1gpwuFx6ldYUYuzQb|qiP=dYOL%cci@XJZ-cBtO=KkU@Z_U-*Ztn7qq z=bWz9YRwVCEQ*Df^(K|q2`h_n(I7w#$B5L?f_*T77M6{~P4J6V*jSb<6OJ}bK-B); ztcw^*E+>YRsnI$T6f3JK6SAjuoPs-T*kUaWhdfN99t+i6Jwjx`vqlS*QRY!nTYB`idS%bt3>rWRTj z9*oy2b(9AoF+-=>9!3KHRQe?pR$6GqJi)P2nT@jIgtMB1MMa-GYs~gIZs~^RU+S1` zJ<-IF$Yf8sl0HzvUBSm5F3gh|7TbXc zq^_@}640$PNZDu=R+e*u4I&ranHAg>7h!Uq98#HvQXlt2GR$UO^`TEM+>)OXy3JdW zsVrhf+_z)Ss5!WitSqddL<})+BeIFkX{^=9*?Yv>H%s}bl?N(J69QFjKZ!DljQ$w*(En}EHTH@O}Dq~%+h&BTAj|sq4JRiVUR5EQf zi8P5?_{as3xDn>Pp<`I;gqeM14xbV3D+upWvlZlmMw61arIu8bwa@9O{-BVhrOo_F zH7_A)-Uubu0kc?&Nu-pU(&{5eG?vD(+nxiqe*PFi^I`V@8mS-N%liZ@u9Qp5rLqNf)E4`P(jj*Y1}%Da5p* z!i->hl79sEa2pc8bKzjh5Hl*#4EpVidvw$UEpt$=I<`p1+oe>*r!l zh3MR&}gy*Kt8A}cNKfh+l zE3Pk9)9dASZ3h|xFN?!K&Q;nw!>o0wq!W$XA{%KXT(hVzzQ0B_CXuXHnXFGFUcxE3 zMiP6)P;-eSZc+TgC7RHsn*4shMl^9ES-&t|*OqYQ%UwI=&{HBM| zMu95fPE3+ld{cxrk%u>)EMUS+>euswpe{wi%~gnZs_++W?HS`JH##e3R1!&C-Vm4r zwW`$Nb4kfM9Kl4`yU8^N;p0Pn1!UcD2BLyYsMbu93djD^mE?Nh_-qITT@U(vcs`5M=w}zl z_5^R>*NT+Q(c}T2EE-u{S)z6$-pM@3wvV8DhLlY;*N(;tUy55l0dikhDjV-iN0h9v z5pSL(EwW@DN*X(eD(9TD>3rc65r0KV%wt%2D>@Jda;^xDoJsnVG{sQQ#9XX4gvKhJ z+6RM74djX6y>ko~H0FM|FivhSuY(^=RI%7R%P}okl};^TH1O8?5d9`9Syt?z;R6!0 zW4gY>d&yuvYV$FH)XfFSeMi`y{@XLI9v`h!Uj^U_dwe1=fg8c4(2eHkv6H<7G5fjvIwdo6r_YrU|H*HmhnMv(3V%nZ? zf~DIwE}R}c`{H&Byd$!+E9cfPzeLY=i7=VRI^tJhb3B_wyNj6FkxpZf)KQO-?6Q?9 z`%*Yc)Vraln3|L?tfS&2bV>wg?o}cO`62?*ru->``Xpl+@i_AIu6A?varGgxE`Tzo zfMH1)i16^GgfoqK@S?o|fWIYlwD1_9fHl?eV|Td$|1_veJUeU;^yh!KhjqmJoGIAd zL=+Z>AKv}NLB)F|?q_^+m{Ml9dWAunt=hFdHZd|8Yg?Zqx+`Bcj^GJ}#s}we@5Tjf z5K8`vlM~1|*!jw0`3l3f=Yj+{-52%+<~WFM`N;c;|4O#?Q(>_7m2E3@cHr|B3;s$b z5(}NhMElKhN7rtpxA^IL$$XPHi&I*MrnF^o9ZlLwqG_GF6rO~g)Xl-yh{87q$~_9h z%dq_jpeO@us#P`!CPOUSDjQBh!`Oy)j(KhL3*4JD2yJB-KnzM55abmZ9D(e0-Lt{a z5^)vJq>`Y|C=)>SK2Pg)HZUF_Ys*f~CbWKT>mg^>J}tgKO)c5`N}vULe&4 zZ4njNVE<>m3L3&Q7zYUd!6s}>gahLosXvc|ufIF59mECl?v3nmgu^OrI`_n4PF&1= zH}z3D_Wam9!tZBI=UjxW;%+1DqKqEg_Q9^G8Z9f`vA~HzqkuN-=YB^X7AMY}1rPXY z1w?yYTU9n&yd6C6*L=IMzul5EO}H2F`f)T|U09r>u^Jb4@XQ+v@gT=b>J#=IS31_w zF(O@9?L)B|=W%eHc=PcPf5wn6?JeK_I)=mmy0O|vVmDnGo8cIf<6xh|z41=Hple1e zBT?HMrAqEUi(7QiZcEz95zeZoQD3ZXCW4vTDB@4JKjrO$L)E!9wCC|Ms_cG9>b3moXrK29r>e>7Cp(7tYR0|WO-jz9?uNa8r)4uGPhsc0rlU(B2q3KWPFLLQdtvne@S|4UB0K1$yN64-q zzTJvU>DQQK7?|=bg*?5liG-liuZcL~&lCc-r!~Wd>VPpQN?df?f*Vmy&L_h)7P*UP z?M6daQ?k^hKXRm=A>K8f#q`digDe5Z%u>VGh>hJtgfNjx&TB#;y5AYV#eyl=&x*Q& zW<#iW9i%+AY%EUQf+`B%!$bu3y(QNgM!WoPj3?Xh(iJ9)9`h(VQ|wA_KmyT^gp zueVckjl%2QNWI2}#ADNNuu`~<0Q4GdD30zJ$XZJ}spUN@dwrnYy2-pyxC9MdMymCvB6{2B1YeLA*s5j^@iZL)+)6r5%}4{uG<^Sg;+Y!d zq8X*ot=xG{wsld+0|8oND#f!s`iqE>n3*NXeq_O+b?Uq6zaYM3C){(be;p;F;}Lde z6vIIr+Eb&#V^^qbELD1@+meFFjd^zb7hw>?jgb%GjxpNAXWiiyg;n5p-Fpk${E6@i z54nwFC={s1+P~2))rpvq6ykE9w(!GQ_A`&)Vo572znhi9w(M+Li^Zhu>`IHprL2PA zhKk_vJsi$B#v+zdgXf&IcJ4@~DeG(tZ{=t*jJlco^HA}4<$=$d*PJw!=6*|=AA4Wd z`+Hy)L{vK;V0l-t0Z@RAIs2ffLD2tur9e`_Xu-KTaB{@zh&TSyIuGnLed^)|(Gylp5^s%;6JehR*O?}5J zvC%m0=Oh!x%{GwP->I=FCXX#yHNqT}cdR`x6@`W=OzFJKjFHkFJmFnoWU9~L(xdx+ zM2_bdlw%lMm=&&^Qwg;=5`knsLMh8+>#Js3a!I{Z6ByD>4r5KBNmm(E-I7UuDrtIT z0@`kZf%lJ3i@0o`rm(Z%Ia)z~{eLNB+nT;_8VSF;<+8;3XRWVaOvdZuY= zKsnFRZ}~2}mXLfHS>xiMQG>#uyocz?KkncY9cmmL(EYH1O-@IUx`&IR1j@_!hKDzo zb_ZXs)3gZ^gM9#!Cj#lE+eZCQVE-ox)oR4K0s+zJcT}@a;!ip91rZAl$|l~qO(EiH z$(DMLReDcCl3Pr=-oLm@`Wl&+$Z3!6>T)CnK?!|^H{DbP08O_GkUh_c^I7MXzxL3dljGPKOK0K(BJ+;LK z_#@#>br)0R`+HGnkS05pyQ!4>%ZmJim7jDTD6Yj)-v4<|e_v|ZCkGq|NDlem_5J^m z6X*Z50>gUZsiA*axF^dcTP63M^U?#iXSaEk!_!ORD&xu$W-KfewIy$_yKb!KntCWN zfPm8&stvt>=10WS6k%9hVYCp@#9O1=)V8Wq-G-CA0wJUESu}l5p6k;TzVA<$0p>Sv zw*j;KFI&7GF0W6#h`<%f(lB=pgkL9sUBB0G#J~6ZZiG8`)L5auY|K5ug`r010P8Sz zI85fA=)%-}<1jb4>rh1_j94VG z(G2C)5WNSG4tufgz-zbafLvwuRR(}|C;)=j%J-RWmhWKk797D0%wOBW4EB9+2#iOS zu8cYi0L)>BaFkf`EIG!WSO!aXP{|6cd{On*o^YHto_GfHcVO{<$lCcsb-o>9pE!Ag zvEMPBd1jt*`?DP)fZwlyVVI6Ti3|6|e1!lqbnh%OpQwZjcQAc3clcqq)}H9?)}HW? z>|%qc^3%-~+yqPXy+NXM zMOp4GMcg2=mUa*)cSx9n^$ zV2Z}n5cAGBTj@G5spDifLW;M=P~B0}@qKR$n7pHc$?l??gyrbr*O}*XjR;LPRB{q{ zzrwjZnP{#t+rI@B)fg}24sMYBs1`!zaXEX?!r^YttZJg6g3%dRk8kTfN6M|N&C-gh z@as?_XlvYBe-P$7d2~)?k2}b0%84~PyhOK;Uz-`qH8BBKjft~mg@_^oWEU>`Qs`}L z<#Frrk7bEgCAlk@J!-i-zkS!oOd;})xXJS{ER_&?;4E&!klm)Quk@Jtu5BU9*-0vq z^A}?~;nLmix!T+N*wHyN%=F68S&T{ZbmJaiv|SK6%tJDj<}J_fU#0o2!{9e~jG?D6 zRaJD%@LSg!+1s#7e~-$o2N~UMzo!bwWAlz_V4eS%A`uE!N2m_pH2Y-gf@3sG|4>NT zE-1|!TN3Ip=a}@ET@Sk0t8J#lJ^kCwT=fB9{3GRWbi&8je}=Tkrcq=rY4wG|YRz*x z-`YEmfDME8j)U2rp=6?_Q8FD-`z>-IR;zc@Vz7N9hKimTc0oMsmx2XI55=y7s{3b5{=ojY23jb`3+`mv_9T~d60Gr4K1Qh6B7$MRxS&|Mc)6H77+nU*MtV=eE zc17%kV-+hid#=)OdJt4t^uw%R5xm|@JHB)kq``l)Ok^pF_@y!z(CaoglnI^covu~# zx@yVk=(XzW;o@!S8)&I?QmM+|mLzQaq0G5m^EE3=z{|erN%3pCLG0_3(8f_ECM2jB z3r56Q2XtT({ZU>JNNOoheg@t;-3U**m2m2}i6_6lFX2yYp-g^Gu}R+}x$pu<`^eZa zPf!~>0e>z&KACmPt~QjM=(qvq`pb17!XWYI-d6O zBiS@QvZtWFqmejLH(?8abf9yKG^wa3mOckP<$dj%JP1nEmx)ds2^`y^Y zy6@Ewcvs<+LXpAcR?^vGp5rdz34EfQl1YI?Fcx=VY%#(&4ej2gLy7DU`LP8{!fe{u ztT|D?9LZn2@SPMBd2xbc<0`a!n+}~RvDhE#Jqs(c1^Q9@3M?LVLw*E=QqD5_r2#*H z2hYDF42|{oN*v$ zD9sZ<_eGMj;U9yeYpo~xiCHJBjJSBE>CLI)5QNGV@f&?^GUOr+2>12`{%MCmcF*En z@XNf0L}jc#8+v3r2*RI{dfV6DkVjB%*AGdCA4>L?7=yKgSyee}U7v%6;?X1!db$+s@Q?C~)?!vWMvT9y*O5Xa5}D%myKpJG+Ki_+7e}_Z9DG(X@~a0?9Bitv8U@A0C1t#+#=sw_9# zeg4~O-)-sc+^oWuyX9IZSIc+}Ne>AU?PoGA@iyJJNYQt;LDdR!8KB@Vo%)AtT{}s_ zoUCD@7`J`d)`O?~T5qG?NoHQ@l19m0kiEs~@R=WWiqfREdtXv_-#&0&@lksdr zf~~gYouL{86=^K5HEIBa0{DQW3@=^LsV7`()NF<}=hj94xM##G_c!PaBDdhdN5*d% zSyl_Y{DO;DJoeG@DT^tH6Fg4G(fLyW1E%bXMJceG1s{;#0sjPDws~BH*M9q*`u?ro zN%Y^NDgKqxX! zMmIv^pQ%fZnVOmC(pfS{RS*_>+#*Du?n3e7=R$m$;{ScP75L}t{sX2U(heUT--c(! z&Gu|A>01tR~uy2zF@!IL+3RZ zP-QzE$MH`Dp{CxBA%!hqQmV%eQ;R?o=G!D~VQ%qVc)n~mueFqVejb8mAvAZc#>xUP z=;KJppo3d0uu^X0=giT{y?7;4eil0os3)Zx>11B{z3}*xcwn$tvw0dJ3#&HiQd$R4 zt<W;iIt(3ifa#0?s%IjHS#5CC}V3ooP{yc;;y0#=Lk|h zQ=W9#8j^0$Dr!}gtla=mZofcx7pCAZv|pM^>~LT_RTzB-?kQpR#tvaikAy8HY+(*0xDX7td zv8aYts7xU!dSU4d-CGi2?>R2G!~hhnM)kn;4o!(W+I;xu(A0TZQc?G>U~d{nJxy@j z+kX_T?#29G^Sfwn|F&rVO$YwpiuSLXeRE+vahH+vEGE-P1;fdPI!hhzna<9*bW5T-sV%=9CMN@GPl&Eel`#cE2BbGjq4K zH1UUxw~g7|m;Pm+zf;+noDMrW;|c;#cf~+RDCrsla{7(yv>*D7!?bN)!wBDqN%ZcX z1HSsTYjmHU1K;|y`>0<1`EIbZfl`?tzhrzBK^*RdsAJv+Kxc_M;`Y!5sDe5FB(TW; z^i#XjC8P^221`fM6?fzw1QW_25-vayg{FWmgJKJ!g{~usKpuj2{U*Z*IkE>z86vNN zNd+B(ts>i7=0{~n^BxQ|C*YQR3#32k z&Y>fHk5m~7SlM%LRyk$xPbpP6Wl2Q=C7~=TpZFaoHQ9$^6wNEoH!y|^_KR$og67`v}bl-ivOwK{4|pn-HFMj!Do4^Bm(gR~NCK%z1KH^>G48#!|; z-H7Z@BZluQ7A1EFPJLljK;Ta|+{rcv)Gj>_Wspkp<2`E+@jFl6n0AX)jP*l3ipWWFeb7B;-?sw{R?;dcF9FcbZXk84I#XblhfG=cM_wrJtbN-o;tu^zI8 z7Bhk*yDI6Pgl_DWof3@b3bfTO@?gIrk7sbh*tpJ|=2Pn@gVI_ytj!PQ&FJAA`x7l;=)V z$(!8vpg+S*g5T4_WcGd<>k<}0fp7S}{n(IPV>@^I9 zCM^n!nehR&%YQRO+P*n{RrvAsi{g={t9cbdmUN~&#mz5`EiF^l$~6izb8+f773gnN z(;NP&T(P{McB9%;2% zhPoB1x7jzgXX{#Kxk2OB(51Zg=%R3$$vsu>Qq<#Xv-I*?bvcS^QU4z7BUw;kBPv;7>H}{+Y+` zJb>K+`1yFxQHNsipYl!c`T=}jTcx5K`Q%U7MZK$EcseOnV!N}QGGiZJ9DguG8+$LO zTDV3`^_4I$b7Y=;NsfAMXPf*~V=0ZJ^d4EbinDpQPVxLKrS?hsCu`aMo^bj?9O&BT z0L~`$-f*LxzWOh-{FIoNshom)nTMOg(DkCL)C22bCOoXV%x^LF&e=(-el~}(>G5a8 zC{&zK$&na>V`dfw=5@xsC0^hYoOgRx>@p5`tVw1Wq0bVk`aCPCNRcHRS8SbBpf-p` zmiT~u=UiTu{G=&=8U+Lg`_mE^kkp)gYEY(PX%prtPZ8iA|H@>na1=gWS7q%j44Ax#1D=t=1sC-s_NzaHnL;H)fNe}EHCYEBV>=BJ*e>$XDoH9N$s+#Qkfp1 zE~+B-mThM>e{L8q&WMt0{3#N9ja?NrCrqY31R(g})Ypktm(2@r6ou44}YS}MLoln%HLbAe3_%4~6GsIWEm&8(NloEVv*UC5^ zTQLTyiO=U3?5tmBVR>yGn@p6!(jcGe!5jC}l2zhqZ&>Bos1}XJXQw zathTcUVdL^PZat@=q<2~$ifzA0u{Le`})eHzbU8Z<%2bUmeWu){+L+nF568KT3<)F zhmq8Fe%2N~-?UxtJK6_Gcy!2JTLPZ!os`!Fn^`}mE+cmx$Wb%YE5UUVBxx4-VUel# z)`QB1dxO0+RgSi02C{@1p+PQAy6>SYJT#QSY^lC_S@g@kjTV~2QFkXPl)$m}m6p3J zLFhg}FR%Vvb@EnE;gx)ceX{e1QT*t5%ua|E87OLLJW`k6X9F^^VosNuNBN__QU^h- zhFh507M#rmj!gM5sNOTie6@|qDQl(}Py$#sQ4WY zSF)|TY5ou48@z2Ep_EMf>6_~ZmG0rfsMeP520ksd)pp%M;A$HqSL;=FCM#9$Z7r>? z5*wQhScU=aDruV#+s*SDn*-4->X5d%FQ-!`RX@p|pbv+rXiB8LbtB1_kz>+sai;U6 zdvAU~k);Sjr-+anPMx1p0$~Nho~LtMy&?K<#2k{ZsYA${2rlIWrQQO+bDvWWIokqX zI2J(LE5O$dhTdIm>Eo)@gR8ioDLTBhRYhw8QQLIzavmwSrAI?F6 zb+r_rgR@ML)_0sfMT1Qcn8b?d%<<%%<-Yag_|~5 zzD|E~`UtUak^|q(ee+)9(rjnBUqS1&I&<7{>^ot<*I{y}KN1qGi`Aorx*~M(lj`1) z#%7;D+$=$5JxMR5NDHWa;B+0m;y49jPb#e^n6#eyhyB4=_1@tY37hlQGGDsfL+l*~ zg|QvplWy9^v{HNn?h8m9Pe$+0sj_FMC6ha@(^aTZeoCK|I+^NY*0M}~=UI~qN0;vx z8kQYE{Sn;k#;S2zy%+oz)#&bUFeb6jFtSHH6DHH3e5?}AZH?-S_JB}UJH6NtZPDz} zor|{$`Zr`Mv=;8~cd-4+$v32`ZuXS1{BL(qj*2PH$}!7j;(7wTk(-sd*dDN^KTMU2 zMm_lYp==&8uZGhYH_=Y2UVP@SfQ)`r!~y)9@xH5Tw-fK3M=Vcy!0^XYE1Y~e_{AcK z4D~{_0>BdbLVHk7Ij#3>N>*7#)=)4<8df(R_Gs9h#j)Xp;x26mIxkCJ!LIcbK~wA6 zN&p@c+7~>b-0=EZ@d;|I5&eKz)5XD#^?3_ez*@1StvP0->9G<2I9}v2IRA)I6G4L=1u%!w6 zNU5Q3aZT~fk-qv+I)jZzV%WdQio<>NmYmJAKnUCJ93h{Dcg`5@#(Inj@D^ zMWX60rGSROLSgDII7}w9!<51)R#^3hof>A5hq~ z5Om_dg5`&hZ4rQfp@4)CAJ|VnO2V`91Mr9%P!zHm1Wr4b_OWqZtk&b>zg|AK^*&b(Zq<6d)t2E5-apIpAb)!I zXop>*$IxJ@%&9Wf843?>qV-4&w$r?FfZwX!UBmQ=40_PKGJx;a?)jkk)(73H+feTvc;;e8?K|}B1;%?7g4vtA(lVy} z(&;G}Fz_ja+qnQ|?Xu{v_ zYG4coan)XFKFVPX0HriN5v_33G<^{I#;?qPzf~RBoRuBuxT;$af~K#XQP-L^2kx&L zAX`NT^d0KM2(KRKy!#$BH@|4mJ$~zPexw0uRCv;^RCi!vR&^j^R=jH0C^`wYI{{6q z@tyWarMcsW1mct6l}yqwYbV|Jj$I!QoOF zKGdu`fIi6`utrawl3`|yZC0IXc#wLHjQS40M2B?57D+HFjVkXfWYsJ`xaN`@=G8ow z&nZ06b6-Uqip*9?SM~f~P$XW49#-M&rty_0M4p-rceOmp>=9>&uuXc}pvu3fstJ)JJ zgVgiY;Iu#(C2>e0oGFMrUGnh`&CqU-jZ_xTr^y&?lh#f;RjIY(B+FX~UYxa#d3C{G z7QSFJis@0F9JVeo++4whzEGsyK%2;MK!Hv_fuZzAsY-5m(4Dmg@oeAz6O6e^Ok7*R zx>u=p?~f~=)a|Lvt({f>qqTxEZzZKl&5jY?s@`1J6w;!)WQ)M|QnENz$8w*2)F?`p zRd=800dAuv#dOgi0_m`xm!FY(`%FnGYQln)Ui9&A(~MST6Hx$<$2xqdr3r+E6LFbb z;g5BjeSa{6=?75d9#88H?yQpqv!zRS*4h^>h_{ixu@#+xWA{JfF_|33-j40a7v)iL zRIR$oivn~E(R9^T`CKrX;~L32xvA+=$I3EKB#n-*kXy@*NDEF?Fm+%h$3N@H>y+1u4A~M~yOZq5g17 zog8i^9Z888Md6)4p{gc@y;SI0d{{qQcRBUW4UJ8;>M?W}J7gpctf^VZ3Bm9955=&0 zYd#w$UrvN>Y2K)t^`{2vI@LB9TxQX6x6jzwb-oj)FPCSLZ6oAlP;EWORv-P zi58b|gpqajdmU)ob9kdpbo|~I7cw`6fjNPMRZU1&KKv`dZC@QG`EBvwUwOor~sTpepZZcOX70~w{eQEw%*-Cj*NGi z>hiXAm!0!Q=nU*UwUpeqoBMi@BG0FoGjOY2p@#>3GGqVnLN(WgI)3hYXRU<5NZe6H zRi>1OeKF#!l8+F-u89VP?EeinfPIqw_`)zPe%kCnEMbKk?RWT)VX@u4%e2yNjb!WNe@jkNu`Bajk8fbC3|yWQ(l(;9^TzTBXO^YBCqr||$&xMz} zp9WGp%8qCSCC$Fh#&=3mCSiNxWml$+9xC2P;7>9)=c^Pe{@{==k4AF){!6&?7>Pal z(=R(9R+k&B{#xQQxZ-DApJ_C*RNBO3s=RjJ>Eu(Q3AWZHgHOs|8Jgm7#W+|)q<4RLII)<8s z{Wvn2ZloX`PHNQUXW}=jkfbuqe8cezSbXwv?S4Pvch-AlVjs(4*`c!y-#X?J%2N5a zjO)dd5n=vLWgJA7xsW`lDdN~nFwZjoIOA#CoHsXa$LiYE9ABf1$`%+Kh%kz$psbMK zPAvvCU#XMK^FoHrvDj%d>t4cGI{esHL9jUOI$^Lohi6rZ$Qi@vP~l*0r;860%jjSa z&weo-i)KVyn|VQ)S?RyuaP?Bo!Zzp6OGBYHeDejLT$wc5EX6L9oED_lx|qPOMLj%) z=kZ(s)F9(d@zg*-JYxH&Gy5bIgMDUg-F6;qw-KodmSB-QG#5uff`4=5!*|poWQ3aT z1InKhJHvgX4u$>KL8Q8NNLNn^z3BqS1pUgz9^$>WSmwa6Akv)lL6Dh;ivNi{znMpK zatkJg_62NbmfhrOO`pl%R+vI-TQ5$*>zNFnv%%THvKJ>sM1_|FG|p2gt+$pCMq27* z9Sxstn!`VK5VW&+eXY)=1Sc>`q$MsfI&W6xsZx?NalTy*g{%vo;?^oFs3Be^upiaJ zS$$ot?U_k(-kwU;k`|b3y{&zH8uf&FD`-}_%N^RqV)AmM5;A`l?h~2sIiEQ=E#c(~ z?~mzeaW&e)vT>;MmAyD}Q_Bv0{mI%FoOpneZe3gZg5h^JxVcQ8MJ}LX0z}`=Nn;l6<)Zx%rKG3&1Tp;`e|> za4s&oHvHWqzif&QnBD8Cg4np5YbCpbqw|g8?nrD{qNyRhpzl~I)Q1&&Oy2>w&JPVu z2fGoua}#>rhz5BwdUcAkU2b-)CCi?eU~o1~9YC7%O%C}^@Wy}OxyU!}dI!x_bHn&c zH|38IjH39g?xGP{#=A7+K7^E4K%1bOMCYP=HjO|40tb&rUf`43^67w%94Ds!o7_`* zp{ZQuXI<0MO$i1|dB=jv6{$2K#HuMq$-Z%ID4f$0Ip|i>! z=~?QidmoqEi46+3RLDD8a{K!Z6q$0n^b)6ul>HpyF9Pa4xK~Ue_h!-M2FWBnMkmCH zt!0!Kst=DFNyXDX@rP@rSF0PzxrSbKl1tH*jCR zh##;o$`i?lHCie^ZRNBGKLgGjg_0nGAW}s**#)T91&GC16bQ<#kP|f15xF7ehc?b8 zL}a%DUJ%B3Sg$6{Oq03Pi`8pc0dA73L-?-D$xVtW;1>)p6sOD*s54`W^p-pKuS@b9 zvscQ&5dHCHd@WQAu6?zmL^1_{AZ!vZ*?59vuT)VV18)ns32a}OzLTy)iYV(D^7tnC zLn7bNw>uQ6ha`3tC(=jrq%O4s`893MB%91yxx_xXLLusWFG938hzJ`utz)z)`W!?; zJuZXwplFj^ai7);V-#Kak57YIQ>)a@)Tg-YQ!L7xlP)PVVM>fA%$JbkO@-(unXgU( zL0U!n9wB%Y#ka8W7w^LOZqDE*%S97<@EmpXUkT@0krQdFdyL2~bPe96ODhQ-VGTxH zB`MnX30;5izrs8c@7NVH@R2ZE&;hrKm^`YpW}0mJEuk3}&6^j&amaFNz&0-d2p3fZ zAz3s#LOvkR2pcAZGAkY!tLTey{#HLdZcN-~UFc)8&+t50rvl*>VQ~u+zXK~z&*f!! zsna?zFG1RJPS23Yq6X{%3{}?zPYiOm;XWcG4{(6JC8nwY{S;s61=Yl?(f@dpWYZa|!Pmx^Kvl@LWe4 zGNm-QwJ#j5qjSh49tV8Ve6XbZpr=i4{dFD}`$8l^ z@aRoGOTTZAx)@N?L^(S@#{>B~VTW2(VQ4t<(Q9O9bzTKt@uucQ5u1^m%5p=$7)kc( zv^=aE%KrN2sXoVp;;#+_AfO$#f2UH={a2^@|HnLRWo!EX$P}rndJcDt3sB5i-J8k-8c|e#qB|8`lI+`5uxv=;)SlE(9kES?uRSl1NNFQuF2 z8P6Z>a5nm%1g0ZGUeuL^iXjc(F?-gk+-k9Tl+9$Bib&H(x&FD^iv2 zQNuEG#{nN!vaBk`T&qof=e4eaQ-Xhv?2h5VftG(i3j_4p9%Z__%m@Z z7j|&4H8ZmRmx5BOrlW!l(1__dMGl z@CS4Nzh;;j1z-!8Kh%H(Fou(1rW1O|7AEHhx^M2RR?00;bxf=Z$>?WtT4t+(94 znC>p>tRsoyR0ZiT-lw>+l-&1&2T0mv4CuipV=X!&ir|!m@Wu+k6CS38VV~iggmjY- zRgXzUl+(!fW*x+nw?J-}5lSI4$ec+bo_I{@Ejr4!o7ULfI979b`Fr-BY}#8jA^#2q zT#fa_x8QF5>nvd0EXwt2Z1Ttx} z=hfLNAF!zfv7+{lzCXqgiG5-$V=UXao7TJ05bI)JJxJdgm4_{ZZTL&OZuvfF!D-6+@h%G2oR z9@ewzSFA$Lo~;HJbb}%mf(5ftEGOIQO7TaD`CHK(az#suf!Ew-s@{&fQA#&jqi)ci z!06YkysuM;Rj)s$wY45P76{^GlxCaG7O~p1M=aB!?7`XF3(qL3g05157kGh!sut=k zyObHGXQdhIt!hZL5Bfj>O-P(A`^A9yXkqz>K+kkZl7plNRd<|Vfo&PWx*m!OH*89L zZd5!nB1%;BZgQXN1PcE1n8wJ`i+*{LAjF$s5H)9C`r?!8ie`v7Wt3} z8g&^L203+e`1%&Kw`8(A2=1-zqrpF<^x)ACM%?etqx{Wd{l81;EdO7+kC*Fzgi?LC z9+yp3q%Sm`Hagmu79_j^x$T27c`+4BVk_;WFx>(739^BVz5=T9I20{sa&EGzyMg1- zNkS+@IIysuJ0x?QBqOXczhlIEh^xeqMGAY$DJFtk{%(tx&F1vxufuwS07ysZcCznm z@oBQMt~39m#`fG*Lc4I&fc(oRDyT3!4xSV?G==Rz) zq>M3X1z%up{X403kY$zqPXHaa|X;hZ>Gqq*}1rFXiF@P z>W97~PTgRc+)T^8bTrRsfjrw|mwkqS-C>vtB?(LPGGCKX+p6Pw&>QhO+1hrPeIlC? zbN59(l;p|^g>ZxhUVD{O3VK=QWj0Q$^KuJ+ZCy8kOY24i$MDS={AB;}QkiD%`s5S` zhm2bOobE~JYYQhh&q z8=l$0eFNepy8|+!Njg_1yIqw2L++gttR&|ZxrNF6eihq{XqHR?@bbtg?DfX$0JJ4@ z0nD}K!cToXCzBFF(yZ|hiqs_^$$(&c66YGE!!2MQm~L0u*5X!DMYr+5aQ{~@(M1{% zoHADbjoPYAzeiPRpM>Qp0%bwY>9;E|j)W14E+y+MeIjnbbE5dlDGsu@IUmb(A{bv$ z9?mR|_Q=B<21t(GM8+b6Lt)91x;p%lSAjkyY|62vAHF81w}Q)o5fBbmaqPSww zk7!^O_3AR_|8T>ASd~gbZVlV(Me;V#FDkk-?Gk3~g=P`!*tBFt zEFYVcLH6F3#h4yOTYL|h(8e@R*pFzXN{0qjZ14eI?s@V)?4xK*mXvM0V-(JlN((J* zX4KB)`%_>-igxw1LcZ{_SqX}oJ>ciFpKGgnzo)ZX;9mua3mj?}tMV=dgBa~h#&f_k zbUpwFeMlSzLCOGrXMOTt+n=Hxx_aX82Bh=y>h` zA?Es#-BH`?-o+M*kyJ)5HwJ!HLPb6H7&?8jwEm6l_P9>CQ}_i6LU0C)ShcsH{Ca=(0m5`*{m!J#kTv89%mgz>&5+r< z2HTMm`UPu@jWn5wmUNbmJXjDz#lm9jsb3bWV(g_v#~6Lw62q03JpM`!E;2?tW-K~L zeuNfdq&ldktHczifu@Uk;8p=c;ix_ckM_SR&XzHR%BXDE0iBu%~autYZhKrN{t=u};`_)Zs{s9B`Q~DY$Z%&S=OkM3TeZxjf4--Om8f#4AS# zTarWr>phOvor4&rT^PNUkJ4%WvXrf{pdZt!V<(oe<$hB<*K{~r?2)xhx5CLeG6$8{ zyaIm_Mgk8-F&KlCWzOyW!_R{HEv3Uh`LVu;%fi)uPEl{Z+GWCfo@UMc)+?(jIL(9i zB*CphwKuSp3K9gM7T?-Uh?sPe)%ujIC<@8cY3>9o#sDZau}G{fQj?8``W@A8-0AGO zSd!D&za_&gSl6Y^Z^5Q>@-jm486>{dhm3D=+Nqveo!-d%7k}u?CF(>)mM=%PO1VnsY%{ zuu&Rylkri|P>DW2XG`yMyuSW@EUJ%f0jol6X~1HCd#ArD^rsIw`6EG7Y)0&}+UPy> zK$Lhg6F^8nagR4*87WZ>l6%k#42d({ta`tDtj7KIESbzNmDsdM!Q_7H;>mC8!I&oF zsu0TLKb2^N0_(qZZHY=aiEPT6}+;s*RK zrCA?hM>I;z7vp}3Y0k%tk60)8T|*}EJHriTDEiBTH0hO_FpS!WD7S|kxAsx}hY|^C zIpp>t%)ODGq^U(R@o zAo(UHxaEa>UkIGE?4~Q6?Iz@Po0?mD>AoCGd&v1T#=(lYgm%Q}tNg{hrtp55%HvZ^ zDX|dgw;ZWw;QP42GxQ%5l=w@)i~N6N5Gcm~zfVK{w@KaJLd@P3bT0D$PGGC5c@Cb zeovxzn{)c{#2r`tz6Q)*!xLQq^Zp?1?m45M&mjTn(qhHq)1a)svlfUVMtu$FVS6m3 zK$l)|okhl0iz-%TlPgzH{q15yp1-oEo@3Iq&BZTh?<>Q?;@4=rXcqN*rF1q0XKOBo zN+fhauPPrt(Y#W({Fpb%xzEX`-NHlg{^qFKV8cBUsNcD(0pHf2MRAsTTqKh<$$u8| zz)pe$#bHMMRe?>9Us;|F;k9wea*zm**&6TGy2tR-ciD`F&FA-q7$p7zjcr+Z#_06{ zzfRxh*q&>u;M4|u6Jlleo&ovSyiNKR8+gEhToi*00o-gE`IMCvW!OC0pb4>hy7B~A zJO6b5ppXb_(i4fn^jX2FvrD`Z>0{`mn*(j9^oUCkdp2t2iRIu4)=jk`Z0M5)npNyP zgi=XyQNxrvqn+@SimsGjD;{GD_2Rjesedk7(dwkESqcQdxmsLd0T(^Y0kN znW=}cDfLka+S07z572|}tOJoA3r=H+@(|jV>yri=CyP%nXDLVv$0F$M|3! zbHlPohs3~KYQ};PWWYV3?NOKWwyhFw1uOK(^En+TB}uV%6l0Y*okj9m`Gb41krmQu z8G1`m!yMz!t($*O8+oev1@}TxdhLWrMM_+Ib{~ZKh13N$XCL@}M`5LDwNN(p7Tg_S z$!G0MBU%yWa-61h6`IpYeG*aH%gq=+&Gp42s8|hAI2-R3a+}eovh3Qiw#(>%Nvg$O zx*MkQ`J{Nj^Abw_9QLCM`ut?>AD;t-<4rv>fN*5~x0ZL3iAD~S_3I8nU&w6 z^-4F3^A52->s&_%%5<5IsVmntAQ%Y{-Ct}>RCPdJ;8*rv`XFeUQ1>zu_J|=Qu(aX! z*uYA8h~WEfai@L8Z)X(&nIZ`l35b}Ho6RQfZ1jPdEp8IB@BuScKbfu%tZ*vWY7Usz zkUiKc4q*EjksI2OSl>Kg%L0`%RUB}%Ax%Z-;S#KOu(wLn=Pp!UC`DT*&IUJKEaLO1 zvS=8OWLqo_-NZApb$29uJ*vEl&(ht@n+~s`dW>3PX}o}d!}LupOwZKl3UxeJ-|?-b z@5e`UDR{*k6RU~N1x7qApCb#YzEz3scm-@~dHF9Q98NS&pCf5(a=Ocl;<;zJi>tvo zoH=B_>#;R$UVv$OMhM&G<=EzZ+2hDwBl2ML9`#_1TxRucT=TR0IMFeeTYQKQ= zNgAMPNV*m9#XsE#xDs#&DO;s@uqIOHMDr(;tyf+amoI$r5v1jvCgmGX@Ql?#tA3nI>}6P_4Oj{AwHzXB01^@AVezGjgR_kM7x z`ze6&La=ZFr@qXE4_CHUl^mh=T^)Z6b%=9&AZ*Y^n?#A}erts?6J0(%Mzx5vKeVIL zqD1ELO34iGQa$oc;*3m?eAg*>iq1E{G~cOxnm3WTbcf()YPEjGRWo%GYh&_=eO}0y zY1*3)W9dXsX0ak7V}Lwgw5*D;3H-IpF#q|0W{Lj%c!DF!*KYRVEvsb$qIaH^;(Z5b z*gG6+!!I3mSZ`2&oKe3LqbDs0MMi&%B9{LJilhPm0uiW~28FZnHFxZ83i5C~4w8(f zQAWAoRQO;q#*Y~%BJ@f&V-Yev_kSGjA4)D;nFLLTf(@GZG9t<28=~< zBAtOSQ0QWD%E&z#aK79VBg<`VBO~IFux)v<*L<{$rb^v=&+nH6x^OA4_dl0UWhgVV^Cl1ttrcyYfF90;=l)w%9Fp-0HrL z=KASlmj!!8#g;sCjzW8?0XEHBMy1BM4S5-rcB;X>9SEieA|@9CNiN>WAj5 z1L7ft@j;JxAw|ZyXG&y?~e9w+$WiEvvGnBdS*Ow{guW-$`XxYVgZFHFNK8^AVrL`M%s-evh3w zD?>+C`%xLcJNQk*PTXq*fjXDInPDQa4wDXr(PhO5;Yf7mJ6`czq1~vNJxe?;ZdE&W@74+!ApvIN+j{8Zzj#j zq$MqWh2^4pBr&DL6jLP{GGZCBRcHr`QcSV=-J~wfxyTf2tCSEQ%zh25BRs(tXb*uP zOuNh-Ob?nV?=`qeq{AIEqcEyTpwAgWFF$G2p~P$Id04~_R}*fin)|(Kp(oMu4>@GB zClTB0LqmTntT!;9Y(^xOFh-VdU@TE2EI}yKWc;R1PLRD^TSBC9a$g?++u;t49pQ7@u5~Xh_--70?aei-m0)>bIVkL1R_^ z66$eMLj6k6WguL_9EVnpUFeHM(t%}SDFp}kYEa`$9;EIQ8AH7su&tGP3s#|$FJ`xG zn=Q7suNbG~cJ)JvA!cg_mP$4Yb7Ph|OgcWt>har^k~#eyYN?&v-GBix2PPA!6cT!4 z8+Y7$+MdXh7%Ie1ZSMKxOUs3tINYsZY(?}ZkX zD=lXau>3y^kQo>G7XjR@)@F*V4Z?2DD~y=GtAM;TD`6x|u4&>26sX_eXE*wmVBV z_nmeXL{^8n9KK|IU)6AyR9qVNM$o}~cfm`U=ren=DivSQ+OMP<9{X+XH|qSOL6{fy z&L1N}kIk-5aCq|hfI}alG7NMts0vA{(SJmH4E9&S*uBeQH zM6Timh+|RH>2WP^xqjIVxAyiLa}YQ?nj`VeTxY@6@Lem7zQ%1#Y1Ut(*EkB^b7J~M z^^FvJ_A3z)Z=b`P7UF2^GgH)C{RQmEcSW4Fa7Dc`Tb%6sdi}Oz+@H9H0M<*hMagZQ z*4V(hf#6hDJ6_)(XMY-@sp^!>C~?VPP$OjhrxB8h#F`cXHA1x;vPtZJHA3?+eIe46 z37r(-OTOeC!d$Qvnegy7L{v_EH)&_l^Jt`r&9SK=ei~H7=^FC6{JvG{PcNT5t@9=yC-H6K|LW0Z=M=%mQSX3|2^>- z{Uz=N7waR+@zIim-odoQA+K;hbYawK=ld)aA-JG;2=p^{)(ILytzw*Ij&bNoZ0hou z2%1<4v5OL#kJR}&>-C(ve6pjrqkV^KO@j{P&z zGEeUY=UO*juNyM=ikYtx9Y!dq6|#^vdwpU-9ahl5c1a0woQI(j2Qf%3te1MI@#l#x ziy>|@A!)<68oxs@lO88VN$l4vCT^P4U<;5fyg zEP2z~_Xh;_`R@JhPgY+Z?}?be^m@TjG+&xFCA(tsi715TUmjLsTK!hwe?>S7W^VUD z5K)37qri7Z)d_oC2*>)IUvG*E_CChKbcfUs_xYlWeAuUu+cbs6x(Ev%Q6l#V$3u0} zigiG%Lrre-gvWs$(MqfiEDTnclN}^u*!+}3Z7TdV)e1LDBTxVn`K&CYfiG1Bc(^nH00WXwoR+PPFYPHq|dfyvZp@#Q2@&-PZB7ZXIG3=jQLlMH_PRj&-L=&RWtqh653zX(mn({*;nUg{Fp7ZXST^&KzF$7 z4gVOpD={#=SUVmR^30j8xhr1!%<;4Ii&utP%)C|n1#8fpE=2dJ#(;_WtM13kngN2j z>h@)?Bow|doGaYXA3up;FBUq-c|0h+x4sKT{A?L2bct;EP}*wiEl6T?rM61**)xO@ zGuF@MrZ%rZb8en2qc6B$n$H@=Q0q(GP}w+Ots|6c+klDGts4wwM&Ce;QNuNX-=uY_ zUHIVWW*QR4EB|GSbgZw6Rd;&VVx*RVV){TN=&NTfKs7sKr|<<|<;1;hzZ*#J2~R`FMpek6 zu*`=_;^6X7qgQ6v@E0G8ue4C6Lc%*RWRjkO@wuzb4~VBb{NxcS89$x<#9~LznddOJ zCMVe-n$A3Rw8;m5HSHhP`M<>vyo2-0uzwB3EsXh`89JgAfK#jgVzY^^<93IU!b6l@ zb%(P1;vh=H*D<_B*nNefsQj#SRlmuH-DR2jIe^nAqq;SfqU;x$S&@nl`%2Nm{NnG;i_}Kd_5`Yk&xpD9c)oI< z%a_2Kjy~f_@Hm^U?PEJ6(77D!5xAkNrd^{X9j?0UL5?F+y~-$(9489y+jddWJ%`@J zw0jI|J!P7VhqdqYMh^}Xl6&Ti_sD0BhFmD$yR1Wfdoz$r7+-K%_1mm``|De(9E8*` z0uXx^|JDHgZz>>FD|@GZB`}Avu&brll|=OymS?GxTy^W`twrTkY8GCp+4LE&AfPF;0`G3 zI#5k(YB25!mUlzXMVf|30fsgGj03!`tV~NUTP~LFFxLI@O?#BZ#dWD>eFjrb!et*R zMcZ!HGtjHZI5lE4ueTrzRTjE;9W&P@bixhB(^tOa-rjg^N+Wgq%5GGfm(R?9gdp{< z>#fR!>~Pw&KEnauZVZdre4U=*_a~wK3-KP-qm+YRL{OQRU>~6fVZBdW-ZW3eDL>~P z&t!1XYuKt`rLs*N!^FzC^-TRv?8^GkNtEBubHI3{BR>mYLfZaBOWcl1;K2u-UvIq9Un#A|tzKIz)J^51g04^ch_Q39 z8;~@OfqspL^ZCZj85WvNpF=HaZ^S8hue1t3y}_u3~lu zeV%QZzIa~_2d`NOlnin zyt(D~mm&lK5ueC(Xt${dqBzh-ot_D7642Tl`_9S4bxoB`ui&-cfTNzQURNOv!gZ09 z>3Qp4e1V4FH=nV4tGJ`rzi(-+S~vAL#`DeE{N2+7`8Kf> zQVy{~xGWgFXT|w*tA0qBzwyzTZzODTY=pz$$lp@~F3?DVR=w@nm|nP{q>c zV!JQK{{s=P4&Uyd3^wg35%SLr){3SBgD}nwazWeK*L>do_a{R+{_l6tU$4LSIvR8( z2A0FEsa!M!T0^qIS)j#EF(oHzjg#kYcw!)c(~@!*#h(XjLG@h)Y8j}Z^hqaeDcE3( zNQ>g`v$q8rBUWwJ0v8;md#K@X`-6F+Q#kaxWO3I%YxHDv({INIdsvKp@xt-b6|u~1 zIFaJiOrA^`Q`d;(lNe$>*`5B`hhbn7dFsx@t~X;qc5__LftIi zYCP|0J*>Si%Vn!-q&H3|Hva9BW1x1Blc2SJ6co08PDdX5n9%EFvPwp$Xxnp&iCC9e z>&UJY^`f1k3MJ+?UZyNJy*Q7VAtkq*;kturXQzb|T%kQ4jU{3NnCH&$q`Hu`K9m*t zmP$u1a^`Ixd;@*k74qHi;I)$b3CL$uuk6dG#aRr^EDA>9{NkgsRL&gxd8}q{eYLn; zMsE4W{t|g(1pGM;Wljw|ojyY7VMKLC*CC0-e&(U_F{D8-n)MzdC(L>4$Gku3UkcD3$I(xw5hI>gH|&tp6H2fgea1) zj6sTFtw>DaD)2MN%oTuFmc1mgm7>v-nr3u-VB{Xx%U3aLe8mQ)h=Er6910(t-QI>R zT|Gf<(U#<#K(Bn3pL0z%NBSrL1V=ePTN4vGxbkuhBt{FmdfF56KMb1qkW)AllZ+a$vSii97@jXG7sRNndJtZ3thI}Wuw#HYnZ z?w*bUa7AJw&|^Df_}A>Jae;ug02gDdtF{kx)7PR(foiuT`BxJtPp3T{F$#BRwI^Us zg;bY}D-lP&9aJ5ljowNU2}i`r+JQ3FX`h;; ziI}-@xsp$$!JkY;veP{+x`~47<%e}i07_H~F+_}ELWj(4VqJkljJ_c{Y$6r($v~D{XOXN<=UI*j4AAdAZK-L=H0d8#ua;O9az?J@lfvv~B4rW2u^o1rvT21iWeQeW zR}?zn$b<=uf8+t{SVl`!L~pTmwp7|$;JArwywhVt`Bd+s@UJw zb4jzFAEdJU(02ncsjR4yeuwYG%IeCj2tH@iyUcRdc9%1*^#|DSy71MqwMV0K)X=n4 zDuZVVC#BEpdtH6ih+r?as;=rMmUK(DFaooO(Iqr4Cy&cd!wMe(AB_oeuSO3_kpW)` z?NL9S2et|Oa&NG)OeW}g}{GF^qyKK8o)=QoRAFU$}cw0+Z;HP+~6gLOvTqoD6a?$gIXxf!++*OSO#R(64(AtM7aLefeEOVg(I7Y2$W z&}sFPK~AuYPv#fTMnK^f5=QkkJ1|XjO75 z!+D&alBF>4+2nqjieJd$LJ&CeGV*5qQgGh_Eb7_K?1A6h_+@if*8G&OX}7%7A|3$i zN`=({ZWmZgD3*da;-HDm+a}eZmVHHzt+Q}KYI(52O#fEql)68jjBFCibsDa^!Gu^7 zcCsTCSvW=p^bpl|`8re~;GQ#0|JLQNiR$hVmV8M!a$8oqsYA_Zd_EsdV3#d?Ul^@F zGD}A(+7|O*$JMI-O2*!*E}&^B8q#W?xbTHvbX{E1q%!yV+v5v;(ALszuG-uf+{D@R zPS`Xz3AArZ??JBhtaJfz5889OUN-kz2iP!;$?{=UY>eYR+i9t>S%JX`ml=!gOQf<8 z*`7JXhClKE+RftwG67YaGKt^aA?h<$6^nTM{t0#sijH~Lr_s!fdGk}lUQ6bKYqAcv zXBwTxm^_H75f80`w30Q!OYjqjtgf5IsDoa_0}B2NYvs<12IXhoz)A5Hq?k{^lIPzf zL%Q>44yy7{h>G@Y#L^zXZ9ERc?9HMbpJhM1r_RHuy#G~5X}d8neFe!|68i7t{oigY z{C|uo{Zromn$!NHZO|F*fp_=nq`8)%gxtX%2dRM>2r+D8STYuE5pt+}lD4A;cyw8= zRzQo@7S`1_O59NL-UUDE?Ph&_P7Mjq8fW*3*DU`Do<^S6>-zBD=AZ9032askpr}1LkVmFIJHPvwo)UoIyeoMG9v;-^tR8D zd7|=W*iW;)cVQY+@jweg3inzne#2UGv z^?Le>mOYn!MA52rfURQ7mx$h{xk$^Iy*Z6@dArOjEY%>@H=~KIlDVMz_p6}5VyNJG zSJg%?Y|`-|g&OvKDP|V{##JUsOGUDg#bw}@j2$OMs(+FAEiSqB}+VKS>e=* zv~rx}rINkX=C^>z=wB&u?^GaoQ z|5{^tNnAM zKD(}!QLJUSo1@F5(C6mQ9{xwrJ8Ub@65d`FgTTx>#n?U(r_R6Ud0;3n));KdQ3^>d9&-LaA&aH^MS6JDMjSvs`pRjJ=uRv(J4pzWZ!egAE| zc|Y$jVdH#d*T9gYk)rX)H`3_J3@m}$!=R_uiX;sLV34e+qezs5YtWD$#N}&>qwC|k zL`tD7FbB~f%3{w~2TI~*8z<(8E1KbW-Sj>Q3W2`*&}}W%^D{)xGzS{1Xu_@Rl!^?o zOIs%XP@AU>N6K#^Swmqtkmc^)^HiPm>|B3rwjMpVn%N8Ku9P}<-L7bY4B(2A+H|#13P*12Wx#RRkM@%l4}?8cw%^9p6P0J@5qs`!D7ZV zN>Gb(*%IVscWlO_8!$7@<~lTQxg^u&8_f@W%NPWP%e7;M=0@=}5VgVGwo5k?0-Cg} zB@ITIm+(v2ac*qV(px>gVh%F3Y75>XS|uwAXu{{N{$`_1!yTP;S4PQTa;RyF%`u(K zR1x@&?^H+Y#K-PJ$}TM>teBFC%O1*WH9UDhJO*)UVwK1BnNQrsW(#`ObmdXP`>8=n zVx>+|X*ctdUP2(^yUk{hzXTOw!9-8U0dHxP)V`%5Q;p#T+zj^hB8!P=eOWv~sz6u8 zNQV$=PJiw|j7P_^0?HaMa8I&IDg51f&&yu+4T?`VMCkQG;^qPIz?}cW3a4|nLFIAj zb&_HQDf$zH?eaZ} zpdpH(=1=wZBAN3yqO(fi?DmbX3f#NDweM%{-JExx4}4(NN@k`YVc4t*rb4x#;7?+v zGB)TIfiPZ)N9nf1IX#G*B9X)P?YBm``70b?3 zlt*`~7htfn+S6#!TG}+X3^Pt#`B`gWby#bTy)(nWmc>Q)LRR(T>Rt@t8TC`+&-kuG zpULwVa`rvH3n+&tbX=JKbeN zr}ArHp5YirGL_|=V*Ih@b%*)LcdT|z3|1WaJqQCBsORDw6)vYP_(>8gT4C<;jF!4l zWLL%Txutr#uE`D)b#75jVXnvWCqUS;jYz1l!OtVNv_IUgk0S$d`wpii!$$fEimNfx zE}Zodx~ja;?wXb%A!Tj8eH2HKAT&ZdqePLwp5U{#ifN@8F2pvN7xCJ$1`mf@uDU{$ zUJpk8c!Pm1PY({%IG+hh`3_dQx%ect*)MR9A69d$6|1m*S9{;V*QT-=^fqCkIFk8XtRHldA$UB%2q<{; zh`c&;gqN^8&+(}W z{rWVutGM2YDd36d1$)o0vtsDM_uWTX$OXkcs6e!9imz)bF82TdYGN`rMCF+=U(TnM z(S`RmY7`9McezP4jVrPx^v7^pK|Xi-{g2xIZM>`BMV;*CMlkFS@+0vKAh-RT7rCDdXPT&NaI>=J?7zS6J>LFL zY$Irx%MDGMwIyE4^k+scujZyF6Y5xp<^n7^g=28xE$^Wv^g@y)-=%;Oi@8|PRE829iE5TKzg zgwj@qfD=NrR%Ku7O|*cm!;^;sJ zv(bP+1T=FYBky4n`v5M2FbtD#D2$#UGD@xWD>D#7j(Tt-AkY$q%|+Z)8yH|^yZyPM zR9c_oF24B%j7iHH2n(_!WtEih6#7(I8m#f!1%pBtTa^Biy{UA@j;dqFnI~j`JGO<; z4C@g!o0T6h=ZL2aod|qCbUsQ8R(bw+0LF}6v`z!Z%TBgZ zPkNCAyJbOEe0;Jo59h`FmU&`~0VPy;jb451qkmpllzm8cT8_xEBtl+;h04b)JnwnI zW}KIH=PLA-9dn&xCWeK|;;;b$$GB<6B6Aq`;={hgC-&#i9CWe+H_^&z18x%Fa-JCJRQtImX%!q*Nk`!b-;} zOe@ld(7!=aQb1nGeg$ie)?K$!?2AWOYyg?)Y5_goBHCP}>+WT5<g8Y zzzBy7PUBLl%Q=#*{`(L+DAkSPA7JRSxt8bJSs;0g>lNzr5QVhO4QvjHKW@}IE9xH#iK9}!Lm+kV%20y!WAS4k68AevyA2#C>4Ra)hVV- zftm8X6yz!|>9`*|D&7AA>(cZrVcibJcEiQH1<#Yk#9*m@lprt3`d>m*u~5De6~M<# zop1Mm5@ZzI<@GlFiDp|Dy|l(}M-#I8;Xr~}1;^w3JIgIq?5js*!`4@X3O*{OzytL^ z36lTzu5{g~=gAe<$Sf=g34p zYGSO2n|yR7Kp?SNoJo>0HbH+${i+BvIfu0J8x@g@L_i!)zvVmiK%HGveXt5)%W~$c zui-@YYUbPP{WJD2Y6I!iSZ1=n%075hOp0C#{*()m?)c!8+t^8p0w{O0*)0&bm&_lg zC}%_(vAxU&4`LnBvf#9Vz09C*_aIe6sZ%AY0;x<=2Z9655ool8Bl9@X6`1RTyNB#t zHKPv8YH%Ws%j_nY>yyPaRhRism6}qz-E`*Ww3cRF!>x-^WU+DHk}T&REJ?liVMBDk z$n4jz9sV>(glbQoXK+o?ZBU~6srbTbOLY!I-jKR*gU|?rR~DR9h+iq@S3Rp2ZHY-Q zyP82Wdm?Xk&B9w6{zOL|=hd^Q5tmIaUvB0jxqeaBs8MF(pf;VM0? zaoR^34T*uU13~b~(KjYfkb$$dBZaIMkihrNmxVpNYENODy=fC&6WknHMyZ*JqTJbZ z=660i!dqW^VB+^aXLdvborvyng-a`Sy~)vzZEfJ&F#BADK>%GETI0x2-7~R4r%Wr4 ze{GVRb2i`O>lxevy_EZ}-_s`#r5K`;*2LUt8jm+>UowRqN9(v;~tW3NC3n z-ZH^d?qn8v)97F7RUkd7ZBNue9E&$FnUN*a8X7qw;-v9U<-ln3{CD1<&|KC2x z{HN~yzYLiC`?mnpy9`4JEA32ewv4faC=gNzgUWJJ7BbxK@;|*xecf@f&xe9A!@hs^ z<$pPN3j|Q3IOXj9SEDF!|0kNRQ+;sKijR$pO}Y>Rxg1Z}2~cgGn#7DH4$){H>>vrM z5m|Y^h2<)y`r#P|%87Kbv%>9x8buT&4E_Of(U5dK^8?ic8*d%`5#03V1bwEn z%lNuq%ra+Rq}l3ihgcD=4wKXLIYwT{n6Mr|jiN7X$g26-v~7=h=Vp!LE5gZY4?aa7U9)>?y7g=SQQO^otU z921+CA;Y=5h?O;D5d^2MvfmVMDp(1V8UejhOWeeFA{ylO4$cF=i-;5aFUAuK?F{ma zucpmb^>95Xk+B*Z+GC;{_pM`GG%L-{kvky<4w;3=TC{B|zbhuErx!4@oN4PuEQ}ofFyucGp#kf?3 zNQk!P-P330NbwvO1Vu3!`XjLKN%H~0NNa<4~2{*Lmy|*7V@_ja+MIunF8o{Cwl3vKuz~ys9!^rwgAn4sz(5oI+9JeIlgNgy%NT!r zPRLc3Po4xD8SLfH^j0f(^q#llsQZ?YC>ab~s<;oY{ggAE+iW3UD$GiU>aIagWhAnQigFR!5dMa70w z9GWUH5Eb48h5X6bPxqJVe}V%v#c#^q(i^+~(zfD1w7o67SenQtyaTUWuIBMrV`me> z)5tf0<24&-?`<)AJs*L~iNaV6#sm!V9T_cW5}2+`i|$Yys~v1MKMKIL#Epeh$(yfd zI=pfHme&)dRi##dp;q6*C|2Vk60rdL<4!65r? zzvxQ22UXw^kEgfrZRSoo>`j9en|@_ox#qPzj))@O^fg5k6C?UQHu}JmeaR(8L7Lct zXbvNbmsKGJ3d(f|J&u>m%!Wxlon)xJq7P7S;3v|JoIPB{Y2`VV8Ox8hP!uN8O^Jt% z&Z#i(gWC&XUIne?Zx5HxY$rtgqRHfVCk#tu^^!uH!`gE>V&lZSaXr~zUSRA%PTuc^ z6~J`}1o&L>Eozd#9!|+l9m-D10?Y$Jt+`Dk?!sos9y2mwI}YI~=urqLZaK9eMn)); z;zMY@^*$jg35)R>Pes`|sWLDZBlv=Tit_y*)%n+zdxc1l&Vhex=c)datoo1V^gpMo z&;~Hx29g=bkV3$~YnoAvY<~@2^`ZPq9WgTfm5APG{pW5Ksx^fM-N(Z$gF{~~>6v2d zrDD7EhC=x;>E*;VJFh>7tK2g<<(E8LYi$cHJka+u^LUD*4EyUK_4IFSuqmBdYW zm4^#h8rlmT8rn+?nwN(>bae=}3sahp5Ao=8H=nmgR7T0y=mUR0G@(0R-~?95DhHj* zvS8iIuppf$ng|L~^?h6szObe_3e^lbmr+)1>uXXEr+8>XmpvpHIdJ1yqv(TsN!;Lq z=u9%9@T2LY^-&2Y1|*npl>X?lEVBBwP^cARFH?hIm}J7&LEZPsM=?B0?6)-2QGUm1 zw{Fp5l#o;nG?H`;(!F#Ikx;_c2S4II;TOGdgBOu-K_Ag@QS!+cB3Ot(S!V(B_7b5D;RQHhV*QjV&Q)>6vp?v^L8sE|z2M^Q4s}E>=bhSE z<|(xG3?dIzX1R&n9h818Oo|2>l;Z+?jt9?%wQ*)RQN6B}$yldNr+dPIgae*nKQrC! zMDvpH=N;S>h(SlG6bmHm1sOf-xL7l+8{C5uqeZ-t%za4ie|dKIt+3%V2a#Bcu4S-f zebs$%F^IoEF)?2_2Lm4Tb%&4H4@NNV$OXFLzbo$amnp;7|Aa z1}-RNC%ttZ zbzG|1&uL`jQdL`ndv^i%vjzqV9IC?}$2}4f7Cx`6n_O6q8+02TCWI7o2W}EH@8rW! z`emko_gxvkJkvEtUrAU6AN4Jj&7-COt3uZN~A z_t6?{FnKYB3eVHBCAuUrHHBrSah?&bi3>38OQ6@tL*CJ5oNgPEPMPFe7mWo38A`4F z@f@IYOQ{}v(Xe;#&PG%n106LY0P?NQgu9SXZO}G{EGA7t-8Z1%Gc%LmLsMm>c0J>w z$};}W1>4LNj@qKtJjRUZ-TLbX?d0hg z9d@iIwv#8eZ5th19oy{Kwr$%Td^zXb`&Hc^=l!w%>|M2M)vCGI8gtD##+Z$w8!0O( z(+bfX|FZI?R03x$g==aQ!{C`M^C(p!h&WZ_G5-lVG5T51kyy%dc&=SWe>sr%XZB`j zH+`d+wzN#0h3H}d#KD}9j1@aK_vfdTXi02e+l7&aK9UX;#oF zx)b%wfool1FB@h)I$mNRI?P~bYBt(j&)B>aBY2l{))S3ZEA0sH1n#cIQk_!a1aAf1 zA1}K@4|72VGuPR?bc>%HnH?qjVYTcv_dmcNQMI}3z7ZPnXO`m3cbrX#& z0bOjjtv0WkCBcNlFiml8ifb-IDW_d=Ps)VTbmer^@5X%p=kW|;J;>db1mJsr7@~y z>av=}gLIMIuaexJ3m$9Ls*y^d0904H>f7s#ylzco2G0IhvV-^{%%_C&7PUowL056s zLuTi_??L=BeLNgnV{;e@KPtuKbIB?fn`Hx!DiZo8N9E-uDQU&4P3`^UYl_3c$j z2lp)%E*D>1gxn^udux6JZ2^8kl`SU!B(n|WxxH6vs6|YHvt}L;v)Zr7X+Qt)Y?j=n z45cbIg_j{#dqEV!L+r|0htE~kAIh~7;)!d+4scj+%(S7L(iyRbXo))jC~5pqtL&;Z zf2Ucrb6<{e#g03}jy2>F&+a>HjxV@0D&^s}Ae6Q~EYWExCP92CSRSw+bFAyfL|WpE zI3RFk6u@tCIME4kwPS9uTd#b!+mSCh*Byyht9M@>A83bmWtwLUo(H@tnS6U7qY&Q+ z{YNEVYKjSDcz}HmPlnSc7vG3%W}e?5c#v98mrjv>;M5iD0!sk!20Yn=RHq76Yf5zn z2f)0dboitZoZI_JJ7Q+|x=*TlVOs2XxK~zMd1VrHh7`QLuvG0dj{9_?tdP+eKavtE z$7F+9h@W2d0kzH=BP>+}+gM%^6Q`KkWy!nb4BX@KrZPgh6Y=lQBq^0D1kDjg(A+kN zx>H6jeBza+rKB+)emX{;+^0w`WS83|?{0BtOTS{YGS*%k`o?4e$qgyng^Aq1;b>Wp9fg}E`I+V<8)^04C(-sn^Cyuk`-%PFYp{#QSL za9!#vm?YH~qR?=64%5QhaHDNQ*WhsCjpIA2sFpAE4grqQk7I zS3>pjD)UHBTfjwCozy=l0!D}~(#~Q?7tYIZVE4_2^aagZ(_4t*uq{KdSG(FRYAvFz z;czHdUG68jDpURCP)F|r96N!hf~weL>X!y=Ct|=4t=e-k8F` z#@2@7|Ck(SeaYT_-Q0aB=H^+`xnh26Rj&aeH0la61_UHy!5?=YA8V}wP`<-aAVzeJ zz+}beJ@gQ7$2#!hv-nvjMj>;zue^6UZt(rPTY4h?s*0o6lI*sIVUszKC5!BfVhF(+ z`bB0Kz_t(-Y3SjQ+1N1;4=`bqYe$Kp2>L=+wqX6juX13fF!)XjprTyw<%45wVKhLh z3vxH2;1&I9_4`A~V5r^8VnSJ=uPKE3L|L&LJ%4^FAo6i&64!8&MarHYN&=m1h3RE>?B408o z)ZAyGUzoRUzbp99J;1+0b1&Y}TkTj526tn(bGfMI%kbxea962@d)w_cT@!xv%%|!3 z)bBY%2CJcT*zxtwrUH8?@6X$;%sm<^YUPej^xC_LeFgP@&6HF9V)D!C**&ZK$AOa= z(+m4(m}67l;B0Z4G)%aYEX|c3pBl;QrjV>e>yO2(2Uwhdt{9d_^A4t<0XQp`u|>1l zne70-;ov?YnDbdFc%0U8<8Ss|Phx_n)zg_p%6Aw^37ubL7lw$Y1;bUWV9U}Rb=`%m zxb>3&gELIkyooY<5B!o^=u&&=X`u-wKby_%k`0%;PH_-Mb;-*B>`mB5h#7{NRf1)h zPf@tv5JljmVR-IcszV;9q=w(s$OskeJEsWG4tqjh0bfY!1b&f)m7{i&-WCoWS8!2=VB{Ly@(6Hs20ZPwr0} zu2%<>QNHhvSKs^;-*yI}IDH)UusMC4_agAST4%Blv8C$c;RME5x#i`52TC#kln;UyPqEY?<-mKv{g*TolR8>34@j5#tyGXNR1 z)YCPZ>a0NS{Mfi9Aa@RIjpd4bDBE(gffTx!T@_5d9R$pw>&}S1u)2=IU9FDdUC5s* zUZEVA)b{cNwEfDNA(gw|@wvu8M<{AHd@kkB-*QU1kfjGi#Q=Gc2ZYJ4DsNGQbbvh| zW>@{+ayQ@rJAwQR1aQE4^B9_6-X7hyq$OII4vniKAG^A|B{VGJ-?4n$9vm(V->~QQAVO^wXv+eitkv@K5!DPR9ESoS3-DBCd@D`9xXHpk!PAB4XWtSD< zSbzT94tx2~~xtDxt=77w=Z#bWST@QY~xEM)sQQCf-iu ze2Bq-^EcS@IgjHbm)?$(-FAIb{V0#~7>k|=@$>=OHmNdL$a_d74!_|ia--&1fx-pK zU--w3g~2`pk3N7kvT}-A&}wL`nmK0Ps9kQ50y~J&28ngN$@l zK&Fb_Tto&*%cMjBc?~kSvcx2v*K(#^#O}xYYWYCZu?|})j+1Hs>FfCflcDgb^E=T* z?$hU6Pq%G&#-Zw1n0)4GTR__#lPZ(7o5`_gbDw=a$oI2W2i3HF1eN~xy5;5<|B=b} zetvpPCq4YetRby9u@iSFLlSDh902DKJ!=no{q_?y&Ly)l8I>j$SM8#g*J@=gptJ7c zJ6pW=9?tj!F35sT`ayiK*S}0KdQenfD=^knF86P~3_U$E5^*&HT%vO5D1@>j8$U`f z!`f~!bGyOJUZRkk+L2D(C-p&aX9w$vXF8JL&u{_86H6~>-?~|6+NK+EiU}Vf*E>R_ z)crG3m1}PEWY$^Wy8F4T1H#E@L0Tu|Z5!16&^IR^w5&u@`=y!e{aeB#v&oc{ZbkEd z=qx;gEX$ov0y^XzC=j#l|jG{n>p!;aR>q`cu7$%kGNP zvZLo#;e1fShDQ<~M4{P62;2F--9~yd0l{!GDZHH-CJXC*V?qlUQpesMB)Rzu<;#HF z$U+ottg$9RAuSXl1rE^xIGH4EE>`%rKajqGv442S6UjUY^5R2E-ApB}5eeeIds0xN zEmMl@(!%`>#D;Qe4chFb2XB)R^1S)&=RtXo_87x3xQhwL@cV>M5#!cawX7}}&Eq>U z^$g{_FzZQrpX|6JC6hIUK-T9KU?eol24Vi6%}l!1@Z|tJ6f!{nq#JQ6k+1(d@u%H| zjg})mcgp9x=W~FJ+Ed&j_dsc=<@}tdytpsa+=~E2f~O7eQe(G0khWN!Y5tnLy-moN zkCW*IDf?J7J|Np*(Y9Au@?kD12ayNHqV|Wnjoy!lP{64`XF$O-6Py}leVp1oO6yZz z!-nW(PG|nw@Z(1C=@>~YbR_rCuXg4P7o}@pvFs z|KFqQe{f8+0?>9&5XBpP$lg$9mWsB47D`w;!_I6Q3LNc9eR1o);{|nC!KX^-UC0P}Q{X~G} zPFXM_!V=JE=-IHZJ0O~uj$<4HzPKh;XrTxGlvdlx4i}riDqpOGQ!5bS@pmH>g7`(2 zI@C^yFjIOZU%<-wla{wo^UGMqQ6I$RK-MD3ZK}St9DJTTN#7$y_=@~>>u4Ya3cs4B z)rswaX6N#Q2nzHKpHLpp=deH`KlQvvz(A}9+a?gRxn4DBlmzyr-iT$NZVdgLFpVbm zDIgJJd509)TPR5sRjriz32IL5Lno!?ZLaT~cqumVcSOx~F<)<6-1rsqkL1zlLMJFC z@=becNsM;kOP7s|wPrZkVZDnO%-BK2<4&zlf)U%U`d`a$T{|bZ1Gj7iuC}KOCK+O1 z+Bt#K(-lP^j|$vz+DSswKS9n66U65yb(t})c#-!Hj1_M@`_Y^<)kN$u0Y587&YoCt z{;^^_7Tc9M1>7xUhM*@F(|FG&1xe$LwK12(!t;p+W}8hUv7%f4EaDi)S6Uu2r3_}1 zB#gF9Kd5;n{4c1(Dc3}^@CD0;zd-N*4s|&G6EypOej&uB)4LUPQ z$dL7H15gQ`Pmmi^3O)m020>Y!e5HdPvi&P(iv4%$D_-INJH!(6mqvP>7Z^g zo&2L=7w#u({b!8t$Lj|K7zb%-&jwxvYO0K;4tUZZ=P-4#d)bvRg#o9WOM)Eu%T?XW zWR;CYQ@sb<=#-^uo-2(>i~^q41hMANxwoY5u|K8}{KnjS5WfzNGY>76Q&trxSPX8| zr{(s{TuVgOv`VQ$duF8^qHTXLxL`OErxKpmgDA6MSRqZG#%s3o$&*Or1r$RH2?w)i zVb@!J6S5EB`kN;gT{Y5AD)hT_Qvc`|Pxj0m{tG&hx3ViB6dtN|lckA&k?UfmRb4bn z#x@C3P&rlhBCnxqbkh6N2YwOc($w1MUiW_Z@JJ7i;QNUo|l2dAjw*xU9F>4F{9V8AUCgdt*@Mz-t0pgB{P-y*aaZJvHu5OcW zkDJ0?$Poh*Tmw;{sK*)emqSz^9QN6?FyIMx4s3Xh39|((!Jjh^s|9JH8_^7sKiq@$ zR1WmPa2T75_Kb4~p0c}coQl|i`GR5sel^hfFCLi;8?==*tWr_{>VWi;>wf!ht+8rK z3a-Vk$Wi+urvC4d^B;-s|8L}gtPC9gPt^S7gVR@DXgmUpNK>K1`h$_eoxvJVks2Wz zq=@EA3icY>fh4F5*hYdzf+i4Ao;KQcbhYJ0L>v5Zu;<(6FI6ndE2SGNJ+;=_oZ@{a zy={{_+*#9d-!-S!U9Y&00H&=+ou0a{x16y8P+$YMeqhVc;4EN}LwL-w-R|Ns1d==1 z7@NO)Isb$yDfOGvlEslV6@}F+^&8U~+D5q1hsiFH$_j{(h>7o11_vf(Pm;$|W!;#r<;;9nY(kdi@3AE`l3x9wKAa-uqggtid zUkT{!+>!}MK%#`5ig+Z!XKps01(TpPCj9dP21u3arzO*J*WpO$VlXVgx`!*()fR7}NbHd{Rc07A2^RE|Wa zum2gDNMFw%sZ8gUPB68+B>-lZkKD1f0u$EQTZI#jiiJ=;SumxRl}hC&Gn5QyJ%3`V z*7cRzv3OEp{#>pvt?}nef!8??Ev<>?%Yi?t#a7%Qdo-le?$wnF9$QW+wd4LrhgoGJ zP}d`oE!8d7ElZNsO@kk0(!4^FanaO+bJ_F@DMzr|AduH$81(t8TM5<(g=+vBw88`X zYzN*7c?(M*mm4F<=f42CxS~(sIS0KL{tn5%0G+T*$YF2h9_KKa3Pk>)6g}hL1qF@4|)QUF;4Rr+Wxeq?U z7*S1+P}3B2>!6Mp9K;wHJOv zEd&R~rY-E-By`rf0=^UqF@#qXV%y4-bxqoa&^8Bn^O`;ak6B=S%NX?0H3K*ZE{dFK zBZNhK)%si&T%tCbb5);b!V6~2GVreS((4fcyh}u|cWs2Ixkr?D1sYrFjItuO8y!-Y z>KcTvErpn(aFjB4U%ibECsWsxblwcPS<|C3?}15d5m^2*%a%GiH`_hnlLHNPp2S9D zR9*ZFHzxxc)9btcC|k+y-JCwv4=N^VM3Z_w^6eqC5?~;=*Dt*BgWrN2{_f^Aez8>Y zqWaMO7T~OK43dF+TqqJ`agzFM-_EV!*cnJwrAwUQh=ufd{lUT6A!TI)Js#yCgA}Js z3wu}|slh*my4k^pEuauVnEx>S1<1DDA?aWlNbJ!3m1egwT=@L)Vb95~Z6Uo}@G=K& z$gZKlCt>ZtB_UbhqNw78Npb{n{n23=sHX+N#8=0w$gds2eMzo~{GU+mevyQCn}H9Z zq{`&Rb@PHFz*bPyM0BUKsZZ%j2eK}&ncGpdb0e<4tg5;I z`OCq1iEaOY{E*tVh2*Ecb_M?w>=s8!O@`K5iH3FK_78@{LRHU%Hm%P=d&c!|M!DXD zWgxdB^T#x|BjpM3lOwe&%WA&$cJYDknwy-Wm>zUpbLa8hnB;k`zUWYrp|JbzKjLEl zvn#Hf9nyxvj?*6o_MG~f#-ATt4xLM88y8%F;+h=1i{cvEe+&H?-`^MQ8PES~U8TQ5 z7SkUJ5(}NnzdIaK6-`}KKU$~a0DCz&LomgRbXrQE@7oW5R@h9*+-N}_e?6Aho|Hq+Y_3f}(@i@puNC&%3nruuxgCuE2IM7a%@V*RjIF)J zZ_5H3`}W`YE}2i4j`sLeQ=~NPfuc{Ej`rkKZ6y6mpo@JY*9xHO;0bLRguR8%?qbjP zS*o)xcy61IdY+HEKK`RIQp>EfS@DE}ZbP<^r^JamsZ*|SL%A(d%2nxvi0)!XZDqi_ zV-0YL4TR)%xd9Vc0zBRTU2aIWtgw;irXTMz&UaCLB6SG1zqPw13K(F=-srYJfAiN> z#=PNLc!-b7*=|4IrA_~s3-U_su75*ocZ+dd8MyN*(CH9#7TCdF8IUt468NvE=*q2q zi@@5&WF746#510nMVGDiW&wONk26bDy+#r9n=)5K9 zSz_zS_oW2gQu6r>5R=(;13IaGy=UauVt?*%t_}FUfLv}6^Cbj}euIb=!<5byZ%r>) zqMw!vvxw`)oaU{#l{1Y}aDzCb1gZQ{-zeJ&fQ44zpkwAxsaECDJ-L+w*^fUux`NKw zxAo_@WZH)A%z)YsNEq55q8g*pqD?xwgSIteV*?iL9Ra?v-48DAn7;jh>cPTt$g29Wvwr3Fj%+hqLH|+w@OMDx0>HMYO-tmj&gO{g>Vb_$k!(Xbx9yZ$Hz--9q;ZV6R(W-FJnHFKa8AvhT%26FrN$!pl8jl` zMHW^TtI0&GudPTP%Ow1IuCD_N8VXnDX7>SCdRnbjP9DYsZ6?qssEz$$2dpSBN|qSB zM5VXIi`hGy#{dgQ*9I&`Ge=kF2_vR_y^C3AQ@3<*g$2ZKUCjlU<&HINI3fhPw()%P8Xkmr#{|@Liq-Lt6*BONXTo*(Z`<*5=~qdB8>? z%Lga7x6Pt_sLL2o`(Nvc$oEc{e#WR!&&-eZ?xoF>%WmlYY8fyALlJu3i!AF)uxN|l zn1#)M|MCcii8gLlb$6?`WFqyabCFqykRSU&XI$VmAitWz{J4}!9D^`hS zD)$u*n^Y#AJ@zV#O;@aJ?%R9Z3rco@?zq^j%r;@KM=9%ShdARC9s3JBtF;Abb2a{X zOW%I37&5SdMKsE^2s0%~xU+=jK*0+d7s&R>e2G76+q`h)B&|=IDIhbKnTkaV(1g~S1>xznqNw7gJ0BgW=AnJYo*pCJ4L6;qZj@W6=-y@n;E`y z7NQuBdKK@Y{C5aHD+Tw?^)%S2;smSFj~#a{b&GP zk0h6Cc5th?P%F^2cx21y;M$h4=o!(0-@MK6mYwgS=g5h)wWh6HEH%c5|5t~rHr=G4 z{?f0x(v0*XRcVje?9qw+uq`$mCMudy;S3qg83|OZH5GS_#L;_mt{5W!(xkuc}{O z;(W2goM~#cb?G36vdRjZbWGh2>-n-SmBoeE7^5mxd@pM!?|_@40|q)Y1Jg}-v*loB zxIpHJ%^?T5NOm7dFE2HpPayN+>TZi29@MXMV;_u$hfWOAh;bUHb7t~|><>iBzsluVSiE z;{_7Cr4kx0QtifvaT%Ny3sMtGDQh4<&#zl`(zjA^V2(OUcFAw`K`?Q-4l4Uv^&xcX z?_U+<_wxA&Edh%1MlCKT6|$?}qtJ$+N)1ei_yNVzZ`78wX9c;M3EgOvR5|(TR>KqO zA)KoCQjnr#4!HO!17-dwhUa0VLEkoYHC9!_T)+&=8To&^C4H$W356H_OrB&C!8p@K z^$<{YRNh}yiYiA-#^aTUz`3n=TQE1zR$WVjs&p_>t!wIUJTg9|u<(1tL3PTUOGu*9z?FD| zNKLv-lhw1o;C8}n^3)fD=$zqMZFYpKGnF@GmO$H_*|Y)HA7b-t*UVn@BN3_QNBaW! z07L`1vXiPVm_MzEDs?EkNt$$m!|a_H?N6w9s z;cqNBXU`V2B^K;TvAOekNg1`kGeVV+j*p2Yth1-dt zv_$jb_v2uUY_!J;tJ0t5>pb8H|-9i^t(=2 zJ@&OzT_YE2YO!#C?du{$_v-jG|26+?4jw{38n@U)Dxw%POL{ol^pW=aNz5bD=y78t zX`Q8L=GpHV~At^9QR6m|V1C+&Wj)|adjG@l0$f_F<9u{8niZztuuK~rJ+|s@X zW1BFxXQvqzlWC_moe(HR53$(8a&gxM7T4WZ7?ttlvbEr~WqwLKOQ{qyi0xJ?w zjdl(Yl{d4KfW{}Jn0dCB_~d=MvxTyrS!}sRq3E6F6=moSecr%*jr+oM-)C?@l8gJGJDOw;DlPM#cG2;S7#TegWD!a`JpF4A`&!-ib! z$d!D4m9X}l;4M~^(;&B!hs~yM_ECT=2$+%>(Tgm9#voqv?ySI?!NEC8p6+;f zi{vUFLSy5JgY2@=X-7{bV01xwLfCudFT?f>tNnWpIdMyU@8jC`dfZrXcy!_nudLyP zO^h4es9*5>fYoW>YSe(NYXYaAggLOGcQ0dI=bQ59e9<;FSBp^D`4gn&dp0IN*@H9W z`eqCu6WY1H0m`%-n;_{Dci?~#IMI)3srsDA%{n}rbH4x^dF+Q01@~=#~ygiJ- zdhJ9gefN=5E3PiUZJ(LeUf-~$iTPfzUt-rjR$jz82hZX8(;QcEp}KRPc48C2%V?7p zBaOoFXiJOKjPYbk3)h@@Z%d2VocQ$wp*iuvmKM1=F;oj&m)A6Jll`AJ&6?AzePRmx z&U{8((tU&b7wwjag#cQ3zPg|yzonzpNldyVziwcU*3!m2qQs0AP^GQruUSr4l$R(! zKSG4Y4FHAc=GrPd4T!d$s2q<4u%{1QU7k{Vj^8oT^0J<2%uWWgzj@g(GQq>*y`)0ZKqxD$v&{^&H)Q89 zYv@v~>%s_v7K6rAK%B{NfmF3R=_j>NZIg8(c8n^4LTz|T)MpmjH=sK;d zSBS?MeqzyMUJ^W`G*D|rJd2f>+Da>Kunap<*eMovT@F|uEAt0FHT`0R9jpNVB$Px_%8epNVx z8~Q@8v5;Qyl#^1sv96%Jy?*Wp*-c)i!Hz3GEH4@!6e47;^SwYt8jStdCp<}jnZzeD z2a_A3+WbGfFEo{ad9PQRXX?=ub#)Cj*gSoztYj3(ZlP2U$}6X$PzmqtUS)eNg`-Q1 z&JOTMCRn$eedrC??u^?!wC)M7y7uH9K$i(O}u6&F04hV8=KR3s7yr&dC))Mp^IYLK0e5yUUGE&iw#QU}hVu)1{$mKqAEr$q&* z#rq#V#ck!wMk%JUlq2wU7?A43?Ebz@j@Ug!bM%(9*HAf$Dxz}q){tI^DoY+cIefh> zD^iqmr?pw0QyD&n9co01%v205h;+dYy&QxQ&0jlu>xt`6y~%A`=nHjMpqPXIaR{cR$_04+6g2t?CAf-Equa+42l4(#l(pI8A&&fFmH??4<=xc`%KV#!;iH#9qz$!=CuIUGAT%NF>nbC zNDL%D5p;y-s3qty0V;TbeUAzZy44@S46FA6P=So)9^O6qjBPm++)|ZPXvWETeVu&{ zy9&=cd&;#*`5-I5+s6j?=2KG1`;Oy@n1v3ZCe+H2Uj?v;WNvlE%oKh^yd!9BmikU0 zJt7L1@CPFZW-3drLi5`6UP6&K(iB7bQt{TB-~RIBvlN{Nfs`!DxlSlfujUygMbxsQ zzo6nVC}n4>T+v)hodk$n7lY9H@;;oDVAEt;wOyo#n(@=|$mQhL* zaD%k$M6Mp96Z%$f*z-3U#{deCn~AIHz3R|f4aV!4v+3hw^1-p|=aToT>`HLV$ObO@ z6bt8@gX>0yrv~+vnv?XDo;(q5LHzz!9e#=YeXJJ|20E?rxou3@xGy>9R$T6BL_)=K+L0FUzZ#?|smm$t-Y2Bja8VR*zmfpWAofdDzb&Ba2+8Mpf6 z&BnsN8-d8cSLL}RJa!VtJt8T-Gvm^nn;Y>gFo?6iItabR_oGzJreX_i4!fvceMNKT z{@W9>Aqox{DM?c$R+?&q`syT3Gkc|U0A2APt$oggtWxySQswF7XQM?a98)DUkN4%4 zw3Q55JG#YfOs9(~SSfB&H!bm4*E8iNpzkhT@X>h2MAxELN`+^m((9DQGywd?&=~%a zZ#kW!lsnPKrGy{08BEbMfN03>SKe``(hZ0NeY z)&=dAYX!PxHbSKU4jZNOjOm&b{Hq3>nu*^%+;K3+Y+&1XH8VLSkW`C1bCBEW$Z@CR z!^g%cj?1CQGHm2Bs)z$|qf3#L(65>(Wlf{pQG9D-fW2)eq5xk3IQPHjbY3Wb&!x)F zj8j;0lZCIVbzRt1Yjs_qLdAvF#f8v%c_ju&qk=#+Y?wvDq>qb$LA<{&HJssHLzpCe z3Ap!lvPchArurX%VLGQakF*53HCSziT$Ebbg1Vf((_Z~@k7HwOmSx-1x1SQBXZAoF zKgAZ*!yaH9-3RGy)MN@7lJjN6c}Hz_QJ6RPzueLp6*m!}pZK!NjC}L;W~`J61*#mF~l~h=D(i zoIMK4w@Ka9^lQc=>UV(@e9a86-C>_$H+a%qC}SXIxstOf32WmNft5ptSMd# zag0%t@FL;^-*fu)vCUw%)8D=2H^VT?aS4L-QgLbsF^4z)n(#{?yHIn|f(GT_2&b9v|vok9Mq?JVbNnn^ro5zat7<_g@nFSsjSlK}2<37&n#j3s(U3 zpQrkM5LMZaj!NWP{H&IF0k&9M`l@s4e3pIZ`y}8+;H22mujlm&?Tk3@qMFb*Kjqgp zzkLrd8BWNUUkZstclqq{= zD?u($gMpBT7%&?>nH3T`PEoBYT;u^REH%K>MSGJ{`o<(|i45AKy#;ZHOgHz>$NRp^Z0NFeOr< z`i$@d%yN(Re#fhta_K;MZ&S?h$Kw5q)^IQCW4aOk2#kBXr<`zl==mcyAHZzXDn}4d zJmJJ@cXxte46Tz#+Qpz<%!+&e3*Y=y&*KZ{YB{Bp?!qfr$H@u_kpcUXss>;?U>-A? z7c}Q$e&O(cH*3vzFCi2oDYWgACe>eei2*d)tF&1uUS z<-qOexnfv1p}l_V>d$-o#wGD^kDAVsdL^?P0LM%L~i|vY}xnjt&wB)fO za-d!l$ipbkj*H7{F`|#k?8Y9@Lzw)%h%8X|BfBT}o28N&HY|L%ihQLCwHk%W@GC=F zk`pzBDfQKl@!t;CP03kNP@p!mtT3vxCj$4m`5vppCCoRSfT(z zPxA^1?l1p$dLvy{6*UUn&W4}d^ra~}{iM}GW3zSpHp7#$E+*z;#n{t$mMw$6qp}>_ zWYn5W-MCyKtOk$`dD3R$%&y&R$NgWqv9dpLvLN=)b{lO!y=%_s8z3j|g&Bc=Brig# z?KcOi>Q1ho;44DkM8baJ2u4N!0gzdg$~B|_u@!hHi@?bG3ud>OWs6&MTwkSW@}!8r z@nec{`*0z{IH3Dd8#v+u-Y!niS6WNCr~lLP0~tGg z{d~z36(C6w1@t>)#y>!mJwatax z%+(M31pKtql33fJH{tU}P`lV7BNu8eb&()G&m}a(h1#E$Q3};r2ZRhPtE}!`(a~SK zQqq07+q&TsG0W!lAM_G__M3gxyq`RBa13&%n83&3JUYB^OcqXt#G_bct$AUup6VI{ z$|kIuW^}1o7$57J)ID*by>1n2X?iUcrpOOl4TEvm~ZuH`!% zvH+MX*#aaq%yt`*TS2IC1~oq_9Pyz52kbkIk>ZWTK`))_FSxdvQ4iA{zU|yVFC=kn zS*>{tF_<0N{mI#^;LvS>5XW%!Ic`mMn~I7p0IX6($%_Z7`14 zt~I7tJ;N|w?B+Av2A$8klmo}qzc90^rbyh70;u(lPq^rlK??8Vb&#I4gSsX{9Dj-9 zT|Oddwyt{lEPsc2WYuX`Zccr_Z-sts;ctOQ=2&{_)`D4~sXGvj+V%X}i}zgqNu&(k2{f!c?|9Fe{Ydj+clHx5`4s@c=oS>`ZL;~WtP<3ee6e%+Ubg|wq*zjw&ODgSdiev(B)Y#Q zD5^W%=I$)RZq)RIQo4kSIc#sP$^v`su;(o~#e248ZldFnUqao(MH$KGUZ@5wuiUTB zqO5ww25S!zsfgsnUGc(IRyLXS*z*EV;@8A)1J+?Pbw0tLd?kZOI4LtF_FroH=F#q< z+ns^ey>o1@cO`4qEy4OP1avv&DO35kD<19$NzD$+5NgyRhtV6#Tc7p-KAYkp!k>rw zgTe@0x@Lh$G>EY#G#mL|GKDkCHYp);QXA8&5QXfBfvDCHk&4q*WkVR}ae@b3%fogF zgz)ifqJ&9Zgd3GAL9&Bkb|AxkoVyT3dsl*6 z*0)Tx9w>?S*s5!*P%NS&vlJpL%4Heww67^AOE8yIY~w2A(TaHrMDi@}UFRSoEDKnr zblcwFka=Q$SF>=(BaV>4%!&fI`qk|s?3whULKFH$xPUSNHamV}1I%_Qso+nMUR(@C zy=T2}IdIt0Xu%9X84$WCY6fXhy>WhOV+5K@%&^H(1k1cXw(!sE)I11jv^bw%Z|-mj z+onT$gs3S*g*wE(GXvW=Qo`Pb2x?yCMEQSOpvCerj<##<08s;kL!>dmX@=kC4bE3d zu=;R!xwxbm{OS8|uKDc2&oLXPsZW{g%0ip_S={t8VL!3P(sbBrcxwZI==3Yv%%KzL zYXl;5!M59{ z`A=sUlBn8Ks>h}$+Rgn37DVD*NUtY}JyL49mQEofs9D+N>X`+u9;*3SkNEav?@vnF z1=KpUPiODusG!xNSsfWB7#nyd^PFS` z5^AU?%?~t2Aa)FsRi)*qK@TFHD*3iZ*0vIDNWR9%Q42F*;v!*XFcn^TzO>8m7|)gA zO+LBPH{i~zWwkdHdEr&K+_MyM_mXBvy+s4qUK}yM?Kv|R{T_O62uF$TBC%_x8TCezh%ilD^E|qFtm+K&UZG2L-#ET2XIKA!28;IRkK2BU{FF?f|7p z9?6(`f65#Xud)}pQrYj-w3(Wd;!Ipp34z^uu%+wtsyMd4-i}4*uAki0 zK<}(}JrhiM*I?_5eY02o3LfUed~sDNA)A)kvzz0`#;@$B`QHq5_9Ek&+P0x2wjiLO zPEDS0tUj|X#LcvEo_YqNVKg*<@W^%(xdCm@jE$Z4K7=T`PXAcAgH>JlryL|O)hQT_ zL4W_s_NP3yuR?pOIZOAcJjP|qWb>?Gq^P(8-CjzSrCjaF@yo6GYP3*48&Y0yjP~v} zLbnTruM2dW63Jt_YJalx6sB+=$M_YgC1+)3mEqMl>NY9v7_E6|Wa-Y9XjhNUpJkJ$ zMCiO+KmmyE=io9Nr5YQT$_DGcM1!jZD;oTLwLW9n%GbOT~lV&CB`WKCSt33BxRY;P0 z5k<}vBLIuhVenq2<$IFojq4aggoeTr&?Cn%92tL;E7a!LR?YT!0hTqB?#cQ=*4=0-9{Dx5XeKSg6~)I|mV_Y?Llw!q0=!Ox zGRjniDQ)6UyV-||nOWZc@Scjp{@G#Pf`UyA<_8@7C#T7qY!cE#qraXZ%~tC6bZC2~ z*rsk(+s)^&SGZHwsMl-CudeBqPKV)@)^_F7$Xyne-(#e1E|1ydR|n}fWG@%pFN93- zTcwJ5`CJhzt(?fe_aKJti5=VeBa z+bSj7?EHEb-N+`d#z;l$dZ{OQabybkL~(e&lONPm9--31@otSaO8EZ>d&ls)-tXPF zP2;TCjcwbuZQHgQt=M*B+s2CRr16T=q;VTG*!f|f|Jmod_V>J4@7FcPGv{;9f%|j2 z1)r58)kzVDIT2xerJLqm)HPk|U$dQfG@0ys;+s72@m~ZNjRcrwvG&ErF^C4mZ&>8f zKc)#K=PGXn-?U&Yo6oQp)Iz53+|vV;<*?p1yEX(*HEjysw}L)7a`plSC9=Zsj?7;a z(jM;m!&D*e)vIK{;-({f6l`wDHO;PxB=NVFKf18^dC7b^@w?`Dr7cv|KqGGBy4EPGg9Xd+fZ44Y)mLlY~LRIDf zO@VtMN5da;Nwot1SlDc>gBYgHL->Mv~-O0o{k`UC0QO( zqw;xB3VP&8Qg+)#@f9H>ceAkSo8U|bWSmv}Y?0;s@@+%{lptwkc`EQ}5QChaa4_Zdz`rxj|p z=i4*1h`O|yvW(6x=lFyoD>O%}tgrP4_ZRB^l!{@;_G@PTuqz|d1;2EJ1NHT|Q~Yol$s6lWx+cz%2} zq`sbedLIouz}=3B)g5EUe2~VV-}=*)ig@v(mE=i41yl=Z22`nuziA={+Bi+2hPI>h zsCMl0XqV`#v8v|x@**sZ3i)Lcs0fmF<)gxQ8fBXb^%RYYWMwsY_?z#pcS+l(1q)CC znTZ&GL687ewRPrk{HQcXv!qej>B$|{Ob)IE z%#M|~>|B-&CGm{sn^EX7q?wWH0-OcXr8rOfx84En3-AI(cak?jh!~8?+!EB!4$B=K zIbx{S_AisE2|T9EhGvkFOSv8*p4?wDW=V5#bAmC;7f~AsK^?o2^2)n6Si~9O)=hHr zyjX+W?DG&DXHsIXHBF~d;t!z#Qh)A!gRl`i#!CtR>cqd&k5K5Ai7q+C3TDSiWMf{6 z1yEtFOfg5x{FZ%orEeM|%JS8Z5a2XPsAa}@!VZ?QsC?f%ZJfzmp)X%B(k0?{77Dnv zu`+Xrv~y<-&cGUvT8W z4(ZMCyNxSY;v?I6lKR># zCMXAkjR&#fzK8XWMS4gq+PSewbM>q}xq@ZM<=G?at=JZMW=6@sRM%->4LzZIK+csg z-1sQ~enVEI*Y`oaVn@INix#e2<*z@WVE!!QfQ(l6$?UrK)tyd5SWA^s%8dlrgDoQX zwkYLIBk5F*)0;N(bEfVAXee?8Lxr*(>X@o!hP<|S_F@}rRxfc>2pTF6k#hP(wFrG( z=5vHJzrxkefU)$`$m~B)2~$khgIQ3LN`;;lRn6-8P8%yQML0B9#$;VXVq*1n-j{*uQk%rHh)~! zuU8m)A<7%bAa!!zZKxTC4-YS+#&XZ;R6eE7czIXoMGVINZgv)Vow!0}f_yMxFebFb zJkfcYX~kF=Suu13OkUHYGO4jr7oPgL8XW z8TtpI5&F47=Q5I{?UO;1Z;3&fyKFimn{n*Z7Hu1E~%0dJRmLL;V zw6r4YZP?lowQN+)|D=?zu5H^W3Q730dvlljW81u$FHGiJ;On29-QI_3o`;vq&iZDz zvu3Ux$OfeMT4B}E$XFFzZ1WzHgQD2}@yF^|E{h(D11Hv<^qctDpz%j5imC-dQCwXy zYAHQfHkNVYFlGcPvBbt46CzL`kPx_oLfb$UCVOFeZF|odmIruXac6dCy|6enJ+ za25h(;1HTWvVz0_5xDF?9I;#y)FJ!}V-&zE$$n3s$v3bxrlm2FSZZMem>5I?Gz9k1 zs)Ij6_rk;LVq`v2g4!{)xr_IB{L{d2z$;e&1TYMIV4M-{BO;;3wH|-X9(_FBBLO~C z|Ckm=Ph@IHM#mEjzS^}EfX*A23ZrMJNlV8Q$p-3#rDRX3=EW-T=Bqwb{ec}kE8uG1 z1?mdS0`)$GjQN5`g{eQph0!y2{W-91Pb}SFbPhv*PzlpNRt+%6>~Az!?wt&733=b6 zflE|Y$zWYn%;Tms@oeK=%||utl^WW*{uwXgAg$&hw_S=?q$BEJXl{M{Z37bz!=+su z-@{=W-5KbPVO7Y8)h<(5 zwp(^>>vcQzBioF6MDq%&MnRarOVXtAL=|AE9M5Uin#SZOs98Ic@|F2|b^Z1?2uD*N zVo0_Z7?#*{-7v}YO6WH=^lA^vOl+bmsyl7Ue;~YwApa##jo9pLWZl{U4|9l-$$`Fm ztL@q*)FQ%dE|>yO=M$nWHR+MBz(th&E{oc2Ec(25)pC9WC}j=5JGwU8;@#H7t;Lw9 z%r`8)c=+jZp$$=j7}VIzGg_=ie>vSWU(qF>XnsX@QQ&@bHF=c`dX-s4(96qe(r6U? z#Zt6aQI?K@6cygZWmGJ~krz0nsQRg_F?$aBXq8rVNFJ6=aEq5R7k+Nw(4RmC=hX-` zPf=J;qCcH;t6Ev;{as-@KyqbbtYpQE+v z?VXEe?la`{YKS`~^Uou&Afu(js>1dwX{(nu97$o97}v;-J1gY{A!lL(|2(ms50r!t zR4W`A#YJn+F-2IB=0-fiEDgmp22h&!Pz_(s4qBrMIxNRXC^ZzTrHKdI;*ET&zD85m zg*Rsf1GZT&jI{?sCm|mrugm20BIb~fsB8zb%3Yg1nI79SPbiLjn#eAW2nx+S`L@B@ zU42S*F*ZaobGy%ddj8<+SU55OE=GD?;b7^nM-uT`+v@&eC5l}4(8M3y6{Se~S}Sgf zCVb>|XbCy5jWb$L53hAQ@o>wf9Wq+Cm?d3a(X#Y&nGP@6&gl+=DUTCjcRt0+g#B|v zQSMU(`3T_#I_uj{&-#dH*<}Iv>Ci&!sPc9oEF=l>p(eXaQ8>AJ5AJ!P!|JLFR`zxU zw|MW(K1N4tB!)GO&+mQK1O?mYk5`5xSq(OJ1iKE}68kr`qLn7m|=wS*`k zsf=D3MBp|?hIYF{FU+GLjvG`w^@AR-Sb=lo=alp-qxwD>{~7l1(}=rf;w6>$hEjW* zHrM`dzYV2_bkptDlD{m9NA60hF;=}1l)$#<}nLQ{AkQ&Ykq3EBdPz^8-hdfK}H{(ho-~2^nEA+1>roO?KcP!8rrtBqmwU6W-HC;`g@(+_ z4?p-ri_a?CdpG)tgGF1!hlZ5Sd;$n(oeq%gSP>^AKmRh{L>6MXY{bLqvcBf`Te-fq znU--i%E42elhXQ;-Ibuy|DmK0&So&3MPNGel`L&6vWU(yzJ zg$J?gh?_oPaSm(64#!0^3Y!U|XMmV(Qx>yT<*%#B8Kuc@zPwN$ILK|GfaG0wA9DG; z`ho=eKP@-Znu?8-MKfUfAi}#7uvqi%2P=Qal?E{D?vnS(V3AYgW*@cWWb>!7o2DWsxl&F>s|VuC*!Gmx)$FO zV`XwN!+3Y+WtvpDcRQrtt=EIS=2Msx&j-MPTq;)n+bVS+cl ztJUj?Na>A?W_OReiM_&4!E zD$JM45G9(&mJlJe>l64l(ZLT%LkAU9yXS=QQXA5*cKrbVCNe05CBm|$32EUB1Cj&O zv-ZqhKaf&KY#Qu^1$BS_KcGb=ommG10Z)Bllo6VQC!<$ zPk&=zWyuK+$APgeecYZMSOh!7}+`-hc(^qL#=fEze)O$H1A z{KXBNyv6`60ffvRc|c4!ffLtsz+E=}scQ&O48V!CXXIK5=!LUm;hg{u03zeOja-v} zf;z{wc8xo-z*xYmjy>wfP(1aHO~e!vJdoEmGgQ8@_bg%rXwxwhzI%%qG2aE`?mdra zxPAdtU7vKO0`-0f#}9Eg1`sm&P+DAoev3x}5E1U%&^0TF4p7h1GkC29>}PoaA+mve zxTZtSaq6rdc|nMPUGw$XYbwmQ&;ku`f1^_P`yef*;HWKTu2kT!V3ZbzXG9wf|y);yujbIfrJP+Xtai5cN#r7YZcybvzFh-SudLA&K zy!DcO1MlSM2ea~MzQG0^iEna%Zl2Fo}j;F^Ps)F}+wWEOAEJH2gC)ehu=_ z3Wh{SC)_GOG+nsp*X^@$Ck30&nJSN*C~MvL@P~l?F^vWqqM1zKKv1-O)5m?&@d4p0 z{n4nHMrKQfR>-}In|LZj-Kti@9Q1$-Ut zlV{iJq3fU>bH7q|VNm@S9bqiG8@*AvBw;v38vnu0q}KDum=q%}v0@l&ff=-%J={w5 zGs!XUcdCpNAL-|SP{PyIIWo!0ap_Z*TCN(afP%=;(_SUte%i9yB{LVz1zcXOx!^X| zeJkBK@$)4zU;4un6_+1`1USp2$EQ{@FY^Nf${}g1p1}Q!@T?y?W!07mVzR^T4#Z2+ z$u%Qlh0elrry1l9omf{~ehhPxY*yCz@OFjTyr2O#)LhKF*`k44WfF<>W>XfoJWPT+ zfMx_v&`Y-V6D9$)gQCRPW6CrlEuCsh>w_K3j8b6}b1y=hyCbd7$h@$VWLddrBAzFl zs|`P!#C~R}dP+DsLtXmTqvzvj(Rr=JO0M42eFg70u-HX2$Qf})66OaUN0VkZw~>{3 z;?j#E=Aq;##S}vvC-M`$G6uY89N@}SqqFe5cBRI28)1~RK>_)=TT)~mySlriSwQQ? z_v5QIJF#=l)kUNQ?C4=TvW;(qRE>t+q&j>K)b@4FKQPTkb*U0O3Wuj~3a3O(bZZS3 zBC%kf;tZ&Jf8#c->z9~~NZy{xXIOC-8pPl%^wDDxBu1DczYb7jZ8c4+iyu!X`r!X~ z>Eb`IQxl75v}{s2R2CF*WcRF|O4mv!Y>NJpah1Jw@1Hzjjnc*KOj)8SH|mr&rMShN z|JBFIj<8;*ES2rjc*KI`(%22*gi|Drpyq&pM7EiO@@SC$N@!(-?n&?${*4WL%UyL# zyL;snv-o}SL$aWnFjrAzle7)T*usjX4Xa& z?-*Cx%+~|^+`aJV)GKUt*gMZwY%drxKGWFkGDio)R*Vi-eUvDq96$ILI-&X+xR zE57$|WSJEbBCbuhGsbhrp}yXS6dRUUnZRZIpL?$A3G;c~jVuD=u^WFHuoAp%l9ZJb ztR9O}MUAH=pu(SN1B{t#D4Gf$N~8&jX-9VKX_tg>)-0FuELb%+PKMOcra&_5eDG_@?Gyw7jePSD(=nV&jcode(gO4xw?*-_)HIWNz&{p zmkE++y40$KxbP8EzZ(RWr_E@!=io z7-d+72rf+$w!@*qxEv7S|=b$jE6*-xAyb3$XqQ# zcYe<}EGRuev@t1`C3-1@tQ>!cuqi3oDd>4TTq{F3SQcxt)dByJg5j!*W{e(T_r1+j ziirpj5jd_schH_+$2cXRb6UuTm=Wni_kuQ6VQtPtFj6Zn2z!%+OtXsWwVAaSJR;_F z2vK>~4H@j}O5d@B|C~2NRZW?N*$2UHAalSW!JZ)_6WkZ9h&k*DEoSo7U1Xlyby0a{ zGMYLml~@9`GZLC07WgI4H&G9|m?bWUT77{dNa%+t1-YFBO7Cvl+K+m>&)IH_(_(_H z07SsYlkCk-BIriq&Eu~XfwoMYyq9>tvIdo-my?Z`MPOtb?;}f{pKOWsJL$Z#KGEY| zDl@k&pSmSXP9-d>co7MHTaclLdX;N9#^emT-!Utzs4leT=K(Nf)G&vTSk`w915rh= znj<9mRh}l;tRSX@=oT8Via`p=UxcpS0G~c z=OK|mJMqxOiISwBV;FZ{;HVhc_pR~#-sc%bxC|VMQY-o*cQ2A>NwL4XzA}b|EV*{x zm^P?!UW@@|DQ2VF`G6%Q0vL$#DrvHCmV8|ANAS>j5r3esLEo2Z#^0>0a6#pqL;N;7 zQb2e?^i}(!a8FN`^(dJVHTR72=V@Oov_%u(rGWo|xRxv>r+`PhtIUB=MTp?cikh;3 zEB*8bn3crF8*i z_&jw**B7>?ZCcCbk@(;qZl_pUlgZh=*c?54>?yI)w~QLbvRCHsYP>4irZr6M8f*{R z9F8SrYNzJt8w0#RUz+wBfa*`hOjKp_)MG|n`?Z4ExhC@gV9Q$AmDf zqhJW57^5XU?0e3}{TT=qtgd6$P*_^1aj?yWO7J@S zz;VyHV)r}lt?~A_7%$!s#oGS+w1}%;D2=rElUXhd5l2}`qU?bpyAH=LOr%m~bW0II{YfG4C*dPG?_iZadx3tNw7G*P^}2A<*nM|l z{)GJK!Ix0JxPA3E9k_E1n)$Q1$(KnOlRuuvdnhKsB)M|S2#wQ4gm6gDa#yr+gdsop zpf2UKNh|WY#lH7NC$YC`AP?%x{F*9i#Y}FkA79m!w%x^8nN3;lqo~fKsth1ZKv|wm z%DsjFQcz7NEeFEMIWT5#$)o^D$g}(~XWKg-Ljj`NLV`xE?`%}YqmmrJ53LLu(Hm?S3NLny!#P^3XL|>XLhRNb6tF{d(%Q=Ua-q zHXONZgOuEY%bgKw%Ay{qXKb4{vo{rHMhM%bdcW`!t{fnFN4J0dk|k@FIm9IGT2A6} z>;Ubw2-_X%sAST-Pb1IayE#VX7p)3e8Nc~wA#m`0vteB~y_vej2dAlR~O`J6uQO21iEmZ007!y!F8tJt=u zEE=<<_J_^^IOs0#v&x*K-rR(FZ^|T4MIdd3f~;ZobGnS5T)VJ~#~E1*1f$Dw z;CJ!|C&e`<4Q2xWM#ZAL^)jc2Q7w++p&({)aW8~M1W6Om?jUyV`4g4SZzbAG{? za_tsYJHoWdDqPStZqy4tVVWdkB_WV7DmS5e$?L{Mn;Q2oB%^$L2u1;A(tEtxk|G+& zxlMdKOXR8&?TPUo-d9T+^@$WqcNr}vn~Oqe@hMnAe&sBqVy#H-J7ao9#yU(^)@p-( znntec5K`jwD*_?i9gO+g@3yxuWpxJoj;Qf5Am=9WUUq>3jdTF05$yp{eQ8+`DMDta z14@s=j*q;RYkZ3W?SWH0EXhNbvL{|jmW)qsKX68KN}s(WH3?g0tNO+|Mo$qB!HFTf zg;y>D%xyCw034GJ)nh=s4B(f?(XTfj#{Ol^J;ZCumzzAP_v=2V>XuyQh^!5{$?Be8 zpT2IFGs<5LI1*SS-iMk)VZ@Nukz;Ym8s*1_1*dqLTu~o&<54EwYgAH~TEQp&MKP)3 zY8|Ft{U;CFZ!!BMeU!wG509bce0>%};E2B;P&oYEIFGv(q2$eJR@jg+sXz90aX>L~ zb!|nP?lNsm?y%jZw&bxn8TD%4d=TA^{HM?YgR&5n$}WfoLL;RD+1SiCzp@S&xMbnk z%if~Fsa4lNU&EIC3>OY$}pZ|%;(BSJar{Nvd@ zW^^-mPcCe-?V_De%%Oehd4Gj#Z+WGR8G`8fPR*W17Nq&mFoq~f%xOA|apl9%O?c@e zc;-K^%p)PaSrArN`>aZ``5>qMz5p7RdhF5T*_mJ~fQ(#1_G+j@{L!)y&1P@;U)%_o z0UD~I=ND>1IaLkQVT%@`)S!Nt;zx16rp8muCkn1D~=lGOV17 zg!@R*b!PHjs5XOn5}C07#BI+rsg)l|AMGkOSGd(6@4$_Q%gn&)izn%ROavNB_|C3F zbxr4ap=|xc??if&;EB<5aG_d2v7;es)!c{8RU$g{q|3MoWW<0tc@gwQ0+-j4S? zN*TE0&8c!W@c9032kwhPoFG!|EYjS^Z!@C`;VLTf9gX5iV8{}CA{1<$Fm4( z4oX7#>kyW`K_K6asVO;&>$gN_H$6=Tn%ZDZM*K&S$6#zUg_qB7)CYDH7bZKi)0;n@ zo?c&|=ZE{E1kmfggn)%ZRe*75BK2I)kSb1G8<5qQ;wJcI*kP)6Zm1a(%BZw!uFvZ! zfhDu$!Lj6YTqZS+&5iYx;fl^(GMsSB8ky)`oj681oui@0X?xtlhjJZGmkgcGTgE-cw;d`lR-m6wn zH%`m2|KNweJ=Ur2J1CE7csrR3&SKO|@e>2dDHKxgP$?JwB41UDXq+JT(oTBC?X(?^ zmECEzgSca8S8Vq*`^C$@F%5!6ic=&XMY*t#-~Zhtk@lbBF5_r!;ca2AX5sSR+Np<% zfgFSghQL8~b#<9#fm1lzPcgYUnZz^{``^Y;0(H%1fSPXZU#f4_B@y9afx<$xUCq)G zV9-`4(Mi7F$@JLCCf_$P`SFDoueN|0%}t-fx~mQT z`+z|*T+IunEvKY|l6B@7Lb z*ho@vNA#Fjp(8s1=eBl;vuJGL@R~;#-yt4JDxNjOf9$ybPax!N>@5BpjKmpu5fzN^wLe}jY|LCLncTRO$}}ER1L*h_y0UELjdL(EIc5R)Zhj>8?o{gzzXe4L~{lNxPs^1zrJS*>a` z;eq|KagcTA`97m*m{NYS)m0ArL#4&w@75X+?nsHZ@L3}0 z1jSYeA5^-65U@93@HCcltK`lEo`1DF1O<0@K3|w=jitvs5b*I%LbGR5vNFq? zuM`e8D1Wmk4D*2Q*7S<#!}g9Rg8KYmh8jLg9Mx+$kmpGnBA5XK_@VonPO^LZ1vUCH zkz_5KXTxRr1Q33gDW|b4{{ttw4))1{_0;8uhEK9C{k?&GN@}2shwDN92H@BuRl2|{ ztm?z=p8q_ekacW1Z3jI=OkzJ;YKZHE7@7Z2^>G##IjE2?pGeA8{1?N;rL?Tmvf|}c zn)~l}sd*x6c9-zBW|WYpjP3dx+18{z|%fWfbMEj}0$A_1jj zb}C!||54s{=%^+Q35)i;mt*_BxJif!e<{6GZGv*J$UmPr+5r7U2GK2Z{u+htYkJ~h zL#z#O*8Inx-V$!h)2i*afPBT$yZi|U$4rV%EXxsEp3x*D@ZUD(+jK3Rn-~f3F%D_v zy&lloR!mW?GR|@%eo86qT*h|Cm&p~aBXg4I7Ts}ui-)=G4>DY~9qNrR_fV_6*$hKK zqJMNdO1w|fd9>4yaN>od9Mrp)i~Hmrcd^?Q_#?;BZ4aMLdUz)NjDl5CE|K;T7dqtC zg_TDhnz7%ukX^N}L>)(&q$T|44E!9nsJF3UE|{)C1atIbRs7{{&rRwm00FJ#JVbrw z;k$G)&e!4q@TD{-$Xq&jXGumhSCZukqJZ916 zA}e6^yl!v?%LXLjq~^j3;IdF1_;W9bSme9Gd+cli;=JHwT(&&{uatuJF)wl90+F#_ z(zGY0EoOTkXtp8w8F;>Ll}VBmuYh z&DusYl1BMAil6KBn9_Bg=O>Y8yM-5d#eWfane0lT`_NKdiE5{zSuNf^yv+DFO;}iFB7r zTa~hP>3n)=B$jv{Ws0XUs~RObeaF?`5WbFYu#AzB<7mQ=T*B`kw&-OvBgdSGzwwoj zcc*$D{QdU;s`Tg)z@LBz!p5;+wLS$5C1aT($Fkb~h-CvQuD}hA#E5UC3WndJ-wQDFnp6LLo>o6eG zLNS<*zXWW@Uj^o~Uvkdvyt-kZnxe0_=8nQG_bZ zlMeRFj8U%6XGT!mxj}m4ED~PWOgb|gmYg>rTz7`skB5$L#e|vN3Ub^HQ9x2Z{9jO# z8I^I)a`%{!CSkchdFe2^lEh_@tYW)EbX$~&C6ZWhWM>@N=$^LW*%qdfC(x|uw8!2} zkc*3UVQ>1r!xEOzKyQOydt%LV->2-4^>#K_ekwS0&Z+Ix0cGNt4<^ac5355}a|@jh zCW&0rXR_s`#TqxMO#KvxTj!(oM32QtTu0m$_aE;yA54;cN-KMCvH4+h4D!meK6mAi z<{>-FU$`xHUOe20^Qw2-^7lsv{Jg9nq8=Woe6$vB7{^SucDD~UB%hzwZtgF8m~swU zwPL|?t>Y&B)k7Eigf}ia8mq79Wtc2mDo%`2cksNfUaK5M@?n~~zWh67o0iMp^`z*7 zzZw4?#j9gMja$6Mug%h#50321je80@&>-k|vc}-dY&+Yo9cW*S!hy%&Wp}dSa3~f) zJ!jog=t&IGgZa`-+zL5D84+rysF+l?wvR*w2F6*YtbMJPB7NmkQ^xo7^>T96?5OL@ z*@<|re=dKj?zY8VV~BM}{AEZjh(NBNP1_+o8x5hZ}Gx zl3Q0?rm$_hyx7Z`CpjukK(}ZsE)Y@_U#*9gzZt$Mf8`V6zSHI zIW7z6k7K5WHLhp7)Z5{hs^FL>AZ*&IyeDT!A>2$0=R$vEU@293vvCOtM(xK9vgKI8 zY&uMC;_@)S9i0*UjFEblnYsn#Ihs}AfQlXAR>f;*+NQcH z&Zd&SjYln`7EhI5XnECZZqU15Us??i?C!I>uIJyLt2U}elDc%ebSmC`%@G93g?ORz z#gu$xnOZBgwt@@PF)GTg&iqjnvd4DEKDFLQ$veOWsjBhe;XN*;S9qP;evPA(hf+$U z^-gF>^h<@_?~_!@rYlYF3^7Kt7=ygeIX`V-&(2_S$uKVKWYy(O7zrBEm7KbqorcR>cla+UJSCy{m&@v$PR-6*E-O?m6F`bQiNV}^xG zN9z0~NPs=%hkM0T`HU!isv!>9qAJ=i0PkS^>1L_MufZQdv!(H6|t5sR^u$`YcNYe)FGp83w(6Rt-H7cGAq!(`>+YDghsz zjcchJO58vldgPBVWQ3%Z7edYLuYMm=JWlZn)!@%>m(`@Ct7cZPDcVVR#2NhiADs6o z+OzWH$0d38kJyucR)zm_QU3R1MrBGC^f9Bfq_xqqk#R+3+L#bN=n)fB<$>;@g4q>& z)&eMjD}$2|d7+4j34szCNI<(PDGez#hITwwoXvG}oLzeuqxyz^LAS2jP-`qTSQpX@ zS){FsDyk}hLxYLh8AF;Sie#h1N>gJNSEx*T95Ux^5G;lfBY=}ADQ!?VIN5TW>t#B8d*5+q`?S0-p~I~; zw1U%riUoK67hGpGq{ELHBQM;# zsbC0Lwo?E$OHTp#3xLS<5f&tcD>!=X0#su0&j!Z;j7&PB!FqsQ%a2K=zW^>)d@~%Y z4vL7_tvM8vNLGCFj!dvL5`EZ$e&Svzz-TI*z9Sb54|HPoXTo;fBL?~5?2I`_g3ke3 zY%j#vuHark28(|P*cCX(tUq|I1Ds>?r@}UeWzipZh69xXv{?P4z*7Jt3+`!WfsZL7 zA#?6*1fS~({Y)q-R};<`YV2cXC!`l{?C8C(xQ^lF79EIh)SzG2M!1*4#?mP+Sp1XM zR6rqgGbnzN0~`8A$b55eSxLU*@eT~bv_`ZyngiCy zh}fyDcKkQW;xC4&3sEDinxJ#mRpy#+To&$y3ZrTKx(H0;+7Z-d0xue)&>eE4k{wc` zl-}~AtuMa3vl%V~54B@Ftd3zu;~i10EkoNMv+4YF2bQagqo#U$Jh<(RB6-gJu?E-C zvD@0j#SyQ{U{u^*=knOlOo{^(S=P&=s6RtufcInWFu`po7F#PT@E_*M# zK!(Uf3*@I_)b6f=zj0E~VLcPrX3V{|J}^TsRY!S#X(l5_S|Ooz`N6$adPZ!m-CD?h zLqeAYBD%;NN9sF#MZ1&`RM$wWV~!tE*gd*=LkK<8f8SUAjZ&dBKeg4yy{?uS^I(ua zK#(|KS@B#@Hqyq`1fj0(oNbnu37^AtWoeQXAy$cST)1_&4R2^Nps0qhtYt(#_6ZYL zHLGfsGYu8;Gw@VvSu8H%O8aVSKt%Cp89PKjzYwO0wQ=JzVDaj{5kOyxH)wb`Gh(Qs zxBny%F(6=H#?0k6wt$;TN}jBW&(aExg3?%Oy&g_03S0<7B4H76t&xqhXK>cWV_|Ee z;g~pcF?#(2$s@)o(QC*y4)G(ehj!_2TmusdA`ThpoC4iOu?Psr>+($^e=hT&5kJ$A zDU6~-SezWn3ies;3{{b}-!{6tRWhFe(>$D#$o;dt|%MjdJPdE{p1ml#|Z(F_(v5sSj`Bv-3sxIv2_J$JCRl zJSJ%NYj(-7vUOW_Wu(7Cs^wxJyDaFDpfqnOG?ZJ`#a{uxr@fu4p~-t5YnFWN1?=R=KZNmJX(~S_%VU){8XzQ4o3t^t+T}m|suEba3v4znsx0?|_tN)+x2@2& z$DA)?G>+DqP**S29;!;Nh@R3d(bf|`hQ_am?G`vw6bpaLwIpCZUgWhPD( znZiUDTZx){taMS#0c7`)fbUFCUHWym`Sd7jA(2Jx@!MXoyLm>@@!oc!L9bLTRG<`3 zx^4ev7$Q2cFiiA~o>5yV3H6J_7`0D{EzQ0ua1%Zb(pA>-7kPgE z03)H$j31RLZQ>x&-D{%wvCUKxR5&?W-ve-JoVDY^TE0h!H}jh}CA6{F+=Ovtb0*;V znT2lZ8J#$O4_EyGgcXWK%dl8pNW@yzK>qC$7n(>PBj9dVB7LhW@=J9cs{Z)Fy~;2z zOIQ2muaA1M=pmtKriCT}TY02li8#>o%otA^2@Cf#8I z(&A#5w1PNdH-tysOqEvD^Y34}#?4<7L?Q7v1 z#w9BH0-B71w*^7%{aI?FeBbbm?apdaTm$_`S7d`VuqrcmkV6Gyie`*L)I(I3FWiC7 z!N2%$bK5_99!arqba90^O*VEDWXu`j>iHWHQ>6S2iJ>?AbgM{i2 zW6d0sQ`<)j<@sm0SS7n7e-gD=*`BM7JxV$F0S~b{uN3*|gDBqvi=cSfQ143j^XGA} zsPJ#gf?!Krp%*!#x0AqDSo5S~0BWEnf1;~&+m{Z;ctrS+rfiCQ3t z@pm9Zml_h!d~ugQ6Y;JPHu_8;E=U+j_}3Ztu63TaeapEEQfokBoX<5i@oZT;f8^`n zdEBx5TVW4R3&!FplM4QUunQw@#4j6X1~--hX7t2)5mG|$35?1R_oh$A@t_|Luq&zY zAe}SON`~uPn%3AdnR3rI&<_h~jRcV9jrzysL}Okqc$sFl8n;BOPnB5ljVf7wi6P&T zrpFduBSjjz^^RcnCsxjxdXfYFNA^7i>4wh!;4V3!D`_Dx!G?zb=XcmU42hQ^6W~Nq z50&qWowM79(FhyEurKAw3!^DChhk*(e~|pyO;SI#Z%jnU=M2Y6f<=&NksK>jOoV!Q zw~WGK|5(MUb2oWoA25g#b&lsg_zKP!M>~n8Sf$K2E7vwP*you=ms@9{l8JqYbhC%! zw}9udF>+f!=E-H?6ssE7)a679XoCz|}>MVq95>pV?u- zDcl~_L+FJuep1ka8(q0w$4@{oO*5(@P1LqElGn7>lHVgQQrBta-rF{`VmMvLE7V~IY~j~mG7TrE@5q$pjB zt-?nymvRihg%pHMC^;n|%QZM9>RKGxL;AT>Q@jq?FRurA;^G|D!C`tSi|SR+jIJlX z9O)qzQ(lLuf;@Cbim)2fQC-t5*I22U3jNf96KH!YtC0T-TAN?PjDV#ZTxbK_fsH9s z&57(1X|Qk%yRE!)BDbwfBq#~{@@Eoy`8Vm%`IyO448r(?u zrZ-OiQijm0C~6lId>>7y{Ed=kD5!|3s_h%;3dXUwY#svxN(SM_;Kr}Kc0N&zeQ)aj z3!BPg-4a`XMNp>Vdzmd+d3;%{JhJS%ng8Y?^_`z_iY`u<2&f-905N|io*+sVD~u33 z#EBDX*n#2+5@EeVf?S$jnt`fy=B*1Bg?Yl=ODScGb2N250k`L$gqPXsa`Py}lQ_fC z17m37yIOf?Eb-+CTmog!A_gtbjr_v8lyqA%(=*2BGi0VTrooYQs^p z7~D6HM|4=Swnq-3=Yw++1kX=tSh%E~^>e=ln`{hy_MWdYXDJf* zS)l8)e+2g}Ptd-I3$LOjNo1oZV(Wz0FUo({iJwJRUJn9rsx;YSOyrVKr&Ap*A2zzc z^lJGMc@_}Jz>PGpgx>!f@ENf8n;O``}g0(Qng+C zKV^uYK55YXQ;nPSpK9C+CeHu6(Khq3R)q2fO{eklB^3u9nb5Za`z!4udOYP<#L>{c zfi{DQ{ig6Xx&3ON>(Bnw2in{+5E0v1{y}U%Uf-TxAm&A;M*3pDV0&OAVk?L);nA77 zDJk||df*s%FrkZra!z?xWrlV(6|O@57i(|f6nE5YizXpJaCb>?cMTTY-JJ}sgS!PA zg1fuBySux)3=kZGJIR}Ta!;Li>*U_=)vJO(VD?_W-m6!4uV(q}#f;8|wk&+KEhYOD z$ToPTX^z%kvdGG)YUy9YAe#R}82!mH3ELExPl62lb}4oBm+++}l{V-EsX`*tJ}ot6 z+`;tPrG#o;UpZ~yO5Q@M__slzU3+oRwrcE*j>El+t=7s;H$zbbl)OWKNfbTl+-t0WMdA)spMtMW99D5<@&j_vw zM1o60&&-eRFGmFFu%ZutKWa?gH+~}HkKhKB|d?N*Hl#$SL#vWQ&beo zB#HUC(mBM8Rqp=F4`AB-`pj2^ePXlQKW)?Zc@ZVe26)$mw_|yU)Q9A3Ko9NQb)EvU zIW%2rgsX0i+_a0pIQI~r+jlO0jMQazJt39Le@=+YZ&-1VfZ~@O1p%dA$vv&qsP7hQxCdqh3w6GK};Ai#U(0a2;8`x%&OANtXSG;oPobN}f-3HW#bqVb# zvT^s8^tL!2D=F&XfY-?0pHpUZqy`!9z0x>GyOKgkJVVkXQvkQE_ombap{J|jU8Zh7 z{c*<98o^4ICuodJ-cOmgAxreZS8`dluC1MOI}FGCQpYT+Lij8*yu}|2>_n0Fh+nUw zGqElD&8t;dgbK7IzXiR~L8!qxmbBNVsE8*J%zUpALljW=<82{T&vo<1-Z5h)bVD zey8G><&-5KMBi4 zqi5R{WVtMAxqyAh3wIrDMnl`Uv9g_N;XfOH9y%fThB41AO&CllK{!Nc5(zJn&{K-S zZxDs+Fejxaz(T(ug0)l&Dpai@$cUkfw_6vaIg0G)b_IFRdr0#vky^9mpAGVhOq?S^ ztN^7Syo=TdqU&Ex&OkL0`4tADj?Y=IYx9@H6nF~0%)dyC=j3T!c~ol~xWAqlfXH=8 zQaB?LNqh+ohZB-AV{b@wDa4F%AL6y&OiE%7TPkpn{5<6_iRX(rM$2>TT?S6b0x zvV*SlcbpIx@Wc$BSg&*sXyG%9Qb*UvGOQH%B}4Vn=vu!2tSeSPUpCy%<`RUrF#eAH zI@5yKN4)rUhm{lQdYO5Bs1$%K){)jSe!Vg0nd#S&yx&uNq#S7w}SD$+A3YUY)* zm+|GTWx1r|x?2e&ZpLS{ApB8*irthf#n`ZXBV2~c@x-Wv3py8H-bWeq2ixOSn}&Sh0;54m$Uk8Ui*=pBv|-Q0jL9OK zi%g*-gg-a1Mf+Mp1-Sv3zpABweJ~--WdA&N@8~XIuOQ;!7m-n+Yz9esNyPB8UO4B| zK}A)#khl8V+$q#x$?ez+JEEsIe3-p{Ea+i2k9EGk99w?9LL~#TQE8p9Jm9O~ZQ*|g zjtm{JHVxw0upKO8+b152XeCz|b?nb^Hd#SGffO+##Sh+F+a&=Q z7#I;4eHR!P7Z_w07|ldf4h_S@BH%lJ#=xH$DO_YbkWzn zmvq8UU)a7VeEC#EjW#?oQZ6k!DrI6E-K~h0u6-Dnf)uaZA1$pgA;T~#yXeX^$`ov_ z(W@Azqx~*+)^&1ZN+yU}xx3_pB@O`@6af?&6ao|m6kD{h$s4GDGzMr3AdDqIe&{*m z|9@@__&;F4>0cO7Y=ei8rgVe$-qeokFOfA#AW7Rf1@RyL<|!fXw^G{)a7w-;ZV|J*1bkKx_v>1bE(dYTm2 z8vkeunqDrrAHSk?kv$}u=rcEqm9#~Xm_dhuuHvA|r9wm)A9PHtA{mF*?T0g~nOJbg z3g*vZnsiGcer?l0f6eQAAV=IU*)!CGX680HHZ!qkI2;Ni>Eu4vtoR@vp>elacM}?pKua74XnnQ8guM}>?i_8!? zWFL?IeJAlf2pVoqG8Cxf)I{OI@+IuUzA-&OYmlgXL=x;59xORVEf{n8UJK8}nswZ4 zODi*t;b)?oNl_`=IO4~Pdql^+qpjQ6Sljwr1id<^XI)-kW6R8%QL^$?6>edI2+Ll4 zAIR!3u@X;jjI{f7bu}f=z;0+E4cj)^_A&EKQ+#%Xm!kIW9Sx2?E%?C^=s3h)pE~#Y z=_TgMg4<;11kSJVXNr0lFM0W|wCEi|3ypZXdsf-G2`&7+Iv0L7YXUxF{8bEk=<44; z$_08>-26p|nAoHdCHku878BxlR15loKx37V~~sNr(!fSKXtD`g$nVU zc|`W0$mV1mVE>#}v&1WKXsDI(znl|6<6gg|PtAw&8E*X^57;d{W5uizMW*l2SlZwU zcr`|nan%D&pqeEDJFR^7oaYJ;V|@#s^`?NA*jU-r3;3-k^Q5^TMJ4I&KzJ!o=AL;% z4tCCGic(Ge20ovn;nMXP=k(mfL<53 zvJg$5H`{Su_c<$JUM5B~)IA>C;(}7F>n&O}&jab>)*qCn58S;~fP`$3|L@!eg3|wr zO$o7M@(^wVc`=m*mD+bH(2x+;xo3h1T<}dq{NW&5hp6&3P3zeLLZbG2VJ~vR%^2yZ z0-KLqCy#uAa$SG?6LcX)gBpegJA%0s_SE*|_VkKJsnmHDxyMkX`??RxLgSe+RPP^IBSrEbc;GjSp2Dbrqiww4=I@QM_(oa1y zd4X8*DOkTGYU!&%a^JMZS^~sJJJOx4^K~WIOgJuEW;wGctY81p)hV(zn@a}?5V3!K zHUG+6RGb|C%VO$Z&Dar&J%5VC={B>J5i^V89dN{=Ys83|48)jxSp1^zqtcvcbQ%f1 zOF?&yX1>F2r}c?{;H2@-Y^}E4s%E<;zP^3EL*6EYMRp;2k9iN}xbVxb2)8FG$Ck?-nJw8$+Sr9eGK~(p>5Twi#m14M-0WF$uEH532 zGCK__hpibBhn;S6GrK8i8;3Ng-!|+=rD)aV89=83~BW+g6Zj|#iM|7+@+%5+Wc=d zb8n^j`;ZqBV`>$i?tRaY%UA>*HFH~DBwn*uN#+IbVgDeWCd<$S7joo7e~ruZkjVW% zx`zC>Bmd`f1@WbD!&pMge7jcr!|cg4nAKb_?9&>T#h*?9~LjwcB&= zap6tu)dl-yyC;p`Ee!U{VGoL0H=Lasv~0kX2Y}RT5`n^Q8lk{05D*Q%)~5~-ffQ5Q zW5D>J6TC=fzH#Q^i*Ovi3k%ScTMl8R(L*&3rBE!y4(u(c46o9}4WTark&C500c?Y4 zYvu;q$BVZ)Z~+9}Gq)#!y%B$!J-q3gKx|_6iUx}yHn9?#Hb;P6K)kr;#?4`1GDKIl z^&yCBm=XvUj~L(<4`u-k;bj^h8XcP9e&rstO9$s6el&6m1@9s%ut5sEafn%l5B@lm zeF^$k-k}f+u^)&U%sm2`{2O?P-DcZ0YNEhj-nz4Dz%K?fsBBY**V$&8UVOU_i}oU)bBhtq1lw z--wdIB;MW(xWEbuwoB|C(>(g|;s)Jbej%+pGM#%t>m~$VST4T6PcOaDekkod**<}< z8p4CO?S_Wm@<+hh486u827RP!M_Qt3Ct9Ly$FRcO46;>xKznHDZF9eX{CT0v?jXm>{ahVSoEVl4z3@Hzb23p=V4G2HI<*%39 zk_mSwB`eX&0=?-PEq#NZcsR01zJ2i(eQvkzm@%gvk$ycB_b2j}wqNATkO~tgpp%G0*r8ma-H3L>hCNQv1M0{CW%%O(s-QL_1y9aU8LmJW*2XQl$%o-m650R@9O30 zuw?L+Hhe45PHn;{h_HB<%NrYY<1F-P_=&DD99!#J9eHc%jP2#t4nGf(O_k8F)8DHv z?M02MV6UyYY3As$da)b2kiL!4^kP_QjgyMgXeBe#NH#fll@_k!9eL?D;Na{*bJStu zTv?CDf?65~iQfX6?IV`-oyQO!;MTHW)lC#91$^oKKsD>W5@(SVYuG}!GhMEL@i-uZ zXV0@6q8o*S?egO?oV(>Gea?nl*kN?b<0vckh31@t9)z(KCOH%D=Czh zZVdYe9ymW$gB~mr*yEmdFx|aj#L;4VJxl20JoDZ7fqWvkhQ+!CMY=c3^)9;b;=tD; ztc4!4?qYjMlo2*_X9itse3=c)qgq!g4PD+djW$Y|INpr{ryO+OndToGHt&ICRM^p% z2(B&nWxC;$#A!Fw9EC!&oc`pGz}G`%;N)>$sXbS)3N(iZf+q8pfv6y(Us|YCc!?wz zLh9AzVC;+$*7*69^28L*xVr_vBciZ_miDP|K*u!2);{`123CG7SUB!vPSl$3$zTfG zHRAd0Z`bGYdb$1Mk7pI;@whm%B<&B@xLJkmQOL??cyXEIW^g!`<||Fj+gNT0s~n6< zu4&u*HAjqh_^5bErS@IUl4eCSbv_T^2yf5dCv&VjCT8t%j<1|l6vC53O@Kz*rM%1U ziS!;IqFDyDllFxn;)Qr5CPkYW1opZLR_VcH(aEx#7OCrUb*vyhcj!F&CrGr$PPWg&Bi5njPRD9m3# zJo*r~7LnXZfpxZ6amGj7TTYtgfZg^ezfYE1l;`;Ben#N+VbjKR6#m#L_S43kI5ppb zI!G}mNcaf3Rv~QYR`WwnJdhL?t@N~v3E$f4nqucA{W#>f##lU?uXup-RL%k|7U|A% zag}@ohRie^RQADCU?5KHt|P&w;pU7|Caoslv>*LLf)JaJU0cgyEjxZA&ucv+7V=3V z)AYvl>@w!@q>VudM^I`X(rshC$x%o%9zsmz73x_Hj1Ax3L9|Kjiq|Cv2TTZ&HMx#+ z4A0f(;gZ zwBmqPf7p&FH(NkK2@MzYa6U6*rSReTT(Z*!IWU2I@Utw9|DCXNHK z7q~>7YbQ_<3`k}EJ?nRciC+UY=LK8pd-WwcbHt7?^n<-SMMHDX zWT^U*MQ4>Xd9h>Ou@THw*E7-#mCa(Al@$cvFfYWF939xipK14V$5(2xA#|iEyMJ^} zD_|=xvq1**n0q4k*80as)W>qo<<^gtGYzW|0HlO?bhx}2=$(|@(Y%1iY`yD-yu=Ca zu5C)3xJnEwCqL_prBLis3-60!eyBXwhO|@Tp7r8_I{pFr^{w%T2CVvZ)xe z|AY?t-lHZ?aSCo)rHiy={4yAvsk|WqUE-gKK3U2nWrnWD{XH(WLp+y*m!tFx8G#pF zKx$`cL0bs6Eo&W-to^x{-(aB{UBE{DvAm?ID{Kb^>fs_(-x_`&5*>lMMZHmR{%5cH zE^#s}7IvAZpWIdo)c3L{u21JkX*$?sC!QmWZJ%sDzGFs`&c>t^gr+X`rxEnq z--3**BGKBBy}U=|np45b|Gk}e_k*wdZzS)c-AUx-@Gy+XJ7*Skm+;UBOsX=(&&41FS} z{AN5Daj^g>ol)wLSAOM;@#qxokje+f7TXR;#eSTO3FiP8`LEuhF9=rU-yzZ)AH9&~pv?Bi^DwJXt6^L$ zO^5Z(1p57i!?z}B;VG8B04JE7O_tk8!U(yBq|8zAb9>O-%{6kQx#H@|}HEL_q5 z%1&$(ibmPK5`$|^oM_R2Abt?1)MLMN; zw!E?jo_fe$9l1mE8}|lLWIzjMpc=YHQcIE4I<=R`g=U#3|0iWGnR$tUAkP@l4vbDp z#i_XBp=#mOBue|;6$VxUeHNoi?50^{Klmx^<-z_Pdy+=!Mo!7w=XVdmnsFx-S4J1! zJ=!p%%-dhZj7>45*f0@ zc#p5Ve!;!SOlZl>iYF}zyk&dZj$gm9`E=)g5!)xQQj_n$;`p?^r`sxZq~|^oO3+PGQ6+jdrKleDSE-qd=hNLq|4Qg zfEz={@E>-$WKG5og3h_qMTg#pc<3IiZPbo@ZPY{TgrDvbdeP4VHg%6x?lzzG016Fz zOW*r0tKJ81kbkQI-wd~h5XhA;7m5H7M>3)C#^9>yjf^C8v?9W?kYBxMH0@T zP~D1XqIWRTwf3TAq)@HGBA8f=#(p`VAQAJj|MK4C$zV{h2=jM`$o*%hfg1|%X|^|P zCLtPEFv%*(K58z!`ocl}I#eHnN7~$;UQv&OfsbAfRA4V|xx!yF47UOgk!2!V9V_Vs z5%)Gp?FV{SK70OLe!rQd8z=M01Uasxl9kp_>aLm)_LI21y5fA^sfo0 zh6BLP&cyg1m5G0)_z4MP3K&X|NvSnu#&Dz`Bp+x6-pbI;-y?WUC(>#P(i9hT7M{IX zZ~=u^pt&;YHZuct&3Ae;5HJRcQ$%;zSSW!nQQ-%O7>0 zV2p1a$w|0L$)31e2Pez~jj={=F=Iicnoq=6tdbWAmi04n! z54P~fs!|#9Xv@;zy3WpOn~Dk8PH1UcT#B8s15XWR;PAKI^9~%uMAPx1{XzjF4Z3;X!_j z!t5 zV;v^8hoy_%-ZOQeJYY#v`F2w!_8O36kyeK{Iz1rd2LT(s8XGbhvVt2w;fTbD~~DzFGbxhHy+zqLFwFLdn_wk({(2r z0uUgaZ(&kBBS=qX?4qa-o6}dH;R&lZ--6Z$#<~9u>wGu6w;=1X=(^w7AqnXFYkxqq ze_sD?KgkZBu2`&FB)8c3Hva3}aLgo{vu?0dyIi|WJ1o|H2+DVqkf@jej2*Te#~lRx z$JmF|hs1}s?p~xmWIj|L4*~n-yKlAK^lR^;gXD6WmT`AXE`9oWv=i?S-RiUp44HP` ztGQ*S=XZn*{&VbcRI@e|2&pf&KrCnf=jsOJBmbX;qyP3D+nNg7+FF|cfd8sBs_H3V zKnxUAEHoCO>EV3@X$*T@!ot6OB*#a8FPEw~^9>hlu_`|ndvt2LMzQE?d?OU`u^-mw z&&Y35W#BT>Q?;iVr2l`@k&Es5dMS$R&9x@0-P#*2yP@uOKWM11S4q;1C``uGug%t^ ze&7q|5z&t@a?*%e&3;zcg?0nIh=mN8O4W8a7})24^Ab`%hk zf7LsahSz97?6$fZb&t1}M&KpZ#tL>ReZ5LjMRe?0PC4z2Wrm?*unj22i>7hFymKDG z6`F-xDcaA4IWaYX=TF&fJ?63ihNm=WZR~2RG$Ur6G99EsABY zPHpU`-sD$G>7B(BsJb@u4i(-I<)k-l92K^GT~GttMV7g#9PQ0xJZ&MBfP10pxR;R$+C#%wXx)5Oogrq~)}a-6CC58>Ba%%~EKhU}zfqzo2Ffxn0Pk|Iuac8KjGUqSD%qcF}E?%C_~*f0?Xlp`tjk9`3g}h zr8&aC(d)^{_Pl#=ukt2Llf`c94cA3?cdj1=)L4h4VKEMkE?bY?hUmM#Cl140SSU&g z7>4y81_ZWd006lT+hJJS?R$NYL~{Mb`zE}~<&}3wXQ%sq+?cv>RfA2^u(j)6qs(%4 zdGzty5Uln-q%N&ul~VIH(GsMxE(mF9e=OG2+&CUOi(mw72 zqQxJmh~P?_Vnf_-{1{~N*t^gQZp!)aPD=|xWJa62tS5Sz3`MF}O-3TausT2Xb_)v+ z=GTh{dugnGMaxw6PA~`KJEhH}b$;thK{1Xcs|&$EB)qF9^sQ($w32hT`YBn zm;WY;p2=>FHDe+Y_wMiM@moZ@in#%xUeWf7JS4pG%k9fK>J$7mm@Oj5vg?T67pmqo+@|{*k%c1Dpy=A(6gNsE~RDIGm_QvM@Gf#8P#VP z@1-bV2|nip;El;}fS@W@UvyK_lml}<@`y;P-HN&r0}HNj=-|(AL?bvI$yaIcf{p~@ ziH~NJdv^vIppD9gH(F`qHr1uOukgd!U}*=1C0}gCfsc{Q``$A6DT}0&c=!4fV~s$@ zr1P}Ot{}$42jvwhTn9@ZP-hQ+jW`Pm)kOX zFtDu`Ci_4BqeW&k4kObD0^!8JLYVzO2*0AX&X9(s{|&^8KU8|6ltVSEH%Q4I#QaG} z=qWiWia!lru-g=wj5xX0ouQvhu?Re64@M3Ci$E~K<@~|0<5ariPyVzNNEhS*+Bb@F z^X5FiKNyzks|9%md^fU2g$qCa+MG)Uy1a1K8hQhAUd3gx!&pLE?^T1kPG*ainC$k> zN960S_mgPuu_H;NixxnNBabl5d1eq4V-kYC(?2EXzQ)_JNg zf4W?DIbFo>>jvp0#JifA;L@x{dSSDV&TgR%4d1vgvZ<=j*=SXTrs2VOuOYpJdKm_{ zUM_WB@M-DYd(Y_eRra8+k>S|)s_}+{u>(jFtkYGEarvL2E%+!hJgl++1Ggu-Zn?Eg zE#AxfnefMrb92OVD(c z@Et3aeFQ>u>pMk7W|}Wd`hyi2vV6{)5=OZ2>9x-;L;N`>O8R!@v!dH-VquDBip7G! zg+rJAqy9OTJTrQ}9Va7pZ3aXCXsyvgNC<|zrmN43f`s6}N0z6zOc$ssXs(f#12o7f z2t>}D#4luH(*HF97Pqzq{Hxz5Le=V@^p}S2&82%2hy2n)M}|Xb#e%N z|4Dz(|Iptq!xvZ|#od4hzmMBdtebuuB=xx;U4D+Y9ISgznVS0kda_6S3P%UCk7NfG z29tQ@WNXDg7(t}rn2Q}EA4$Sdj`a@8CO##>R15hmpoiE;54A&R}VS)qg;cpRDb;&?kz!(--18^gZ?f zdM4!9Zd6J2Du|olGPY!T5p&S7uA^azrIdt)7nS5IZLYg%c_f-LH@#AqkNTH6=T!-# zy{~P=2Ibb-DF=|$NKnQGmPJPL41ATMNb{wg{czCv5veSH$^0i<`2!#zOi=Zn!WD&= zPf@AwR+|qMAOmV|8Rs(A6D@u1wFM3fdT<=6@&15Wz~y(YVGz-vzCSBz8mG~`O=#SI zBDNK`$=ERqHcN)^-_@!iCCh0tHglUKMWVjZ`@kmR%+9!T`fXdz36M$c!} z$(oefjOp)jq92(Fnu|rF@(rXbs~RIu)#dB$9?Gm$r5_utC82^uUqawu>d|t@C0O&3s=HS6o9gDa+V%0v@q^BeBUE-*&1sfe<FTiZ25mx=}79@P5|Q39V+Mr13iLr+fZv;k|}rZ$@v%+_C{uB1!=T;7E`O z5}kx&8Hj1Em$gs=xiKyI4TEkyVB}ql%GkTQ`YE8L`#MuPwLf62Ar!X ziw!J80ZwD~19Qb%SUt#9(!FMr*C_>o+AX!0h9^>qLR)va-Pmc7vk@JL%|F9dwNWoz zYU2Jtw}KKb$p!?LALl$%rXhHO*HDEKk(_x^@gvz_X1Y%xff1T0b=&lSikmf+RW@kcx;(b6K%Ca>GbxkB zOWiWOhWQxhRb&xxmz!4AX3Fu6J6V2<7;wR}W7oO4WVq@QyCIjz0WU;4qtZ9_oa2?` zY4(_OE)F+;!A6(TIMRg;0^s;Z=+#SDZI3 z%D;-Jbnk^ERN9=e!F2T;N^r6e$FZ?|F7dioK9X`BEH1vj{O(kDk+6S5gyYFDr@&6i{3ZH)`+;6UQwHOh8atd_ zh!WA;3l+|yTg~=)b}i*4hZ`M|%Z)aL2G`DO0+LCa_0gp-6xA^SUK3X5aB8TTIx)_2 zDb{)AbV^lOfN6M7#6FkGU34;+N}@%763^@MPd_~uur>;zc<*t%;sBKxnhzwC{6Vz znac%b?wED_BbhtX(DoUcTw`7>7^Zuwa(PXc==9hTN@s^3%@vvqC(9viQOn_t$#s+u z4$FM&U44{^d(-d@AZ%*RE|j2j<{;3+?Ji+}$aV98=^bX)95n;U<~o#BJu@DSZum%h z4sY&)zmVHX?tEoUG(N1PC_(04F-k6DLLkRK7&`RXQgY!>oEWGTFuVDf>aH8X5oeFS zOUCFUWJ~3MO~&HRM_J@_>O!06+p8*@LBa%AS{V0=*!Kuu#?RU(R|>cM}mO&L9z(D>-vBs@Rd~3t6Ovvvd&3h&amF5KdEl=wC-;8qB@F)p~o90sw?leA#A z)9d47w8~c);}2)6H!E~R3`jYO;P0G53ga~~n%GGgW$l|bm>GKHSNcGmo@-ao%>aDJ zHC8H5tR5k%Bd3iO%ALXkv9%;VhR9ZarpMT4lcSt0V^6mr5yMHf#Im*7H?d-?!J1>33{oiu zCQ2D)FUB!|)7DwH7I`?d5U_P+I7gj``jDaAjbB!L9o43YWAm5}E$Yc0Qw}-g{^^4(%2eB13HcaEq5r1K{U^j)#oX56-`qrXL;fGp z{136lhW3^I!%d*Y72`!kgazX$E6hW2`xgF}Fjvkscz-Dz@ueSuxI38DM0ApwrcZl1kJWzSGq&}U_~TJIRb^kA;L0{)Q{!0)zt6}86|^m zgB@%W{WysgfS8LOGrH*lH4a;y#RSY!h{yQjN^JJvssPHKx)~i9XRCo?TyY%;0ByU- zQTITtCkyoaYv~BxbiF9q<;idc`N>r=;)%;d(isF+lp?x_s-dwuEA?sBvW~9l zRLM-$8hh+aepPR0E*?3ET#V{~3;a3uB#!$1d^Q9}#4susA}j0Z-J%CjBv@zgsKh&# z(3Hml>*}Ve_o3w&y_D9H)<2&*7zD`;**F@!x52wugx`uqLMxq=scN9`K3Xi$IY~_0 zV_7^YB!!ZoKJcy@Y9ZFVjt9TbgGdW2*lO;tY_w3jb^f2W{-b9BfX1YsmiyI6WV z+B19gOVd?h$R=#aLGV9Z0vN?(k$E$iJ(QoE=1f@3>jUkDO)WUZ5|oJQyLz~PiIRcZ zJbe6p`sw(Dd3Gjarrs<7Ji#9{7Nw#mPkla9Jqn@|q)5gVArw*2wAsCPe6@k7$_r$O z-jN9#k;d0C*8TFoMe4q##hCIO_dkS=ePJe)fT$XORAuF+4F!`MqvVj{x_Anqg?fZ7 z!15QsZPU12l;yjYu0KrVQ%>plA7SqAwE90`B5FX3e`7ap%@$qgj~yfpU2}uFXU%9=$iC)fuu90Br_JQC*v)|Oac*~R#g1deD<37*4 z*BOp3`}bK{8gCje)_YrnkPykz^LI)&dGcOh_uBT|=iHW+&2T`g?*^hse_3G&Wl&F|^kSt|ZejfV z!XQl_!cvt3IApeDZ_+)^leUz_-i}J0HkNTPQr~ zrV9h*KcnRYOfd^i6@T7yGNae70F%&M-*76#D1{exb+}+OvlyJm^CA14;^In^G&jjadL=Tvy4bO#Nqgx>q9CHUeWeS^GC-zW?AH+zd~S z(h#JA{>qsl1KC8=g*G&{>kx8`|P$BqaK5+ zOYh%n|Eu%c^J5g>Q@04D^Sg|8`wM>MVzqpqI#QFBTMNhr|9sl62-L>DUIE6!Z?tNb z=tD-jpY+$V>qTMvbRNvgJ-GeQ18eccoVnXB9!%c%sc!*M!MGU&)`kfHd?)Gu%8JMq z&a8=8ge%V`if4$(+2@Q12O<{<1ta#|BAVe%vaEMs7=wyf*M~3IK}Brq{TGNJGgiJZ z@CxD-0I~_t9dXLc?QI_do__y@At;HtJsxa=$YkaQ-A9L~{{t3UgC+2lnILl~__N(Q z%Jnu}f+Dg)X`r+lkYYUOyT8u%5oE@f^6pQ34$EhSC3E#>0vYxsk1Ud+j$D7tjmDs}FUow;^zUlhT@Ah8Rh zAhipMpfLBZa9&zq<%hgp6IuO9MWl}^nowD$53Rje7v4chmwOEtT0v#oXhA$t#}|4* zR&GuV>xFhiUh)8bUB%|i1M_cLtS=yN6FgTh6Bf{4f!f-5h2a7(NbZ6oNa&%kS02)l z&Dbmh;PMgz`xKs<-NL$w!GclXQF@9niT$!@2)})#=7ayD0r@>S7u;SVV7Q0OUZDpx ze*+X9vzurPXKSAji8(18)Y0Rm`%m=c23ww^+(zu;NS@6#WR~P{HXp(mkNDZ0MiC3( zTc1_n#)u2g-RCn9@s#o|75Pj)b2=~Vx0)YQOR2aTMG#D0alt)t?Xr6#4=c2A@2R&=MF*r|KA|2Ok^64->++@DKE+E{D;t{+_~(v#cs*Jr69`@8YWih&bGj+`TWlS94VP#kEe2d9ej6W9|9ho$h7 zMJ3^XHq^@cO!)8t*C}LKI!)MQ7~L5g6l8h|8g8#*c^;x8w78N1?$AV;K{5lwA2=D zfsp>@pL@#s=gM)VmyMta|J2J@rsOS6VZqAmXK5+fl0jQ6_8(hGVtGZh$-G?!WO<6r zKl2RlF$4EXDO>0b0-6W{RpI!|ue^q;i}HS75_)mDXI=QZKFP-_RQj-F<*PI)R^L=~ zIePHG3!LS{vr_OIee@-spWe5*?WVipw{y_W-Hfa~FF9g#UWt;IS*Y(~$VZsplM=Xu z8S3mfxwNp4z+7#PoW_(e%g9fB_g6B3@^u#8 z!FR-GXY*(;ukt)-tVg1x?i`m~uz}DR23&2WXr_2zJJX&Ibr#$n@=-w^9yi8>fWjEG za;G&bVWlC;3M`Vo^L9oX(U9n~R4n{wq=;Z5UK351*gawSm-TOP8J)D(jZ{P-$#DG4 znASExIFujvwrtwHI%GYR8ay$~arQ&Vah0LWSmt4+*pGDScEhbJ>xT>zB!sGZ9r>&}VsiW=_y{52yN<9$NG@9RI9*kuW&(vixuuUm<0%JfU& z&QM43IE^4id>S7C23Y%}dN0eifW?xBqV^E3LNoBWam-vJa7SWf1HH)#(F$?x<63ZM z!o+xo;I&GqTO{jZGiuyIjZ7%0tgG$-4%8*JwM`__iS1k77RN92`H$v-_+Pk;t`j8W zJh{cjk~7@xd|!zg_Q!G zbWUJLv8A{p?jI_HKUC01+$y|jNyXGT3oI<=6f-9#tsnc~*sR6PMV7GRzxBbG<>NH~ zhgCxg1_a1aIk77~>cyXI#3us_W<*jjORp7ys7_2`aDWV0?ZMGh<>0p)1$?jh(hIW~ z3QRu|Li%+lHIk_JcJnOKdZnM^ z3yWQS@_Y~3-5cGnM|G6JZQV!zU zqB|xO*DK``gWBF7>xZl+vH1TZMLWo^%8BN0HnICe5oKyf7=-1c+wPc>)spp+8$CNfGlR+hWNc z#;B_&vH?Cfl)Hcwlv^-2DT{TPO4ifEG)4mbbii?j)rpeQOMSs&4t1jXs)6m|q)gv} z^FPf?tp~AcBsJ4*H~0^6O3ibSHb~_nfA?&c$wlskr7%K~Tu*4Zc5+v*NQWU9aBNb` z?qv|0Y#*3?fvg=FS;SI)UHn?F$g;49AL50%Ns8O9P@D7d9JM$D{T6qfMzM{;5~}?p z{A6K7nm8bB+(+@MPd$Zhp$glgj335#Qdwgz!V)ZBME{S&8=A~-uH9Hw^HDX)G=fC`MKJ3#DLv-22hqe3cSHtq*wwmgRn?6AD^>@o! z21$B-8UD?iI*>^5zn^*g|6Sbu^ESJ>zRQdRhQN24c*aQI_;)4fXeFN(>w2a{L;{NB zr>Wd);ec{){l7~Xyc152HU-=J+<=^g#N08R1}yM9L_U1oO1i14n3b!5W}msZTc6o~Xu9%J8AqfE%GrON9v`x0`SDq?>K` zR4uJIDlef2ML`55p@)J#`KRUM%AJ73^=ggF_qPW!QYy}MX^g)EncAd%j%xJ2;ffm1;WkVApHb43h zQ}j61UbT-;eXx*D2dNX6mt!IQiz>F8b-+g1&!!wwA+h`n2~x!GPQ2Du=R>=zh#d0U z?>69E6R7MlKchUbd#6)=2AeL6&k*b$t~O;`C8322pbcCf2qAK$`|$A_%{iGMVvhx_ zv0N2yD+HkHxMy(J4#haqwNYw2>H@!E_KXKpG-Padl1HBI0?!1(i*~miB6HYd2?)`p zU1l9)pX<^u!!0}PO)BRx#-j3=TB3dv$D-mB=7GKth;cCD2WA4Q;=hZeKCy^L6&sK+ z;Znt7*`ffezDx}j8wM4W1Laij{R=fn57MxQ_oZKh?+y=TDGDn2%Nc1Ka^&eo>r*~- zjVRLfGtnnyw=Uoy(^}K}Na3wn4_SQ(pk$bF$ojF&2(Kp&m&OwE-DvqBS|@G$DeM!k z%9Y{^T1kI-rf-h<)ep%enbyU2k4GNdXvV8>v%~}Euue2%>ZnyB4in-w)_0Gj;&m&l z6WI!+0T@d&SMHQNUGJa%kUpBs^~?dyyw1S>9sa5Rt7Nt_awIoI2+miIkp~>xB+4#L5$8)6N4RgDG3=y<)ZcL$BK^<=waORRG@RbdABpCTFJH^ z>8HuV5GB<{9aVVSP%qNHZ-_@d&9%|VXBHz}%oRRtEFGmefaYAAiQmjS4fRKatD|$v zSESX}8~!p2s&i z-g%gE8BK5d(zn~R)A|K}-6C}{273gij@*)maRECvw6h8J#&acKrk2yOW$2U$`PS}1 z0xWe&MAry3Pq$*qL~vJOH0P49KLG$l*w>OCyWEw zR3k0}ol$wGMer6_6qC?S&1uVH?+HiH3u5!fRreZkO2iO9>iHiDs0`9{T_KhB)9~iu z^|?i$D@V2$kjV~~iL2lyMt8)JFAVAl?xWo=5Oa{=J_GLd^PvkCwgGoHpRhUzf(CHL z<~6<;UedEcy9T=H;gS#IO%a-*YYxE{0S^6lq2Wy+lfl;RthHjG%-ZnWcxU1bInlCY zEyd)XWBbV{HRfDTA8j&fwSAxCX5K}~I_%{PcErEl{3ViedvU_k2XhWqzzLT?yY zP!Hj*pr9d5j>k2iR>2JdIE~oWGWX>9YURi*4nFt2V-_0v;MOd1OXvFI%)VYWH9PR0 zGp?4R;f78&Xi8v!)aV?abn!mRCH~rO{KO@^TIDezR22A5^aeS+Rm-aze#yerU!Z4e(A>VcT`TWx5ZXCY@C zWU%Gm>KpvauSC(=)Xdq;^p9W3KlFt_flyHBfu{bVGvkhx2^49TgGDkLB6smOy+7D=90`x5|u zB}=wZfH8m^AnM(4clR(jM@j%xzp)`if*ha%lyShoCdSRvg@eqg1^xZ^83^S+f5X4c z0jXmAdV>D?|KHiBfvBkX%ND`kwv2VS{=G3I^tBAiLe#9x`Wr&D^qj51@xnM2qC(uY z6r#lD@Z>@KSjw1_9cVZi(eVLk0jRKPAMK4z!Ipcv!HrpKz@(|5HGZ4M86Slf2KwMt ze`^}<|7_a7ngf!8Quseapxpk4Z%YpK|H8&M`rPC}uv#Sz6zq~4xD^bw6kTEQ!mze+ z{w!0o{h4EB{Sq~R?7HWp_) z?79eK%EsRsOYqOes+t(t8iB%zvDu%Ky8m!ZraxSh30eXgiwK$vT0;Wb9C4gQ0-7sh ztYZxAZBC7gzYHCD&^rxd?L2=4V}rIY@2%`rtWn8>w49uc74rP;jirJ9EMai*hRUXf z;CL|LQSV?m(531iyaEjcAt1`<-J$oR4WR(PKmN$L4!j%a!~Jr_fA5B5|Ml^m&CG4h zOkA0T?Y%UNY~_vYt<23_{_JY}!w3+*{-NIma=(rKjZM6T<{r(4CFQOVgzlGlE z{!C*DXk6H48cs$Sn#!|OP2A*E2=-6$!T@zVs-D5&?y>ISZpdH;014^6AW@a3Sz;@B)S5N(d@G!ua*2dUmHYG4ug*kl_`bj!LN)Wi6Ntx{#h^*1<@YMflg2C!XDSLy&cEq{ z3hKp$AegbT98OK`qeS-_lN@AoB3zJL?FMBAG z{z7oseVc?qBrjdXH+sGLA^`2@b~5&&{+{s0d}{vbAc1toQD6yhyHeLoxg3#tB7N-g zDrNo9^{|wq0eRqKiu#vDi#!Hp>A;xF?25uVN2i6Af>Fquin|R57>dZ)^j|oW? z#S8}A&{DeIyGp6v$UBT|$*f{kh&cm7WY0_qK=C?0QYF%iS4KfaoZ9d`ayi0m$2qE~ ztx?5a@A+WK_daw`#E$-3rN;jbqG z+yNl7^Y>2mIVR*z2I2L0(nuy6bw|RBb%=@KK6I803NuR+8G>$>1m{L>;q-|DsBye; z74B=mg8VZF(fx;U%y|wZ!9@W;V(FWNljs`mUW15E3?Gw|=-T2s(4X4k+LS@apb5up zqv=>cJ+8Tn=W;Bqf9Cd(h2wmz75~wY4-a_{u@qi^k_Ok5tzKRxw**U78x6-_~sXn1Pe8P24XAs(QP_2`f43bS!e_#{P}Z?fO$Zu-bAHuaf&)F-Nk4_Fd~BU=a&%`dJ@DPfjXf%sptZoYY??2U^*HbsqsZnUXM|Fe5C}MUW`>Qxu1rH0ZLmG zo(Y+4A0GHw=bDHQR~Od3NJwKJaFc`0s7U&4uPY&7x8c1c={{k81KZFDA&%PeA)aw6wza8~`U6b`j3Hgt6yhpNi*ZlVF@ zk(9Jmkg9idfwi<%H|=v=fsXW%eeo4}LQGO37hfUqCk}O$wy(6$!3E(7)855bstF27 ziO^ZolTzMb7V@R8X@Ue#rVxw*E{g;a$j?X{NAqtNS%OCA@hw{;E=64S9X)8a$>u+_ z&AY*1vX_*(E%~Bfc!ZFdQN1_6Fyq>I=W9o^&E?fR&~7LA?x{&hEVZM?)97b=_y%4? zVuo+)K+1|Q`Gu|sYV3#)!@9}O_OK0%2v7NSn+t=CYU(dc7#C?BliPk=#F=*YxOS0A z{2}te?2ABVWks&QYRA+QmM??6b=c>KObMMphf+@VmhiK4`X!+YffuqzZvXDC)c7VN zjj|!P()dCCCqiS+n(!|hEt*NqN&+0Ar{gY*tg$uqNzJr}+WhuVjWZ>YC-WgW$)1PU ztIPopICQAb_>Bfl$ltNA`twKjy*RjE!vwBA`#9u~$7U6II1tGfEu={X9i2{+8+yZB zF4}oNURrW=`dluWd7paCW~}>pK~}GzTjftGZ$h-iR!tE)W4fu;3b>vxABl+{mblK~ zhN(hLPF2Zox1>3_>n{^@D`<7M?rkd*}q$c0Rn za5M_)G=9N^thdz1(Q8*xMxkSBLs?xLv`S;knoWz!6B-WzZ9!44E6~kS2V{4?}(}J(yNUszWP|6#*N?@0tAViq$YB^n{ zn=$eEY?qQy6inVA=aQ5rh6pWK9Fr_9+nQOr?=Va4V$3LzKnl+}T?rj9kv3|U={t>+CUZfk2V}Q)tpIk}UGO_{M zb)ClG;CqQ<_np*?DAKYMy3p1e+!hnVLymj_#|O@OZH`phKL4l zB6Y0Am*R--Eg^@UR5|mu_5KNumZ->BNUY1^T>e@Cp*SiJ$^cwgxeLzmR|PL1br7r} zqY>*N8&+VIG1DR6eo$2|nk!7baEj7CWH$UpvC{MJoD#pz!rll}rNw~4;lIx*|54if zQ~ssqk=5$m zHb-~oRu_Dvzk~AK3d)L4JdRTBTEu+~Z(^Sp+Vq^D>M)m09pgV(ub%|ndG`6yN&VsF z_N&92hfOg@fxsTvA9Pw8IrLh}HAUTOajJ&2!}K(>qTD9lg)RX#Wk7I+PKVkY*$Z*mUp5Sn^s%YImXP^h%SxW z54QXQ=KBjASfuB>=C|?~5up5*e!lHjKx$2;-A}f}RrtW8eEI-cy_bl*ZF|jLoGOWq zNn8)rL|nGKo&Gp*e|zdxdyQF?WLI2hR&gkIQ9dBXm@$*QX^beDX!bgS@mjZ5W#zh@ zzRt6xI)2{Q2Q+Kph7!q^Y(tRb(d73D*tGQ5#txImO*RnZMIeZNS3fym#i!#DFrm5{ zb?E!W(&7p(s1wL5X``KEu)O4~4Mm1)6r?C|JggW0^&n3|oyWP8D>*hQM*OO0e{d6)yvFCeYRC5`D(DlCU;lWmTzBq>CaDR-J@hyPT- ze4a^hj_p)xq1JLB&BmShK#1r+8~UgwEf%tC%ErmQmO%ta7@z{K1Wr!H5dp6G2j03Z zig6>Bd}s*aXT?zv3T<1&dV!GFwX{V@adbz|-E{xxFZflttBk>;GeO*9+9fgDJ4VSc zqZeS8M|us&)z4f}NSfL{po*+58@JpLe^eCIgV^z3{pr7|9!0RsGY?E?_gsmUAzTi9 z(BN!L!}q|vw%{Nt{z6g~t>{})yEB_2roVx_DaprI;df4-cSPOt4ojZ0qcvPg&o*}_ z>?RsbNA@sm0X5MvK2DT2H*G0^QS9ps`_mRJAJk|^(3=TTvAc+vQK1$YXXw{yTtD&m z2Q1ofmLHpVrRpftmW5U8EMTHX)(QI)6j45OoYDSpdZGHje8~Jww%@WI@(YDNq2TLp zg_7zTGfFNHgH{jscMlQ8e+wLcy+8ka!#O%>Nd-jl0$+jeLQzOh=-MF>Bl}xqQ4moe z5g(0nHRY#+bE(iX(=7M0I+Pr8`s+q@iAOJ8x#|F4$6Y6n&YpNzTwh)u@A2QkX4nVw z7`TY>?DJspt~)X~F}QT>c@ClOXl-fjY1xPnKBP~5G(bLf`U-&!EwE{QFerb0s2K}} z%-?c4fI`*j`LX>N^=g|o+w%fEwlj6mkJ+4MYk)qA_3o*yf#CyxWpVUsa;_jt)7TU$ zP9$OYBv}apId0}ch*=0OAAN;xQz@=ATWs7?1cE-Kv{Z_dt}MPVW76oN@%xmXRP!aV zXkKM;D&usrw<+JQf;swHN!)SUz`yQwH8*__GjQdrI>DCdkL7NxEF>z+ z`mr<~aQ?KZ}tsm-M2Tk9#%Dh8xF;HM80nB1V0gk{J(Vl&|!;mH}%?LkBPd6EJF30SF3D zy*ZIIVu*D!0~esFqgba~dIAEr)%ClaUNm+YzCvHHyFMweOqBiyN7Azr|jHQ(tA zZ9eY5Q!wclZYamH>yuqA#k8xW;ttbe4!^+~esI@7v`51c&v_H0)TsV2ZgGBc{o?#N zr+C>Yg8+*~#}S82PkYT`(}()7q{xFN!E2RXJhQqgvcSg0WhJ<4yD+4mdec`5AY(yWjGurb1+0~2CNR4TSkDE$bbQ8 z$++-E^u8OmK2^nen!o~e?~xkIq~qczLJz&Fn5^`#eqEzAu9R)Wu7!M65|q`r$(6Nw z^=@>_!kh)^cpv(d>W}?1+E=Zzp{7;mY@W9{!d*&%Brzwh-e3-`y`{3BwMxG4#}(4Y zh%Dx~$I^RIJPGHIk(ZjDr@9I%`vKL)-(mn<+)Bmr7p%g62uqEy;Inh`Q_A6$-!X$B z9szOes_(DVpVLO-h;LTI%W$szsu%1c57>4m!a&`cY43XKRUup&nqkpgaNMi+Q6eTQ zr;h=kK`?PwbN+1J6XWzCb%`Ceu_$Sa#k*KXU{bj(ejQtdf?||ZwV7Pip(kBCVE#Qy z{9w>a8cyQ~^!ldnVIC*n^trSNQM(pcAK^ore0IDdQ%&`5iO`Z9#>2br0az69NY_)i z%htMQsH0i0OJDGp_|02K7c~2n;yxi%Uwm0sUxDQKCT3Q4xMw+c#Bx^GjF(Q9^k(L` z_FyNl=eOov^0OV(HVKM`-+%Ahf*Zm(zX7?_?B7x)|GP{5Nyvq$>i-(17U)|5*VoZ` zFw(V&ta1Z8jo5%2>^F?2!XVP|JGaZ7aZfjEu8V{dl0wHs-xJ0se)=WRk!4mWC49TJ zzQlg8?(<_T>vPu^P|eXF#B&J`fD(sl##q&Glxg#Hy^HNIU{&PnjK_L)*tYf=DoAGme2_I{9q zVMdT+H2J8F5_sP=rC&{XA{wX(>yBS!hR5h{cmcFHNp0fUtkS&v*=edZ6$-klVM3N< znpJy853D0Ht;q81t(86Khg>Pp!=i{FO&@Qvm}j(CgH%se=e|PK_dj`;$$;TQW4k3l zmKA%7BwBEC;sQ*IsPSrCtN1yeEjJPe&!$e8VKb|7^B%mcHz9Kmu9)mLF>Cn~c9LDR z(F1bFUcI!+D*;YIyF&ZireX*N%1-r4kvKMn5ZhY*m$8P$gC^>ZiF$U+Lw~-ip$vfL zQ-@BD2!6@bgWD6kQ7YbXtkJX38Y@H0S+tRr$V^bdfjh@c(FHvg?iC#1{f@yw@aLp( zAB7KZjM%XX6cyH1Of+V>1MTx^&f}=lboM{*93P1q!V?@Ci|13gS=ueh0Fz~TW+CevO z=AH?SFSaw!dC9eV1v_;cA4$ zH;9SJ{)_X=#wLh4(X*4d1QrCd-*DlPD}VXl3RhN~8d~NS%#kWFgTI^auM0Q?XF#d= z!2PZD-ILba%=V=}ju(jYM;Pe`~eyi9;-Di zewK-hKS18M)HOv|ZMGZ=$&7l!)iMyB%u^%Q$KKRxM+Cb-fB$@gvws!PtMi;HMOn&@ z272@6gJK%dCtXjbDcO72cvAw!SjWukPcPaiqavh7Rovu>FZUL1#-(!)O=h?{sds;t zuH`C5_^cX-C1&~cu&A@zDcf;>&ugUjVW$X6Rv*H`8&7#o`BY{lJ+Ym78gDb|e!BF1 z)Fqnq8fN`!2@p9*v6$?gZj3VY|oR|Lz=S64kn4&3=? zcO8w(5~|3{x*7G4&tzT*!-<5Xe+w7%X{?MHd$O3T%v9nDqasUa5M*!C=RCsOpE znd@K$K6z6CDU*;z7bvoZQ-?NbZ21J)M#QwNt2sE=xlE|oHCQMJu6`nk>>*D+6bSlq z{^FMqRAj|lGr7HrYMuxSfxWg$IFr~CGeYRkTvd=)N!?RoF9gRd*9Zd)RnW7DZ7XQ< zd+lML$>+qk@rx#a))O>XG@)^?h`;>rkRfZB@&AGUvH$6RYF~e)r2<(C6h^RM;IPBw zY{pWFtlJ1dN?6ub>QY!F2p*!AQmUVD$K)zTt6LT@5ynLVRFavVXuhs|Vm0*SiS+SN z_$+vPmG$HP`tc6(9TC0qs`~l69&)HyOa?m7yqp|E>7E&!K%5xT;CLJiy-4zpwLH;4 zQ=B9t{umq;0az2I0k>5(Rs{J0M!E8qpb7vlu6_9wr8_aV8h<~9s0w@QjwR+w1Ea$* z%Gg+^InLM^=55q1&>nBT>WejAjH;p3b5K>go6#v6zXa$`mYityPr^@{dVb?I{YW0G zfyK=uEQ!4JH64wT1%VYVq%X-voICW%xB5o9Aui@mR?bWaXyVw7I(?i?a#h$+py#ld zUH?l*BcV&3lBu*-uYOSvdwHu>Dj-+gCxiV8Gh{$EdKs(QK}sEH@V>%SS1&)Su=-L7 zt!OvOU>b+FxFYFu7wwlR9JCKxUa|73lq;1gE+g`#MhT^NZEGc(ce13>Vt4I8qv_@d z4aDN*rVNagRp*cuUpM%ydo}r?Jx(C5;`^U#mW13o?bilAINlQ{pNx)$+D&n~@zF*iPeQP7e){S%?T9ke^_Bh|ZJ`5kB^18R8#~iM z{21(C&t#|u^1A=j`>)mL2{M7~Poml0QwArV=bQO3uQWsQun>hMyeCxcpZzBG7tWa9 zgQ+P=8|wz;<`(2uYy|*A@!UC6X~k;eL;$TEWLk3M0fih1!{#*sy0{oMdy&4-w@&0i zWEyb1YYA25&4gcTgEU2EX}*v#;14{6dos((E)JQGqgClrR_N9K@DVU0)(nc{4pukm zU9im>ZqmF~9dGEOoQ-N*!AfSyD9(01Id1gM&B&umPtzqM^5r{1q-N#`Y%}`_Wie9WDXqh~ zvu*m~dH1zxN(~{!d>`tyvVcBu6+2EE3pXpI(RU$ol=&(q`^2Og!4*~o@9}(u4CIo_ zRKuqbSZ{uu%u%Pbf&+kJShdcM==EnFtP5Bh65GoKyZL^;HZfvt)E_l+kVR(zCYX=N z``VIP)L&D=h9>eXRIiYW&bG{@uv4#()JmU>s9ru@5LN`=nJ=JnMYoSHo8<=S@du#`lX_z>xa-zI?nYhC_l0RE@C+zcj3 zKM+_W)}2IxM*@q-6{PjfN*x^!$C|AtEnH)L1mPN5hM~OUI+sC_8YBgnoN@GHX5;>m zL0{m*>+=($A%;VzXYWM<`6%l+>(E7nNw!JA4(AYl49^hR5ZaJ4BHcITAi9P}rGOPd z-gO?GDcQpMs2*Za*rLLqOK-64oo)z3A=vFB5<7C5&i;C=pb##6D_IEE$2htqZ7 zBMsSH&Z@xC&2Df}RYwwk62ZtCrl`*IC?*E_-YGp(`Nbmhlla!yF%6YNGC7epxn-SY zSesI{L9CTFz!2wGD(miOKC`?c+PWEB`HgsU3haJ9QCY{_^Nzh!h|9dSxVrZ=*XtOe zq0ZFBILc&uXmp~Qk5ql0Q}JATRS8Iv(I;(F*de*6GBW*ZHpg_;+iQBllUzyNmZk6D zwl!|E>3yY|=vtDL3M;Z_L8ZCz`B;Gmg{VHLF{MJ)$T-fa&IyE0@xL{gpQn^ZM(M*& zvDg?e0Rv_fQ^)Rh9G>_4FE`-&hPw{Rjq=K8?P#xPg|p7Wx-fr+4dSMaS}cy5qE#rB zXank{_l~CSZmwa^`wJd>+e!Ya&JQN#3r}-a;boGLbKc09GU^sd&BeWU)(pOzDRp_h zOzySmpYjtY#d)Z9YsLju4?s<~E1Za8U%Ir_sFOM)0w~lcnGo`MawGd_b|?A{TTZ;$ z8?YQcJ2bR9DD|{4EyBSD5#_U5$7cz4l5(G%I?)lUPV3B%1o}o zR!UgSiQmBEm5T-qS-!}y16do3P!~zK0^|DJr%M*gdW1^cM5!mI$d)v?-Qhc->PB@i z`6Tl3+af*&x$~49z#MnA#-Sg&pxV(&;-3?Ed=>vLGa^r>IU%+}SXURH6sQY1h~nBU z`0!YAWuv&cV)?4>65ON`ioDAb=|7Z9y+G9M1OCM+xJc!JoRT}}(E|0L{5$E<$`ZJY?PSRFI7e*O+g@8jB33e=B zMdN^z65|I`RuJx^3=0vIPmtEKuJ2ik*7))X`4}NlOXL1HwC+S&WULmw4!?U7? z;@R%x265LLxvgjzRRU%nED*(|!E4}L;Z)($vF$mIy0^HwxVvaA0r8NrCGi-tl#i~!wj@!2t4EF>6*z~?Xvm1z^iRdsgtERu6z!(~0 zi`UFlCu>A26OnxM0UjADFDFV<5^A$JKZ8RJe8zo)VRaCz=OP z8MH>J!;KCTs92)(tvYb2s`{bWMtZg9hBV!b$_;iWnNT!`+?27Bu4hv~M3?r}dyWl& zK+=!b7y}EKMw^-#Y7#(ObkWhrld=0Ky?U1+=N>t;84fV4wX8~4M7D-0f#W`;HIGkM z?w{A8kMeAD8fp3cBdVAi@)dPJL|1=UmUzoB(o~t&?VUpdPp%7PgGkd*c-D|qT18!3 zU-6-%hzANy-n01ZHQ`G&(k?)PP^{hNn>&ABrOvk|Yc_>lcj<;|DK7UKYZl>6_&{;T z#J0*&2i~CDoU`Ib$j&)1X?U$cEmp%+N3-k90OHJ?`g8?lC)pO&(UvKFZ0Uvh!C4_~ zd3=SbmvJyZ5gQ1i*WWx>Pz(9XFC>}%XGr=p*ZHTI6i`!kg2sV>ro8&*+Z*9LBlLce z>h!7Lo)Jh3DjhFs=2?82+KS8h&^ms?gg7%T`y!u-`w1b7qd(Z*Q51YFs5FZTOIEP3tIhu^B~ovj7pR~XAD&(` zJZ{G6rtttW0=vv2i16pa45+>jB20K42MuB{5 zaRIq9c{AmenUSSW0=!d9dM88KbcTcFNGT*$8B`fc@fT!$G|@W50A#1R(LvntsS|DJ z1z&Sbm7pEq_*D87pfy4MOv**|I;Q7<<$5a;*q(RfZ+v%o+`A$+HaVs8^x`QP(NiBk zk}lm^LhHA%f1?wXE1g<8!-lgeZPYJl7NVdHVv9+4r!h3<3*Ov*sku>fv7d1-G_rBG zFEzc)GLwZGN#uYlCsGQrBDYv#lWzk)B?ytthHN)Ez9=y64TTw~3Bu8mw9z7~2Q2*zMpp66UYL>4$cGyunmjKyL7JAt`3rCRtXG zd^iU*TZ`+KOJ@x?CRjQIR5)178K!sRfP*^QzmOytV9|b?asVqZ2Zjr;)on>?80Ki^ z_W7d&ac+LPV8QmjQ*q*jm4`lszAnD%2V+hVE2u)~@GxZGY`pZSMt;e6)77cCgkU7SDAdA4#8RcDo6N>B_5nZX#Ou z_**_vCy}S)P^5p9*9FS$EcxL8x}4Vq!6yT`od9fhoa=2$Ag-^!zD}}G^6V!G^RG(y zI0ojHhycmh{&3ji-Y}R05RyoNO=#bf?W`p-$AV0oj76}8(WcGD<`X28WGPgd7H7;@ z%CxzZ`xMl&;WCP(CJQc9zZhs1gu=eU37g3RjaU zfhRmA#-W1XwNfRbIWk<7I)n97$5{?25XsCpua2O*HpkO(qzedl4q)pqJ$u&dkF(X^ z+=;N>i~b8q&hObcK=G)m5Y1d{x72xL^z4a{qHfc|$=_9*?x zpVY{8b817_pn$|>T67Rw8laynZY3~h7TGF-wU$C?2aHj<4auKb5P#;K$_#R4FlY9hKo1H^X^wezv?H=2-y+Bu!92Qmn{BEVg_BHD2-?p%woKND8qZjIaN5 z7;*gtBWX}-2RgBe7=T1t$NZ~#X%ya)1D?kap7RSvivQ?d<|O+QA%o%{%xRXGmX&XfuWw9p}OX zUeuqzmqb+bT}m=$Sn+j3{0f^IGz>OwdQecUz=cMzn-N7HDA%u$1}1}lZv#OoVeHwo zX} zcfpBSp+UOwhxb&@5uaoV)I`(|tv?JnzCD!6aF&z~wTLO*#Pq3)X`CXJW*t)*aDUsP)4rKudO75f%WkMtKw57S8@1TmVE6l!Pl;U0L z{|JlgznJI!AelTf6qb9C3C#k~JG5nw(hEZ<@W^xqe!W!IUo1my?Sb01*03m4)>69X zP;jBPa?N(@z+Hx4qmFU+fZ?oYkNTC|MGabsxj)($jiIuC-nm>eG>t0wr`pm42^ z@UBgwtJ;;l@$;MQ+o=cb5f+j3RC8s0UH zM`uZF&}y4i!V~6=AnJ>D3%@0t{Yi-UUVj+wsy(bJIWO8X%%(8!dSDlZO!H?RA(Hk| ze?(6#qy(r=@;`2(>$;)#)g|mIL!L4qI1dNkXWfLnLaVA+|GcRSW64w`r0V3-5m=^K!1RCANxOn-_HgNt6x$(a zV!Tj@DI{1A92BB|)kz<99@Kr6-v62g&=g8EQfR5D@)GT$)yuaQ~`A2+PFQ02%mA z;6mXSn0nbkV1gC3iFaEAl}f_mScX!C926tx*KzR?`<{k{6cWbFx8F6iEvVFkInH4YqRlSbITiwAR8*o z6xVF|!cuJjqVjm$*Q4Dg4sNme>0QT*XNxrum>$DHV5(=%cku^4_`;Jf(LkSh)7cH~ zb`)Ld)!{JPR*y$W5rmE48lcp5aOjAL5x`#CSWV+{Lr2s3J(n3^c zteWoA@now@h8wbLZPO^UMZZiU!EusyjmvM=7COe^cuyX^>Dvq`$}rj$$rNh*RBh2> z+}*)sI~Q>>cW-jsG%=drk~ivw;SA;Fn#H*dI34(*Dj8ZIq%*JDvjdvmkTP+VcBd1f zNI4n%lF5dCL=pW_e9}UoGZ^4@mH1F2p|}p4C2=$>>R^Uh$(frg+TdSE_*^!Zrm+c8 ziyG`~%Uv1ahWsp?h0f;`h&9smw)WE@bDX+{$05qN!XX|gFwuho6Uf^kcw@j|zBM^F zz{NUN>NVshK1uB!hg(|2RM?vrZc>9EpbQ5<%-X5@uc3D6W_8_x@yS34s&+F#yMXzD zBxw&|eZRk9EWE;|vxnwZU=#cS8VUGnV{-giTN@u~>S_(l@0|_i;2$Fap)uX?zrbYi zpOs2H{|craI8u=ob$d8Gq;O9%5jL$fNCCp03+I!{1 zqjl85aV8KQCu8of4cw-Ah-E~hHC~euMd@C6^MOX_9eN2Pc6~-$EY_uU_Tx&U~ivr`WS_% zSPqBKuh=02+Zs*{$7bcp>&;34*UT&&z=wRF?JU^uy;8x&ZbmCR0C}i+&Y|fj$TXi3 z+o52lUQoG|nx6)WOjN2dzamrF5|Z+>{VW`MEkk^^jFq>Z&Q2Ny7f!k;!hKhBN2jFn zE}_y*ok}%hZ5Q(n?tl}e3SBOpYoi0}Dxz5-V{3jvA1Zk#>6N&V6r0Y^TsDn`fztfN<1@Nwoq8^}ZdpXY+NZ9yp`*N)O&^9jjh9Y!J4kO56Tuz&nx0u#%@?n3JLA0?3ZK#mN z)1)`mF9VIwjBZXQXtds;IHybeQ4@ z@6yj)t^|R4rO?V=7@uJ1LbysZq+QRLrSNxpCLF2=ljcftc*XA4sqQHl2ygvael z7K%08)B?iO$S3vrvH($zmR!&c$Zt0c$M*r<+}O^5PNux^LQ4Un9q&?0eb)Q<;iODU z-Q)^e!vPSUEKo-SJAdJ69Mw0&u8SYWk`d`n4vhWqE3_**C^(Uyk3(=^&AcyZv05E7 z^Ig?PmQDd>dc-=?_()2xYfyxRjo`?CWO^n{3Cb@ZJlXtbc;fvRJoRysRygnUkPhd; z<)AtM@-hQ~U*G%+P7qa%O9N}s=?;0x{t(2o*ffllmvhKwOXz4M8GE4Mboz2Fv?h?X z1S*=Ipjm?VP<}W?IYqh1Ir49(Zl&&dj$H(k_plDJPS$qU4%ezh(z(v(;Sn0CZTjLQ z9U=vl>%$(*=F18&1*1x|1Bvv$9da|34{;(kJIVI3*jtC~lmQ=cX3tTc0f=q1`kIPM zMRt45fTZ|C22mG^bWh6luvFTL@x@?TJ?@Dq6=Qkt7$duc)|4@OnN%_fks>)Spxr=} z9M$)5H`PHj;CEC^#Jq^H)Sa+Z&v}-a>F|S3T4t8fV?_)!4DV|DM z_RBajl3QL2qugHA-lij~9#0V>z>hlJ6WD|9)Ye(MQq*^0rRrIRct10vdfXi|n%9us zXNK(TqwPw-c?qy9{NXp0W(d<*{v9QjiJ>hAcq{tcCntHZG|B|6Ic7*SWWq-9E;WEJ}uNt<-I=yNPF^ za&K>^(3aSRPayR8Ny;ncp5Iy`iF`Vw&mn|rIDU)Q1s&&f2$pBJXGt#cO_ynTlUfMn z-c_-o^PL74tI%D1x0M(|jsh{Y`j;E@t;U=8B^!gxF%#CB$Q8%@*g?u5$y3pW$h6u5m9;vQc$XxI!#XihN)v>gJOBs&8KxL zn)>3$b?tek!8!#@ZDX+`iY1r+eZguoPN4%oI}gTocvE=!Ivx%tyWRk{N_$h_-_!f% zpGygA($FSi64B$p%wD(*o6>6scwY_78-Js2BP+qY^HCvZ2~uoHj_71d+d5r zH`#kyHe)t_@^Ao#(!xqesFVUFgg=om@i0okPQa!^F$QY%XbYqD!O5A>1w3lW7)r22 zhQf`(?L+Yj8z`YLz$VwGc9#v?1m;~;?&JcS0(wrGUgRcI+Zxl-IIh>$R`4E{+I8pU z1ynh?q@N zjX|TWoaX91qsu*>&W4>^Zo{}Rho1Y!r%P!0EIArChL-nYqL-f5krIu08A%SY*lR@m z?-2>zutpHeeo_?bHYMRrPu;%u&MA>U5%%Q{N|mtUH6?Db7*WW<5^n3HIts|Opi->@ z(Cw{oO2}KNEv#NkFPcI%je<4ffvO$aZM!m?*Qtq-eU;+X6F!Zbb5Pa$o#{W1D>N56 zM<0hP*$wo69+&CrdX2C3VMtEa%?;9hIEIZE)f05Ym%oGe5Xc=u$4gi~W56SdV{K-i zjC4do&-VPLkBTDSB^LVL?;S|2;O23TZ0`PD<4kpO2^RPqQCFL&8>v#~rlyVB&j9P@ z_s`kZOiHJho*Xf$4!_u_FwBnCU_+4qAmolD5+Cj)i~mVZ zXl>dBIt(_BE>1PiA!rwPjOWmp|EuSm@LQ7vr{NyTL7bgOqzOV{!fgn5j=CLDm;>Al z92kNt)|#BSHQL+-b_s!jBY-rLobr{@ia4{r_!ydgzV^&$-oL=1W(AoWFrMU+LPdrjbyA$0>)W7(B(s@MP$ zsA9~0#md3)#)n3SCOBatz0}HL=d686G{+aHqQEV7;3V!tepsCYhCToS(XyKo$kDe4 zJ$#t#M92>CFdH+H8*IK)8T4owVWHgd@ul1$?L)o`G!h0z7~w|BjcfsbSq^DW!-1KyR{h-})H**L4~t73|B z{u~Xn@8NzL@BT=?u)QmrzM?tu9Ew#xTq?XTSiv< zla^1W(N`62`(!s0>3Zn3#UHJn?{g?QxA5iCPaQDzYGzfWb)DT{w0A%DySCOy%{V@Y zmg*dtsC6dZ?$}~B${np{8}{O`*zg8+3DUf9ljMB>Z%xd1M@S68p9?yg!zhn!89QkBJ7eZ_8~IHiwTrd8Fhr4k^m z$T38is_|iv18{UGn^C-C5vdRAJ(2I&YttaS8IQ4GcrhF1?72pZ*{&W|yt*J>&ZSAs zGapGL69tGUJop+nLPs`XW_zMhb8pzx>d8OWqYHD5Rn=aQGQSp4a%hJqJ3@WnKBp=B z(ZVi8^-xW+!J@&Q+cP3qB^pbF%@t40JA+2Q0QOi2b{J|&FYl>p)qG7kdo`&|uYEEr zb+LFZ+a{pVtT9^nWP9dEK+>yl>4{L6%Is}9N@=s!8XICXyxxP02pa+3={0)r(O?RS zaG;}$&n)UQWHZD1!Tf02o>ZW=(cBV`?MWGe#Y<2-Lg(3clv?O>(7xv+_3{((q_THu zlk!S8=1_rOYr@+`w%CTh1SsFB`D%wMtmP?|3bTIY1l#`RIt({*ZWQK_Pli*S=Z)sD z&i@QDhnM*6$V05>^vw_7z&xu8+9D%b z=7)QgqgkIugXYo=+O@{Ai?uF*TCfgS-(SD=aX78UzTJp@f-Zzg$*Ib}W}JLbIa0aH zZ(LB#ys!F{{}QpRax7O+ZPFjQ;+6Cm@%fqZeXg}w%}vkuFw5L6Q15;^X#i;gvR<>( z@FM`C3PWFh9z}gxaX4Q+&e+2kPUU-DI8;vHi=J2ek2ks~q`nH7r$qYs9=NZwh<*x% zu4$E!^Gaj*MU6b!UqtxVJ3eT)0!vVisJNfT@QQ=RyhVJNj$Y^K`pcQl-`l%4zY6^f zJ+#7hWL1EvQ&+zwtG|>X{!s?^kIeZ$Bac}HIZ+XG$gR6HH*!(kBAt|5=L$O!t_Gw+ zi}{b%Dit23W7wn2DTN~bRl!1eL>TJ;Y4kl4 zHce~{$>f?*ZL>-?PHYgf#F~L+%b+M+vruJdrnXD^c#Jz_gH2Bbe(tJuluNB0UWZ+% zw}O~-LFt(rpNTRfV>p8J>Cc{Rs8g8l+On84Q^aY`gD#gE9alKR3)$#4V%yLngyz2I zU3wye@Axp5p?ys3>7>0TM9^T@aLyAb&JQSooTo$Na4HHgL#StW5`}&RIZADQ=gq#G zXd{XlB+pusz@(_t3%W0)MNR1n8T{+|?jZby1_W49_yc%K`umDPqW|E1MaO@D`KL^i zyYiS~FECkGjBXCIsNEM?lal^@aI{@Gqi-cFz4DzH!EjuSf~@nlmW+O!^$yg%%HV2> zwCN7H>r{v5WY*LDqu(pIQ%FiM>k!Bg$xu83TH^;$`cVFgC|FsGc@^OM$=erEemJE_ zr!$2F?cgg{$imgP#C*-|#-AYrH1lPL84w#|yfFl1A(yoB@vCx5SUb_w6w({J;Day? z>8*4j!?_|tNY&X|Z3Sno-)JK4MSEyzmSC?P7iO9m==zEiz zQPlg4oHV>JNJUer2qK^M6&=|Sw^q41t;B=iJyEL+7MF>n5x#)9f^|*?;>K*-!#MON zna+Qq196vbOe_9aWbmND`vvv%1#;j8?{?Auknszp2}ty>;;GoZdN?W|=AVGy-(&tS zY0~ZhHyZ~FCJ|FtHy0B#H%WJZnVXFx;LpMj2W15%Ao5vI#Z|?=VU&bXBx~=%7V7$g z(ws2y>ZM{4&xw@3Ov{OmvIi#ZQwxbba#a`LUCkl^M9i2gQtzsh^f_a5(e zp)u}RNP~jrUW^;j@#4*U=@VM&R_Lo?7GAdEm5GF~Bzdzl$M6D$7A5%vvlM0<>O@{D462<{7~d zHAk0>Ur!#yxH)lhvvncM&+%Qi2b=(W+|=?Qs$ctbIwfFw>o`?)Jh{u*v3(iOu1{;; z@TG=x?As&OPX^dD)tWsA8l~!QA!PW=Z2tyA7IFtO85wd{r#~q{^^5FRpb;kyuNLn67 z4zmjXgP>9>XLr6X$fS9QvoDtWrbRl)lfir1_KJM`hp+`diVdY2p~J;HZ;DIh+`*Yn z`J&dOQ9GiC41G8G3xCRyd#L@>?nTRpN7me9p;Ccxu{L z53cs-GaklWrwhq4ac2lAVF?FMUBe&hR0eF_LD?AMZumk`!3XD`v_&9R#S@gr!QFfy zDqXVKuyHzNR;Q}|o;8$n8kD-0l5{vnj2O)3fu^>7)#7{T)mzIula+tlwizto8dLz| ze`~_6OHLX$E5kMyslmF2FIPS3<(4YAfRZ4E($@ouWMY85Q!v-Ae z)N5X55zc()uS{l(4R;s`;JO_Bb|e1pb@?;n^uKahv>^p$e-PRX*x-|g!AL@_LJC6{ zoDwDBuR5c;Ji_x!ODhkLjkixf7i9~TiX;ZK9<&||U#hG-7KMa7>lUi6csYz5%`$#G zd%@CKq3jkaHt6xbg?aX9O@AHvxrTG)V%t8UIvZnF!5<)HN5d*|4!EL{u%4(_@f@Jl zxD_eFp_}1ns#I6|p|IkQ@O`d0A;u^PWW4jYUPcr-8 z82*`S_}?+Spk^VV%nBo9-e@|JO-XX7wSO@1V!hb<*-kDO;^uuS7hhf7)y8V35@^t1%^Rr6MNITI6Ausazp{d|_#e@ChkgeE; znHVIiM7Pt*IKZPTpRQayOrU3`5RIr^NpvfnW_c|V>4bLpzPCwn#)+rgD{jD}SI$_duCKbrj>KkkYAZw!h5(;>LHn0TxH;T`_)0TxOMcD+CqDl`t8 zS?j2+;-Q?%V8|&95m*5k2!;;FgsRGZj79Oosbk=8?!g`li|Wcd>O<4B-x1Y0b}8mc zy&=2i7bk2L#BTw7PNPj z3A2haS0%!-dd%2cM22@nvVOLO zPF-7HS>E2LUh?StrRTL)8~*JOhzb91cQ4_8p!R1Qdewh4&~k|Fkp*Kxm-t>cXUQAW zxKMtJR#FF3je*sQ0gI{cmbzG9R0I=cgXNbTDB(PVbY9rMR6`mF6SUTz2zRi1^$u(c zULb%hz$`#MZ&f_VGEkr&##UMtpHyK+gb&!U6k2DaE4?B)L9-P;n|7el_aCbaos5FC{=H7oW0dXT#y}iDo0ghi>jsXBMuk<|3sl7I=5uQd z2y(uCPpf=H=pTSPc0^Xf2w?i@k>&tSQ$IrxrQc%rFZZ8+h9IJjUKZwmHLUq(+ziK{ zjPg0`9)f`pcL^D*m6W7aSl*4R8$2Y+fj$^pXcz-78V*(A`Hj%1myMK}yuSR|n(L&% z-NnVjBjOfzD5osuEdx6PKDtSPb|6TbxX`Ky1|QfFdm`@EK8G)b={}y=M|1eshvg2O{ ztJDMZ|3Im^l67{;)%kkZ7vT)K^GdHH8e(V$ugygk`!4|EYQkq)h{&CMqUl)F(wgbW zI%YP<{SK4O+k>}YTc`)9rKsWk%BJ(du>fc!lQ7IcXaR8)$S|_3ZEa$ip(eST0+&Mj z^eO!+2XBgL*`YhozWRI5ldG(yY&(aBHWU7GLTO?2wS;_(TnF{mQ7d&ji|dLEJj?!2 zvtW01RfcJy@w0t7U2aVwh9nj5ANJXXGcmlyQUTo>{zk@$f^8^ILxAPxAp<0;tfwf+ zD1mB|TbOi%${D{QM$^W3^#|n9xX`so^LMp&V=|EV#LdG( z6(1i!$K6#=&9lIOMoyQNtesMpo0yhbO_ZRLWP~_F2C4bcNIgRod;rWL zD6%oQXdc^pa=th!xP3O}&$bR$#v9Nn`)?BLjYUw1M<9yQvAKUyob{klUj=Z|)4=cV z4_p7aO_fY-fohC@8BE-|Z7(zafKq+N%ECjv zYs6*Prs6OfG*K&Yv2w$}Wyx-hUX6LChFzpfYmEtIAap zrg&svP97(LZj&Ph&ONx4K-vxjWwcKNrhqUK20=6@3o|^?tA*kBA+&Bxw1E8gR9kRS zFp&$7PT?B)=H(f6@Z2+58b>kANPNEbW71dQC#&z~PM{67oA6KGXo0$I$%igf1lY zIj9ggof+Ww_eO*F9}`x!F|!8J%%6uzl|ODoz>;CrQ6PM`GCvOYLUI7u0lwtq)U?Nb zWrd6N6f^^eQ_W5jqS2DOy+FSh)x->E6dswL2$WHJN_%>_c!bOg!@($|rBs*yn5N1? zAc1{Bjc(1teBD1CoECx($qsKOEry7Lz?%TS{p=e~lAOzAbS?*2UK3!WZGba*tbmv8 zL>FfRH|nKW!m{d3yf6#t@oh12$;3mvn~Gj9A>$v@M<9*o7;AWTR|U)~frAy=hu7}G zqAmn^4%>0ov6Ddq>9sLQXH+Y=@?xn&%5vrf^}PdBpkf4nKNig2KlPI+FS zhQaqM#!5}m!i;3EF>8UpP#h8XK3Y1vcJ8O)3Q3#o+!q_zSj{Pts6iV1w<+`Rr#LvkNC=y)?or;Ki3BPW*WM3)Nc zf+9}MfP4{wsR9l=;8GU`+8M+yoW|b6?*28meR5d(os;ivA^x2VnA%GB81D$L-DzE2 z!Z%<-zz;~~a8G_sugXj9-I__W4?}c>I)s`0hM~x!LLUN{xvzt_rI0=tLXw%BAEIvc z<-LCcV8J$Z%Ix|4p4SZydMD()&l-As5cv+o8LJnyDM+jY{Z4!s#TwQHDjO+ln(?`O zKjxr#dk+DMBMXgb<`)kqrGCZ|?t8PsE5s6fdA{cHdkIN&r zymopS&cbdLgwMnU`Vm1?_g;%r#7VkJgkEin_i>XPn zsiS@?Uo2@aMVs5VD2D3X>2Nd0F{s|EXWu^L4yv=9QJqg3jJS+Q7fNAhg>BKwq=X`| z!aN#h&|#WYRDNfabOXwB=dRXh(Dl7eKN9dXUBOJy7_ zLo-IK&s*76ah<4Omo{*hS+i43-k@}ueJ<`&B2$96*zJp9fm*h32)Y5IvReF#I%A?D zB`v|~F$(&gdXJ{~V18S>2?m1RSjBvNj@Fb`Nnk92j{Z7 ziFy&cX-+oElK}O-@6^n~U_qrU+l4$^VnIKS*}X}>NtJ4JLuSIV87WAOzao{Rd z0zM^8%X~?m?lox-DkI9Zj_gzGC9Wdxqu*x)rncYC;l@y6Mm^$!|^UoL~S}W2YeF5WzGPa|wE)f9|^bj*L>@y6E9Wi)PSR6upKY2enI2g4& zXQH`@2W9qpba>Ux7tw{r#YIhfhm(r>l*QtP%mHGgDv8r48>3f|*U-j;@$3Qn<$JQH zT|U=NphBG$@Q1}&=BPGpT-u2Ux1g3Q*|bg$Ka6us0}MOz`9Ip%TjL2BUoq( z0jaLfWTHKQ;G`b(aHPiF%{*$%9K{mFCeKWc9m9&RRs;KiR`ZIwq9&H4+7zL!$ylhb z13nb{%nTc5M{;e-DQz2Ai`if5tlTFv&g`qxcj6`6hnF@RH&BQ>xpjr>7ZMecyX%aA zb-{sf&%C zzEG0C;~j}WoCoS6_e@2Y;()ta?4ZM3(R7f51m9dpVX1>v8{d9hU~zooL&vJ+EM!Af zRR4Zm4b1w??tF5FE!pW+=E~`Km`$^e+xP*WyxzfATGH&SM;F#BUB0S%mNSGUoy%hJ zqQsZxlUeDd=Ph3cXGg?k=ReUC6Ak|O8@bhN%6gLYt+m>VpK z^J_ffzh&PY@xRhAFRvaW1(SFVIz7*%kHx260c&)?ZIwWfCHP^dyyX}TY?K$jk>9;^ zETG>1O+`$^-Lsd~{0G8iud*LlmbCvMt~_~kG(Zz}9)xeVKRO0nDv7|}ER3a|&5hZO zRe6A3KwI_{57CF|(7cizL0(4@ukeXF^>oegeLxD6lNYe6V0a!hbgt3YT~;qQi@j0> zLS)_fI=$~}2AfuUvyI-oNMeqS_U=(Q9l+8>T}jQZ7^@g}&IZ>OX+n&}3`UL%4GDMl z#-IOf7!e;nAI*S@)4B$%n)1bn7?aV?w904mZEvwwCT`n^!Mm8$f{TYWWvAU|u|D>v zZ{WeaZKb<|e?jK(bcEda)ItkodLBzVXYXmwN8} zE}A4e3kO;@JKHV{9Y*$icG9+ZVrIeVU|J5#+&hoj^(_we%6oZW%an43QQgfw^4aZIRElo3ClpZZnC*s&vnBg<_tFR4!{4I@XgR zouXXL*$N^J1RZ9%F~s&x$^)NuRp7powLyhmBS%WjBBd%L5!;Qrt;5_3B}C?@V!f3V zyVgj%?6(Z59jr$|IHx1rBT;?D3OeAbHtRBP-Cr&yVJ#|A-ug^;s?PO*ZezOj?AlVg z3w%-@$T)aQQRxGqVlv7vrC$>*(gK*uaz0EPcxi2o>=_U1gEDRjJ?M{$fRQ>0SHD7|M|BiTA8%s@oFvtki1|4S+{` z2-R*Rv~~iEWpBQX3pvT&IvR|t(-Ok(acVKhgd@b}dCP{DBp;XFC};_q0K;5&MQ9mh z4$j7R%d(po6`RfnnGPdE*)MM&mTo(u&Ng>Es4i1NE6=(ij>D*2Qxg=yqFR&P`PJ;Y zr2~&EaXk!imLnv)Ins~i+6*aVB&GmuXjm~H4k=zBpsl>!v`Cf9kn}CCK#?JQUC|Rn zEy#0_X;B0SZ6|BSsre75rV3zth;px&Ye+3I5A~%BxuNg8B1hl`RK(fuPoR&Wmo8+6 z4tPn9g7iyuc^9hHD6(l?Fo$dNgXn*dkG(N)e>~b?m7cve6S&Qfv1bb@TYId5dopnL zab|iPCM1tPBjXDILcM-yajI%6yp>Y6*wB1Jsi0-6iGoIO91;mQ8^!i z!x8G!TZtZqaFZVO9~lctV1KDH8lPTwuUF4eSU+MFl|?2EQX`5SWst;bFD z93&hp2(uXq8cAc@^z=Wq6<@;Sx*Yqr=*CMe9nl?~=6wV+5I>?+;`+`n!y^T?#)Ep{ zu#$5iGYsc8ZBWGE6OP#EDc$NBUnFN6@u}1;nPK;B^_{NX@a5Rz;*=?!0zt9(nVPueH@35m zxCvc2Z0(2{H%KD45Z1Bm8TLjtaw+ZZ0?NE3?!Y<@Gy{G~EsZO9`aj-GdG;y&pF}1h&cM;HcDspmQEXXgaC)Ea8dUkpZg7Nr`81w>K?e}l; z)kSE$zz9mne`jpD`Mxk;=yS~QU?8~4m zB3jxcNn{on=mP7Q}dKSwX+?1#`GXzw)K+X+SeZwD95BptB?KkLCg9?S)qO z6T_!LtN?5B8d%(?J6(O6E;>>Qp8$KDpoYTw2v3~ax7)$y)Ug4LYJy^&RrTMPgyl8~7EiF-AhUaVD3RS)$wnX;BV;T-H0qO!<6tdz<0R8a z>kaQA`$c7VBg|Bgqv>3~9hx2kw)XUA>-?Bd%o1j+d=^Nlz61!gD^AS|?LIZU6YZLs z01G@2eY{mK8?t_~5QwQ*#d{niv}(w0zwhGx4^OuD#8XAM%FSmFQt*5mo9IH1xK}}; z&Kcy_Ddn)C5|eMk(>{B`rC&=(l16&Y$lSJY%}~3f2jzCs2qs;iOciO;u%%5If0L%T zLV{N#n(H)9t04DL&qG~dw7{_>EALM=T}JzoAH&vuUzjN1PS2{-=QWd&`E*BlcueFz>FBply*_O}J474;16D47qt-W$7FJ5m}V~ z{q?X_zj*YNdEVu?Hoer$BxDdr308#5X1WE0WBf#pbZTZuyiqZpOB?csluyG5Ywg4q zl`fg~=1A#ikR;A#fqiS2%q^ek(r~pw!A!^d(|O4>#MYTKxz0Xl$+Ys(Z>ZHfBjWoE z4J8&SffKGDeFLC+D8?Ci>JF@b+)p%&RwC4IseIMonOtG`c{y@)#`oq6&`jKc2f4q$ z`J()HGf{ANad3B$HgUE7tGCOa5A8pDx}dS3GF0nT|YWa`8*+%1w2<$NYIF_cR&`l;0a6*bC0`dD#&Vx!dKdeVwascL(1;ZNA}M zzW#i?9UjVoq$I$9b-pIH^GM>o zp@oJ(u_Q{#`*?QQ{^Qm8!~G<7MZIL{&ef?Zl>Bv1-g?@FY&yHD@>v~>tBT{pfrCqs zzLjTTVz-Y_ickc!6GwhNep#wj@EgkgiiC(|6#S1akJ`4eu$VKV9~AEn)u}qMznOw6 zR8D7wXu`-z^p0Y4bz$MIl{UyiOD70C8XZ}+-kcS?Wd{XEKaR0rc6Czxz=jjDRMnnI zTLLgGk4EO6Gwn+gp{AT~{kj`ns{qQb0WIXs|Eq=kLu3j_CqGs8)K(cQs~W8@z*OcT zA}AHNM!?xsU}|76;QRZBj#v#szD)uwt#FSWFGX_fe`!2M0sBizuRu|y^ROM{@bP%= zzdSshaJ^Tke5-wX-rpAyT1uGX?R7?S-s7|?1=Hl4q;jPq(GeoSZtE3giBQq3)`yFU z4XEepWKLSmjz;Vw?ExycLKDIfT*Nf8CRk?XFLX*0T~C@@H$?_D=sdFzFgKwM%F;ch zH4|IC}o(_JFW#{zEl6uj{?*nt4Ku@xoxp?6aA2MPHWX0qhLz0v;F!w3LXTVibTM z6MM6gT{5yV`P=KR*(tY^b-5#27&yF}RI+jZcVdqy`+oApp9@$%ts8z$#uC~#8jlks zr{C`Rm7b}jO6dK+E#$8*QNLn?|HdJ%-VP2HZotNnzo_L;Mk!YN!(QZ?40IbI;*sD; zBQRtGs)oot)v#Mgo<7N%h<7}wQIRVe>GUc{iPGiy7X;`-X?rNCCn?%CfyGq zx;=Ns%ti|O*POt9mlLsfhi5Jju;t$jo4{k$9qVcp>~Hm}pw8?%+r;o>j)4VN=-2k( zz>z6-UbXW;KlU4n_?C7Nm*f)VUWk1T_tHt^@~RQ(ruFEIADrx zmh={ZEa_dgrOgTn(H=Lv5=->V;w+!6f-*d;S|%PFQ_B4JX;Ufe$&Z_P_@s#%U7`;` za8ifkABkq_kaGnC7>8h5A9p`x92Usv#>9E8CV7PknzFhC*>lLAq^}miVvS8LJPGs{%C?b&&&%w>HodW?3ajMMGz+>6%c3?m zYGBmL?4}K4tR!%m(d+iyrl%k9-=a6fE?~Wa#~#!-A`I8q5?Z+HDCFC6Pt}C_W1$5-HiMR z_Cog!NIR*x|C^gb*gOy_Fz_=Ni$A>=7o3$PG z7&ciLrqW?!w1ZBP1K=B|ruqY`*cumzjAg^d!UJv1Wu~}&wAI_~5fJUwq_H#@Dab@S zSrI4F<)2xGk)sFZBn;RZiVT?*s`(^)WEEFdNSh*8qDF@3v+r2kG)*|CV6|PQbu(ti zQ6^Yx+Ygb&BL+&bziQLwm~so#3VdR)l~JqPPoNpwc3q=X4^1nRNpoqg;(-7`2BD7O zN^t?BDazFWt6=~cvfQ_^F`}4t8gbdZzWw3(twj42bkv=_kqZI<(^2C<4xg@DqrFFL zs7KuA=B_Z}62*_DA9ZEF`Fa;Id-CW$_Zj{G{==s((_yh-oyqPp26xm${=G&Hr*;pGRqBRG|it$p!WI4fv*blJwq zP=xqyHia)1fJ=Ub^H_Yq1T6iB3=aAUcESCr5ZOj!R|Tw;?c4Chvrjx+NsfGV=!*r- zWGh+uNC1MY!T_P^QGj~dCT@RCZ#T7*YGwY3b-A8?ol6Kwx z+wd|=j7s~Fh}$y+o}TGCsG{biDc2!RwznwwB?s7Du>b;j8FDr7bn zYKtheyAN$=-AHI)7{Yw~jQw~pGGm*GJ?E4%xhL!}vRGyi8F`hp!{{~+V-P1=Gb=fI zw|ktQ$tAYA|X45VQ@t53^$MB7(y^I4yiwE{7rHIj| zj$A|G4JQ~x`euTVqJ!QCeueE|Fq+l7#1OzUN_^$KEqpJRoF^ofFZ8)K##oZJ(ABMv zn?Z}=(`R#&ht@@8D{U#)5`Ra<)c*L4vpx66Qv^Y;C6OCHO*s z)uP8x)wgFK2%A-m>Fky$7=urs4W=c{mXNnY`NwTW&Xl6O+)qLR-e~+iDgE_W{eO_s zpO4k*t1fsF$O2|n9SODMjXUI;Q3{N9WRB=eoN+QgvBpzL0HV2CzFqw=G+T21v%euR>`@5h3w9TgA=ed@e0E(mPUYUnc9 zW?gZi4P<7O{x_77!@H*O@IEM1PBPsVAQ&KOpd}pbp*RRcTkjCynyXDvzFXfT*Wuza zbF85_VSTr?E+{dP!PW&mgSqK^50$p`nN%~^B!xsP-If(zt0aZ+iU82M_C2k_`S3nF zgI&;EnO0$&4BW=HSVl-Bs~ms1F3StgLmTb+tH{g7pxJ7KothL44smVs*o5ScUJcqk z8wbvP|Jq_#KSM|W&yc67E;U?F$wc???tH@Ts?8`z=QgU|q-bzB5nqnO;s>hReO`wm z{ts2;*9qs0v5lq0SYpl`(xPJkZqrY-ooo7q-Lcq@-c2WGtnHM=XF3xlgzh7oeyFQ2 zCD1*24m^UM*`*#IX;)Rh220EF)1*W*pUaF14IXV`V^jsk~u?BACqbZ_+mKx zmCM=IpnGFR+SXPY6|Ah5zO~8>U69p{^$jB`I4Xv?)d`5`6&-M%{M=_PD8&Wo3Ih}VVnu=xVNv}*w8gL!%A%B!UG} zX*l+pl4hVNx6)9VSy}|$oE|&5VLttac1rW6SHR^q?sw&Acl|N_TvaMaX#loX%IYcR zUdKvj`7<(Yc4u^Q+FVc1hU5Y_jjr2a!$8&B~~Rp0Q5a zEB&%?T~KftRqPnezt1Ni}CZ`*HvmE_QA_U42F4_DH^eMiLvp1{0ZP2Fsml!0xS zEi7#S|7gbiPD4R!RTH_RmO_r2^u0Zp2eUIY!rVr8D$MiTp*uCtaC6p-kcH9p0 zAjyPDRv&mMlDgY=Z3H07rMaL03I>ChQUm;fHtdeaw=BUq;X_mF$8EAoo96Hne1H>h zzrL>w!4H){9K+Y-Uh65vUAzbnX-iu;=jfbP?oWaYvBTwLCKDz!?8@m#WC6dI9E1o$ zE&LL?-^gQt9k}{ZXe%2Z25nsOPRzv4B&3d~1%Uk}sb=oEsffcty-bVZbXPO`py#0(_?FJ1?mbyV( z(7++tK2Ev@s2Dlj1*hW$(l(uaZ+F~koo7KS%?xkIRX<>gw5o|mI4h&BIyud9VXKV_ zv(23IE*a?L%M%_lvVdmgqC->r04tw_rt?^g0)w4H$otOMd)Pya6dhyC)X)J>z=dK_ z3rbHhA+T9j+=~4Jt?1mUmyUo`Y=&sH;BLgMl9D6Qm)lM>X0o6>?1bbxfz1|Q!LyC1D^oK}aN;wd>M+Rse z`pC+6J4_{fe;COzIUC+Mu+b1m@(t+Gg3fNdyEvMyC!%Cbm`^AV5a>@hGIBoCi42t# ziz< zFHTG<`0g8)pWoA+V4(MUS16p$Sv+6bjS)tl={Fe2q28UZNl6jXIdgGmWX(_7I)X;wEJ5hbX0Nqkt=G{E8`8u|A;!c(%OC5f);Gn0EB9|2rs z_ld$EgvsPTqNJYqX`!waV+CL_+T-HZP*-!QqXA;DrRKFl0)3`IUR2FRv|g4&)5j-->tjaNZz{Hddk5?`bsumoN?yGes0)%z8c;S5M@5ykwHQsdTE4Z!eGcM_ zZo11}j0)|H`_|L4J?F>0?^~$%uG|b{$WSo^+$7V^PcKo7} z9~+pC$kdkkx;G*b;qR)0;mgh~xQASpPKE5PDJ3tsX^(kcb0}0@$rk7qpU#v#O!#5S z^E_o4yChgGn90s(@MqY=VTMML*6;tSsfjy>ujsRwgmN+@pe1 zpC^T4I8S3-$=VrK7%(tH{YTz^?-zwL5fpbKLRy+xEFSeNptQ!5iPz6(PKGFtWm)ZaJ z-u*8&mvpzcSGBNrG%^1pFmq7(qq?tnudqRx8r1e0kt4oLDjJK7Y6m0=il5;#iQVX8 z(&Bj`zcne#ALSgIoRUfGnd2vundzOKetsSPZ@7B^C{?5$^k0?dm1}#HospVP8{ke^zX9hidB?n>ikM>vA>i$Ur>?L==LBugnw>x@wQ#c; zcZa0K>}v*b6-*tXYs&^B7lcWWS*d3u{GjyyLdaN%EKgqgHJBblJ>0SP1%^BCBh|#y zC+1u~hv1~j8Ji-tq$>Mf)ZG^hvDT*N(&Q7Pbyv<&vNs9~cZM>CgfwF@l9t^$q=G(AvA1DVUZ@KwEOJWE{?GUe+tIx)qYgxM*Xak-^CrBPW0 zMiANxYDL;Q;<;9GuzJbWc^ zq)^Z>isrMvz;Q(ZzrP>(&!f*Dp^}1$hXp{@!pYIq#?8^?&z$7HCIL)n2hIRE6+%f! z6ao_GnadfylDIIh^l6gvqhK{2pC_bR2o;oyMq`rW=oLr4)0zVHjj9WAe z!5S5e44D;deXPHCxVxvjxBE>|3$}IuX%zf7VrNLsUlY~KBh<+QPGk1B6a9-t{(g=g z9*~+Ikerf`FM0EU&pYD@&jqsoZIwXc()<)n4O=_?RW)s0V3f=L7*qHzl#rIchIh9c!>BXF?(za8+;OUZwN z4;-_ljg`9#Fq^^g&sES+>_O}SR*^5ws%A8wBj~d`yQA)qlN`<1=vC;LG<^Z!`Azgd z-BZ^{gn4=;j%RYQ-3{G98V62GZ57HLB1lz$RcMo|*P}}bQd~2*Goq3e=;S!of9#Jr z;p%#47b|b6VqYJpGoklIf23nrb|OmIML!Z+q*6BfawU1%l9x_NKO+#CvT_S9-`h5+ za~V-+&fVoReSWgP3(N<{)Gp-Mt@p~YtDljqK`~dIjFv_E452&M5!-)dbVGWkIWz<3 z_p?Uf?`Z(%UlFjkG5a-n8Sy{XO(RC(X9y(OWh15lca3!xXCKy|f>A)MA!@1;4pF&g z451b7@CC3?SF`m7A9g{pu)t@V>v7O=u*-i_=*=0kMYDMta`oIdi-{)wPtCMf#Z9Wo zcR_%R4m3)eTjxXBY@spPpvegqK8_NkLG{`#L)(V5%|Wc79#uzsk^)(5ObpLSr`MbM ztCXQ`+k=fD_a2m7a82C~mPj6x=dNw{2FzfpYdgjTGklcslEns?oJ$gE^iO@TjRNpw z;~s!L3%|>9!hA4MetHu9JUH+e-55Q?2K<60r}srBL{>fIS)pxUjZi<$W!Q9mX&(rM z{BPO*XF<##=A!!Z1;)a|Lc+_;!s#!20alp;`+Xz7v&$%2?_XSGLgrsB8BqQdfir0}sb#xKx z-2f@ZgIL03_>nq;ATC^B0eVwKxz`M<+JWRiH3wK8JaiQ0E%gn}L@TNQtVgNaeGQH3YJ$n#@jHKsS=!sUy!*9wCc{^5Xz1xh&n}>Y&_lX|M2d7W6wMhe~^aUI99uZ zM|ZHW5zY`Y4?#70zvAX#carBp-`hG=5dgPjN3uQ)88RkjxMS|)s)LA_~B`RXj>>OtR??qy-T zy)*5Wyt`G!GZ6BRD}dr4XTfjn*s>Ut>MQiSUA!N*?7Gq=SKiVlQWOb?DN!m=lOQb9 zlO~$ud?AN(N~qlFlhvErp!quales1{p=TF>*kt~8zx_q?zX}Wf9UFt#Uo;=QSx-Wf zl*@SMTXXe^l~?yP$mf>xgRwmbr2+l7jzT5}|Or$mOM3v+OBAF82}qgGdkr~XnZv58g2 z+aR3l&J%Zl>9yli!ONzD0Ub?qa;uLv<4(t;{k0?@G&tROfN#6Cjx@{M$3cAN0VPC# zDX3=pd&9d-DNgWD=5xyfw=B7rwa5%6MUPI45+iG$AKVB!T~1H}6-;n5!NB zs;+J68Vzm>g1ftWaDuzLySuvuch>;H-GWOX5Zv9}-Q5DYE6M);eR5hmxexONp4J>S zdsX%7qgQ$h5ua|Vwn{p`-_g2MVL51fiKj5)K^muvF zu_4IKd^FP|%XX&bse%j(pBWy@5Ez+9&;I>>|M?XuU$3;fU7r5ENI4gyB48QcJbz%| zUB!5Ffwjtz8YKeQ5wp{DIuBIsQPBN?ZBmE4E-aYNThZ8;xAFKXeJ*Q*kF%{EfooEu zS#qM>X2JxwO!`!+ywu2hNtg(?;zYeDKYtAk2R-l4){*Y7C;9Ux`Oi;*;U^)$AaCI4 zWFie@+yA+$l&pH}grb7_!zN>gJma*L${GYl-eO6Sm@+IkKt4iMC=xOvH_tjDquMzU zJ_9_0;UfC{KELFlk;@k(QzGJ%!u!Y0AK!j-I^e!+QS>{p+ngGSjEuaFtvQWz-n)&p zc-i?r9k-r?Sa;3qMtNR-z#wD5zuQ7$$TD3QagQ|!*yQPc z1`sfM_g!#yV*vD+rJ22V0|{Ea&HHQ#G$+rynNQ+3MX~_|XQ=i~AH2W;aA7bUgc~^@ zgqV$cOeir?5t9`{pN(g>T|WlcrcM$IHt6$z3yXSl7B?C4iJ5MOVuK!QxJK?<+=pkA zl7&q%k~`Rib1{@N6?@Dt0ZJUy`qKQW-(Wye0w^xhKJ-ImBH62UvT#&wXcVCGga!pT zQBu0&zom8ukpQuPU=T!!Q@Tj_pynXlX=Qll!c(vTkBa6U))oM_0m=;f9vOuI?g2a` zK+ZurU@D#!o<&UrMzI*&&+H;e75d7$-Ca}Aro>8K(5i&SQ=7Xq7@jT_<}F3cwXwsq zp-YANVatf?vRO}GH?nqaXVq(24hIObtUvnUK5>gsV543}oGhf3kW{ye2%N;|L|6A? zp6%X%`S80aN{7HcW6Zg_MMVWEOdi}>xB!*)+s9E)6^T>&L(vE=S4D&~<+R(}!jBx} z81~G#j~KkOp5=kQ7oN!QoLn(n4o8m6c+Pt*W1X%Bc0np`2i1WcC`8O_=-4lMyo)9p zONX1@{%KN3Fl+4vx%)R;2ryagA2HNAK5ik;__CzVv5-ljfZSQB`fK5wHY2OPjoPPT zl!M@ob+&~r8dao6+qp?QB ziZn{;Kx_tMNr!+|cxC>`Qz)2C_!ZL`(LJG`)k-ZsfIRkcN4=l?sf$HB>Q*}<_HA*m zmO+lE<$$OERXP?cJwgY+$|-#>yF0y70ZD1qli=g-yT}F4I-@!^-^gM!Zb>t>2K~kW z#hdZ+{S{-EY=wy<`;J~E9=an1y#5i|%4$Q2T__Pr9<*%=Qd&J`siTtjqA{IhN>H?? zq)o7S7TqnRA1hvCtEL?6eV|}n^5WQ?sk1)IOQM=mZ_%aK^*Y3lC}IVLIntTX>?)3( zWq*>mo?0czJtQzuma-328c$_dk^|e8c&{{|T3kt<>zlQ-1=rnIW-+yxE9&kalG;Uk=AbnM0D9bvcZZE}GQ5#Fn z`oyt1E$Vw{4s7m{Y7|tU)2*O~pv%vam6|=U{hQ0A^WRs^sZ1z!$|N@D)QW%QRq#XW zsd|PznE4Xr>UDQQ`=gJfig~W5sTPjqM0v=}rmh@Y{T4j!*c=Nl2^%fqD)(sty)2gS z-S9A-y9We=1rfu$Do5IaHe__%5#)Ww#R^>^+6Nom=3njevOxju~p!A6K7Nf3?cv9tK{8QKtYz zDz5cLSzt!%g{Uuck86_`8R%e|eQh4$7q zAIh<4d5%nYuC3D~+M3m%%IEQ~@={eM>@y+fK>|_@Ms!iIfnZp{zH;~gL1mli7mOQY z*Qr-tH|hIA%^es$#F_iSA|rPSw!BTLS9z<+^>b0@nMIp%HGk%}m}Ay&`N-Y6ij32$ zO8MW1bHkTrQ!s{H9WEPI9~)Hgwv;0mT<0yLirT_!8m1wyR;`<IpoLsmxV%zQ1P(aY{%o^{ITwjbh6cy!AQn zNwg71|A0>)Yd6KtIvIyH(Z?Am-#G2gE5vru3z#X_mMi}ru}p64#12tU(0SiqjoAGr zaw7?FPH^^DiSyqh?*GiV6m{&D7*Tmc(1oCrrlw+QD{)4tbEfkwW0eypNV67F7w*mq z@R=n)Q92aac9e~nH>zOqb+8=3@?qr8o=ZFeXGcZwaBXGSM-Ri-n3l!n^ zw3HneyC5D|^F{x47W99N>F>JlH=-yTIGUOK7tbN|b7=6JxBeePgFnU}SrY+CJqgNR zw|06mfuj+SAkQBK6*D`?qL6zDpgCMMK*k68rQrK_WS_8@xSqV58Dtw@IQ_lwf<66>P zE-!%SotsA=27=B;uPv|=hba?=%|uFHZ1Rn$K}gc7=W_y@C^FwSprV?1GK^_tYsJfS z%xBc+hr17$eUJ`(2KE(pE`G=B1Mx&o}OLdVF>=9z;_ zD4TL3Kyw3V=mh#xNL~LDbOn&q9|K6>! zF|jc;adaYO`u9%;_V(5ur2ld02AJl2GMZfsGm9k5my%KhnR+Fb((MD4#pUC{jzV+L z!q(=-ikg=yBEMEq{s_{Wph+eqL|4!M%6)r5S2_u#D@skI)o!O1=%j*+7KbV_K z#X)m^_ngA4!hB7ckxpo$2qYO8xEg!`qYYtn9tb=HSmHp2I{#u1-N?skMx!lPL(66Z zL-=cOveqvy!g3|ur5w&|oY8sY0!vlxgz7b7lC5V**T!{RS;T2A(C+-z9O-X0Ldyf{ zMt6>LR;8hb?mEpo`USpWp_Lh!c5k;|>zKeUyf98lEVO zt+JV1d_i7|uBfzs6BT37_fBBISkwM%8vE(#|1UKBF&X;R>pK1$WdBrA(MmSJ8#{=; zDWLNyLf~j39{W8sDzd2SzzZ13zFe@d=@$-O6{B0XjV;S02cMBqh$Qp8SuVw}%yL0U z;Oz%TPtHbou1BQz?z7MN-i&O92j>NQp_|gVqn~M|)m=<tLUa=(FjQUk{F~j=(bb0O^NAumXa2qEAsAZv-(y(R9tW%ISj@P z(}U+m75K_VtDEC-4O<^18Pm9X*SQQx#x$5%a=CQgWkU&~lJkDzQ zO&Sb*E@MRy7Qw!uU$YJ&z&hE;uEi3S8>%WYXwEYh&evW+5T4GN0>^F}WV9EyNyhFM zdOQrfc%W*xVp>ObFdS+B&QEW&pEQH8(~%b|u*Eu6OVozZAl%P0(a%!UXXO&a=4k4M zxx#W9(@o+jLt>V-c?h9%EZBi0lPq3oNQE4FaFZQ`ttr(eF`QO7YIu<2_w59Q&OBt! z8n^p~S)xhTxy##nU;e8o2npRZi{rb#@GX$5W2xg=ottx#ruNWby@|R=sddJ z40jGv8TuS)M^GcWG>xG(0nh3OINoP{)b(elq6f6Q5&S3DrJgSZ+>EyvKVk47@%=S7 zFf4yMxc~2rCGfw)^51|o`p-G0ob9h26!r$r=A{2rU%|lJhd+UEnbiR~)0Y%NGIvH^ zssNWU45u_M&O**s33a30m2FTpW7Yht=(i4PD1O|Hu~@;VX>_FU2;PzX4EDPG$H(Jk zw>NV39WglgXPyg}i0;kv-g>V%xtguMKT=P znd#GMsHp~7Pq2gUiu8GL|{!jA5nYli% zKm4nKE{~UO+hbdbx&|LgXqJZ1INI6dBhIv(X%VuBG@rx@@YDplcC%gcr^k82sse7a0aq_KK(9aR zlBHXxJf<~5i-G%ROoTq3nkxs)B`97g!!u`dIgA9HF?j;3a&iemW$)o&A{VDFu-TNA zrHO`M%W^_T2EeXGT90D1bJ^fEC9^Q_-MuRJrsKSPVdKzDbTIsckmN^&PQN!5Z60#7 z&tmDN3%wu7==`dJh(*_6Wd1_Njc03d(Bt}qDwCC`KOiryktAOL0(TK|G zonJ)Gf)g)&GZzKrO^U4T#yS(I6(N?&3GL&D9ImYZ9Z{ChL7XFZuFlMdc<05qet|4V zfA)E5TSXChIh+i}!P5Th>4dqO=}!7;UX>uRoi&{YKl zjHP>i4{)odd4uWT#WauPXzk6h!YsyF_^h7_7AGS|k42Dukj{}!lIrzl6|gvaLTQnk z`yeZJr*r$F`nCosYCkjTZn0KaX^967Evfnj-=$tXZnLAJ(Ilfx_r`wO zG93T)q%i+DDQTeI@MlH&nT71oSwZ z(k9b*;VoK$cZAWPA(xi)poQyoeSXtt8zc`WdB<4C^1L^Cw*2^dJ*@UdhXZ#R7e0nu zEHZy|a?^dF#^{65B6v4Eijz8X%?A|}lZrHFeglYkx(XvYb~jqd|y|e5$fNnor?0Ho%Mg-oR7^LhFo;))F^I0_IsKc z*w@H^*Oxp;fk}a}fn!V%m)-rc{xkhfOwnEayy0K@*Q$`{kMQ_!I_;lry)FqlcGHZg zA$m>u8bXaR0$4`DozT-u66)KK1qh@)tff>HbU zQ0HpY4SHLMg>Uado>cF7g}kgLcsY=Y1BwMHybmg-Bs#YICg&%^qjWmB^%vU0e#ByO zu^1gH4^>LviHn69q02cQA&uFj4R<_5?`%QO!_FjdJ?@W>4d z9|r7qp-)|%Fs&!{=Si>PB%TgxAH~mH?l3w4hWDu+)eB9G)Fk zRG69Ufv*o+DOd!pR3qT#PHj>r*><)_9LksdvatLbvsX8M!KKIaqmARKR3uo1klU3S*!BF(q#LDzXFi8AQR9A4ZFtYj= z)OCPcDN%VB)y(F7I6mnSJ~0|Q=`UV~FhYVEaP;@w*NcX`TF59lyi(uiZ3I35L4Dd5 zdJDOEe=RX1G4c8K40+i(#PaJU_FNly90IMMjmvymIGQ`C*G3`8LkuDSLZC z@oQy&`m7PDCrK7*1m!_nML98Z7{*5m#-Ff0ncJ7t=Y5QFsD{blfHUVZLMBLtC)FYN zEcJMDiQ{;UR;dzArR@6));+rgW`An7{=R8}_>ZFk`m{TXT39=q0IvxBhWOtA@1txb zhoXSUn=00{TE(NfhAu1|RCSUUz<`Jf8ZHqCiJENJv@F%?(z<+N3-cZI1uc#8L40QX zm2!KfknnSQhP2t<7|?K`pswSG_cO=_8+u}zKs<}%{wc2-5q8Aku(sVIwT#=J?EMC>zt^=_Rg%d|dt zc$Vg(O{BheBirOcKOTguMw(i79Y34XweSF(-ZO;hr{o^btX_L-EFcx`_u&wdJLZn+ z*eKEyy~NHD;oHnyn6xSany+z$-!{!GSfUO*JO|ZI>D;ffdnb6Ym>*67E;J- z?6SwQG6ADgsuWzvPN;X*r2~B-AgrV!S*`qxKF~H*T4B{uCRMF$PnA=T6>F&{=5!8L zES9Mtg4nYYY*PeaaWyZD>BMACkr(%c4n{ZUlHtVP;6d7VHdgg}x|;f0jfjfm(1e33 z!BpPKR1$i_CbAr$VDKf!{EE6ss>`|24Y)e20){hi|CMT7oQq0s%tMT~+@L3r()(1H z4A!;F+j!51kJuQfvIbO$Fc-h0sCm^PDC$zs1Z!b_vpSSa_!;7pBNSy~;Q@U5;hX7@ zwc6NW0!HJ5(VA(SgUXtGEH09L-uiR-HEgH}Khx+D5UzW}H7#QoLx{_m1om)x%@$d% zdZ$~pq6uNB1D2bjH0orsteCGS6o%^*`)O1H9sF-|Eo^>7+VTlDWsBS=6n+-K<5d#7 zmzpS~1zBp(Ii~r7z|TiDZK}5r{-hf9(wa(wF&1?5!Y(LdLQTP@bE1{sq8 zOMOCU{GK{K$(6=VDQ>uH)3tUF!1eX>BScO#7|JratMp0w0!xivh<|M`t|S|-Ivbk; z*}yw3H}GgvKk4ieiV|E8_SZCsqlyM;q@*wY8wur30fy`}SE-v|BqcX3#`A|gW|whK zf-JJx6h{%F#S;7wWF?i3^^gYGP!;`VD1APmIsEzJZRU^OBRJJ*N}YNbS$6@rMe+KY zUiIeN%GB=-)`GK3(jd!Hhxjk$8)3@E!$0%z0g_WIh2-_Nv{Q%V-xp2cG3~nEJid{<~cdoD$g!HvU81`!1WU_ z7Sj~Gi-u%-{e?DKwzGchK(v_zKL5Mi=luh1{>mKzkL6c;ZeeQii@N-$(p1v=2}G|5 z^*Wm9lf;%HwqNuazB!`Qi=cFqF;UTlNOf&5?oQCsw~j9A1iVr+$smb+{>a56FyQ0+gols|Lp?B-a`%k8G7ja01HG`k{rSoxt?O zL)x8-4nD5zJ!x*FweNDVK!l7rn}!`nI?jbFHQyZHDly4+Pt5_({+Y)qllPG}n~7|$nI zXvOSylDzE~KXjM_j2w!=Q`uv+8Lq;4ggvY8@r_HNDS=MPCkH&BL4{1}}~d(i1le!{4~;kHR(-?CMBXc$sj%xSFx!J{}m(bJS+cm2zP3jH-bbw zITi`Lbs}ze9nBdhbw%P0{~6R5hHnQtT6CtbAjaYb|Xu=4>}6tSdrGCD-a#VK|RMLAC4W z1pACqXxNBJt7Mj!&>PoQ1)Az|=8D)Be72+c{4dejRF)r)_9lpqYK~lew0IA2v*Esa zqRfEOm};Q5)fZWuz=^;!oEppDH>3t&^O>j^G&FN-yy zSS(2Wa2HKo(cufvDtFNYdRG`HO@)|nArkMf5=fY^m(GF)GQM3Q?fyPiU(<3RxR0h` z1ADnX%=4t935h8XBGDn@v-1#3l^XAQ)G?3!Xb?CoL~?q+i6|LrA#IT0$CCG()MJ!- z)!^eIF;U-AXyV1ku>lPZHnzpmET!;~y? zM9Z}bgTS8~Cu1`}LjIEDRJ5_R!r@_y3l<|ah%SKjMP*mnqDpF1pI#Vw3%qv+`E$S% z^haor@f>AZVwl-_3Q=eP_%N^_TFr$EwMDUqE!5U7&Lf#Qh9 zF9hk+gQQ&y=#T=3sj4p^0u++P-cGaaObc3G^^@#aXI*j*8=;pG=V&{9U+xLFN5giP zi)0ZUt8Kz1_b;>e@co|WtNYE2KauQhZK!Ex^;WmV0ZHUE(HWEfFD*QyV|5h)u%jdd zel`C)Ul9J0FMevWok;&%pWOi5ii@Ly>5GocW{qyHsEJceR*#hGUCFPaji}WQiwp`1 zp=5Jx>I5p4szbgw>v-I8(YL&oH%(5>!feEldAa`phPz$|Lr|!XXO=nAcFuXe@08_K zdrsi@az*ojNS(+Vvnwy`nRrgbI_yDPSfB?CNiXa{QCOiT1zM@6F^u9A@lu!DxS&~F z%Dk6b+n5xbku(<`28O6adnIKe#sJ2^7EWl>P&lui7USIM(mx2HjV2cb4EYNqW=K#$ zltz@(B@BjJb6R{&P%Co^6VWzH)R1~-Ta?OMWLHLkKeNU0k0Y}`zb;dS=4+#yZQ$J{3TOjS&5mW6Gd zigBT{J#;JrnehqGUz@frwR))?lPj@VV5Ui?nro|;H2=73qL%c-KN)Ga!&rv0rFb^4 z938J;<#AO7<({^Y8wmec|1&e2l*f>hx`(n=}?4NncP~Na}K@svkp}byYnb2|X7$t9G zkZeq81n4nUo~h`FnpFMRUWGeod5CpM@}7)_KByk49hS60>kB~^>Iv8_Zs5ZAM{;{~ zpcSN6P3yg(CIftLou?(-;ev-VX-)nLnHggz?N%P1*>wEALC*kntZW5$Yfn^kymwsY z+ePn~uwWBj>&YUF?r|8RZri!_6i)|wYkZ1cR<0CBc(Yz13aL5Az>NTuCgSe8mY+zr z_LP8db+`p|vskN`@!L3jaddP)srOZ1EJYo{brSt_w*aApGkgiNhp1ZeoV@uilSreo!1nBHP%n8z7;Kp;& z&rkS#Kv?PvaOv^u8tpZvxs^TN_xZFL?GLWlAD#dH1gUnlkMI!n5IP%a)Rbmtf7F(I z`XiUExxd-LT)@|Ll7tiZq^XbCPxbt?)dX;CmZ699ixob}A3(7+`po;H^F!x+3i7=X z-yQ#u5IZA$u_+6&YEvZo!A;*0!aoGh2vwoB|p)D{Bn zl!jsVh1_Ha{8Y4)%tR*S*_3%U>B%(eJ0UFA;zMDUg8gcK7f03nc1NMxa0%W`f};t>gO3dMo4=%Ioso4aVZb7x^{>`C zY=5BIU#nneJ77C*0-W6c(}ybzN`eU>`ry6GH`3l}ha(D8upiMt6294y6uwwsY0ND= zMd0PN)hnbB5%>z@o1~tofQt4kKYM$m@|F7N_Vg8rA595Agg43^^OkYXpsTy!E#$hG zmF3ce20v0TXy4@M1hW<=aAWZ*&!!oVy!L$Hrwd_W+tMo>LrNctxy*s!hRLL$~3rpP$q${D%Q?AS*M21iyyr^2N=Bn>F8FT}{v^%O$qhQ7#|<;c`0789$(8<~0RumS76=rY!?EFbT7I;2==Uo#j|TgX`_cPsR?tEQN) zomsh%@fsoU4n+L(-bZ|amLq7j_`(Z7N-C5bcnA0W(QF5y>1@K`6EYJ&n;bp|g_iI* zREOYq+{dl&a9jl65bQR2M_2P+_F`)Vt~hTxewBa|XLA*o8(J;`o63nR5^CMt=gN z9|c3io!*ma#{jn*KP&%alZ>2qB)3OiTm5?vV?(tQv8+grbakO&OQqJw%4Ok*Jxy7L z^~u#40(Ua?-7@+mDrrK09X|ZV^f!jLv$xC zd-sK@Pd_tCyWSuA;tCx$%IJ^-%}uvls}G;QvUkmp6W3~ejHzHwrGFb|x-#Ia7FYmo~kAZ(uyB`{gC8$a$aa(~W_3jIdy5t2xO30p7Of-)c`!hzY% zNF;a2CVn=^=Q@kv3pr*J%kwz}Kzu6yk4nqqheuJ+UT+L^=IFCO*bK zB?uzIw(to;c9&+Q+;4|3U;W$6uNmWzQ7S_fFe29e8WD_teCxk$G=7HwDeHfPg3-^r zlK=OWy8tzSK$@J_wyqFfgIkh)?ve@9px{(gIh=xv2xQ8g>XSUu^xYnKldBVQ9Iw%A20$(Ewf;DDoMQ?XSP&E0I6?@ty|rgeUD4R$6`1*t37pNn0O zb!Gd4cG#>zAJtYVAK@BN*`oB_#!nCYiw@sgCR%Ob6(e=$`=Up=JaO2ngU~KhLLsyx zF+g2LV(=xaCmrSqf}HZj-YdsO0!tEiF7m4JLF5D3JsFau8JtPArvfq*@ha2E#iV#U zg~lb@rlWM^`%T%%PF(2K-R_$(3y@mhD#4IPzn4t^+E$~IO!UKBVO&Qn-+?2o!EYQ2 zT)433HGFeSZ*S0BrVI+EBoUgWdt9>(4p$fEvj{|%hOM~~)Z!iS8ChRbhYa}?5Z;0^ zP02=L7xLT5?H!gEjLVKDiXpvIhG`LP<-`<+;^u;9u!oCGoMExd!HUq$e?$?zf9!iQ zLya}yWp(xITr0(*pv245W{ENI3$noN8u05blqg929TWab$gPWoP4QHb1wvDUBIy9`U>NnZoha73VPSFih2uuuB9ftCIA}Zi;fOrwNNR}Hk;pESA)MRI0 zEcL72b2Xj0bQeu!cK5oOX56VIx zDkQ6LXklWNwa-5gS=u>0N9&b8IODxGnsPcH&|K$GKH{ug4@UL$u5~&!33`@}y)H|R z+R0VnueifZ!SW4^Wmu9jAqr+p$bvFK3dKUDmyAbXE7l5;2SrefCSV!CcJc_o#l?c0 zqBp3kVgwTzT8rQPABMlXk**QN{|K8QN@C@HiW)FI1Ao!u2K2%7JG7cGiYcj4#vHV8 z8G5!O0DUY-O5chci602@y7b(l+rN`{}HQ6s%8AAB!PBZ^kHst3n^?zkz z!27;-)~+VR6gngY6uc3$UnnmNDsx1@TI;yY_A<$U`?Z0a?*g#kw$aIHr8U!P8CL^wJ#CC9 zjq>3d1Ceo|@+p7fzmTiLVNhn%%Ha)mWR)4?*xf zt(!!{gEeTu(t<_f0lWIQI?G>(Fi*;qqh~obh|DPG3Xr`93mNi^&O(*xoyY2rxxzu? z*DxKwA5piFWse)xkdUMoTQ-v8IERK!C{L1<&Ez+Ej}Yds82HWte-AzwgE?i_-ZHY*L;Za(c8j-;JR$5M^^BWT$uS_N!O4 z?8(Z);+s5_-nl>+KT|BE7d8ruHLM~IT8?Q!h0vL4RK|db0Nb9RWCrp<>FsdBhba05 zyd8>Q-V5Rpm+6{k3=-DW+e*BsYzlHWJ{Ey~D=m6y!aDBLksrb#W6TWVpO!yCvV^dq z_kGc03aS6hTHooue24P&IcpEq|FcQv?92P&p#^13ls;F+R2Ata&?G)k3vl$vw?k%_ z`^9j%xjy+6C~B3FG-iDWOem>fw2+~A+{-9iqZjB+A$QQEH1voB^ZT{3saj1!rO{e@ zHqfR8+s}?df<pK0iN^ClaVZ?>V7 zC?BrKLYP%;!u{68t59bXdOVe2KfF_?KxxOQJt3LIs zS|BNST|ng`FX`&{P!vpoHSzWsWg@+IaD~CPti{h?=!qAl9Uec-*FK4wR3=D7Z?RCP znU*pUZXkAXFkq7 zixMO%(Q4*ws8`jMv)%;yKS`3Pd~Zjs=G&4 z{<9+eT~Pd=3NoW$vCm~pk&WKkp)Gzp=St~` zz*8UqvZM!w<02o?h(g37_sGEPo!xqy2Y0$&M~5%iM3+xY25u8a3Wp{x4Q>;sD(9K~ z%5Jk-7wMR#KF4RGSQc#9v{^=fIBZFavRqwJqrdnR}%uJq=r?k+SuHu^(*dyRhP zD(0Qhy-3=2Sbq~uZk3<|l5HnTaZC~BPMV1in4 z&#jBdC#Qm06st9zRqA^Jb?7E8``|kvBTlh!7&4VQ_~oGBAtgctlR3%cJUnVJBVrxJ z${Q2VaG8XlVOC%(@VSDl^g~ErMQeS7f;yh5 zQY|oPjXMy@3^3(8rWcsqw|+DYQP-cE`Jh|kTbO(SR3yV~G^-e5LrGNMx0Mxrz+ z&u}6P!5kOKs8XuR#5Ln*ePLsXv;Ya1S zt@|fui_ezXKVF^*-V_=1UOM1r_gs45Lcr)rQA$0icDW(9r(Oht>j;VnstC#i5E*E7 z;lUIIiGorvQGZK{qU-*d}RWRm$UlsQ3s z*c}eVN1l$Xg28c$6yY^$S-D_p+g!@1+< z#2IFY0wOB1f$=J|+rbk#t|rsX+%`NDbNv;pRd}_=e4Zl^JIA6H zg}B>(?Mr!v=xL6bmA6s}p~Pf+zZxJCv3KgU@)g`>?Ky2ae!>ut=xMN@yY1R zwtaaYVqsv5R9b753>bP*6Chzy+mZu+QjXw3;XWwc?Rd-uomXxwjbfs3_Q3mq4)J-R9 z+~XyVGXn6^W7<@6A3#adXICqQ9rEhNG0N1xq;dJnB$jC8i`^fMzB)>dG z&SB6wobZcG?qb{2C)Vp@B12D_=JE<^(Ni^oZ89ocr%{~2u0A38KPalFgA<-n+z5m7!XGVdqf8kA^61higNsATJcp=M4!P`~pNWa$AdS6|HteTzo zIaBI;a6x+5`#vb5l|a--i1>@~w5338Rg}&C2|;-hOJx~hE;Hu3P-Bg_C64(naU7wJ zQbpM{A-|UB`-FJ(5S5So%%rjk4u;^G952x4d`J%Z$@)=@6Xfn-*>_zZ3$=q_-bV8Y zt>fnvT%1yTybQ%0kml*pFN$Wl2SfE1;0Vh2@kQn#{2y5I4Crf)%a^}2@>hSt(Ov0@emlNJ}3 z8HRu1v#d3m@O8Cl75PRj97+7U{`ZquV(D{w(XASZ{ll^AkrQdt*Ol+zA#%diaK|}P zfSF0xJ_whAbB-rUe+8^RC>9p~se}lcF?8^aTs5jbgc3-WQGD(fT|NVKZM#nqEJ##1 zry2G3HmMSmtJI|pAcet?xL;%COtu=2ezanyKz4Oe;o+IGC4W?6J}1LkmaS(V`f7l= zivN9PZ7Ie$&76wlZGt=(?*63c=8v(=O;6HU z&^iO&bri-I%Z&rbEyY7^4cmb)K;}Na=m|~)aF}M`y0DB|UU|R>eyFFnV4QX~Kb`Us zS<~k?d<>4)`on>2+sL+od#Wo)Nr8i*Ge-U=ltjQ5tIBM+S_QTo@hLi5%01eOrx`QPPg=b*U+ZU$B2%o8@sl;{`)k+n zr`Q<#8V_VZ{=$J-O9d@M;iTINdRG=2mRe?RhB%#VIS-m^r;^Bl z!Ifr#NfNe_Xw9Hg?(ik&;o1lTK|(o`ki8vsM!1zmz86jVUi_9P*64KdfMiI12-i@Q zB5R^yeYH28wZb*URdD0e53+$up*u`X)chqZog4@P5C|dh=?|7KZE_qq&32;`WehbC zxa{B`_!jleu3}u_4Hk+|*a-NN-li9R<@@!vo638)T@S zkFkQ%l!lcT25Tc3#UE?dhL5*VK42HoMQLC*F&Y?53HEj=qhsCd#R<}jX}pg`hV=j# zQN}9VuoUc-_XFy#Z3DBND0Zmr@7h8xMkQ|7M5VhC77?Yf>t!7l>eR^)sA#}RlZsR0kU2(#nc6Z6@l+&F(@QoF#cS6LpC{6?k zqHIVRZ6bQ7X4#E{@cik$CJU(?SwgQ_^(St1sr!L7QcZy32qNe_s#Nwgbr4L3t~>v@ z4b&mD0S4sSFZdjW2&tN!nJEB{%N zBm^gQJZFeTx5{GFOP6Zr4xVmYETAQ&?YY4tzydt77<%9!A5I|*B zimGs259PD{`3Ud*8t1*~c++`y$D5K|%g&mMZ(Rw&CcoSck^w~yOki+M;VntMs8<>V z@pTHq{~u{@6&B~#ZR-ZN!mZE*_XKx$5AG1$-95nycMlfa-8Hy71b26L0y#D9Un_gB zHP3UNbCDZzIYy1%zSetx8+dAerOp9P-Fl@5nc&_bgWyK-Em z?nR@N!tbS?XYENbj^$7T@y@RYJ!E58pg-rxN9v#Ld=j2~ICp?Y+&19WLBI*&*slNZ z%5Ts^WNBhvHZVOyngEi!j35GLwu-1La{$XZm8=p_M_v-3o3eM(H>Q^hh zAWpA&R!5KaB>ET&Y<0tpc6=DPszgike006zfboA%9xf8ZcvTCYwcNN ziebC3>fz6Bm&zQ3D-!tDsr{?P_xmR3|G0VVZ1-m&|L2<7#I65PuLr(+soS1gZeBcO zQ_(_Vd%0Qml%ip>Z6OH{;=#As91BZ(wock2JY_xLUeJc-Ty{O=g@P}dryDA+dHkvK zcb1pY(X*c~_uEuou${qZFdQzOs9-{3@Vbbu2#gB>fWsz%(V(Zh2{}p%PEMA~{R2nIUTP+j5RCr8_TUKN-df*X3Y+Rb= zhnOKA^j@qV)JOeiNAw80o(U2Wq1)RJP?x(>lv@d%^N0p1ZA&ObQb!eN-?J9ahsiM< zZX9Bqecw^0GAn+3`rtoHPRe?mig;~#*jO?ZUiq1*imZJW_U`&r`DNxnzdQ+?3i zkr}^Zk;FOn8%2$4>oxX|JC2nm>X7dr8v>hu{ZV%a-etUufuE?ee|@4TetV)s?Edez z=s$~HV0bwJ6PgbSTD*yfBy~b`15K4M361V9prkjL^D;Xu35w%s+*25;%B`tBCz}k2{U?&CB)AZ{(l}vpD&kyf=#+=^g2@Vvr>ajo}Q@0v)xhtrkpPs)M+f05*f~G5&9R>Kpjjv=xzs6k7V=}r2 zv&$K#ssfC@^9-l^p7#nPfzYRHkKYIUQWE~<%>pfwPlbSLk`t(wJYlDr)kV%GpD|=l zcGM)=YfHsetvtty7dCEN8u)4!Z!5?qfXkf-N5%RmM&)Q=T#nRe-AXWN110$`f{m&e z!BZJlj+yBre=I>AT`G_92^!+V7Yvda73|OnoYu4atR18Lk8Fec5$Rx~A6laDIi9YtziJbl8dSU=3lPw!dz6MCkio3|SSK4QCn zJ>n0)9r1r^&EM+Xz}Q&S#NNc#*u>W8Kh%M8@LD*yZ{>T&uErj=Axind-!Q2CojSjW zti2P$(3cpd@9Pc8n$z`iZmo>JB7X^n7;eFBgA2~3vv#k|5w6YjbKO4I{vFrH^T)dv za3Wvp!VfU6ep+F!s(dpQM1-up6}&IdBie)wuodx7L;s3$3NicKW~&<+}s zqa?dbsux~G)z*RT_9SbbIdvF8rLcQ;-I2B(iNfagyAsh$+$VKZRfV0d%{RL(5omrD zpLtF?c8~JxidlQhpGVulfOpli)MHz@<+U$QhZyAO9c@r?=6=O#na0eESs>G#`0HJ; zLGfC3Ae}qVW7tCGb^3dPYrH6WquTeAl(4jpO6s#xbSYR0a|x;+rZ0(IF*Xm1=0jgG z>15cVQ73>mrus+}ZclIZHE;%SH>y9!Texb zbZM2i)mJokiDSehP#*zn7(%yo@lSM?{EsQ^PjauA-O!nT`B1nZ-ZvQFt9JOWKGg3f z=6~apfZ>UMSxY{uHp(k1SiUtMj5&z=nSQM`Ouw6_r&N!+}~|whe(hT?~`oGff|9Zcp!?ri?4yK;^fBm znFe2k5@UK|0(svvw!M$NCIH~#t(g`zkcIo=88wYwbVNAlBwix$CnyH=UsOjRv{7xd zi)7fiG{{|9$C=emn$TZeDYy05EB9kmz)PZ{{D5&rMUmrUYXFR~r?c6t-P4=d$O;a` zE;9L?cCBEgj>i`-GFppRH8t3@_iYq)tkjTyJ>voh8G zOi&8EQ#+o_Ww~p5zqaA(|KeQyt1oLzg*ak@<+zy+!cNBXrV$vwgJ*mDBw(>rp&7Ni zjuwOGrY;*-(os()n92-USj~UNT{@>F*n!1{Ux2RgwT94%dqIP}9mZ&}AjMIMsIOKo zZf~{IDg_fKcqgljVC>d>u78FFV#vM()jH~xK} zyJqbe@(f7_7C{siwn7L)ZDzBjjWS5`MN7wXQ^iTL)tt}HGs!^vlc+yRqq8;HMd~!A zb@L*l;VWU6Yxpl67BT+Os9UNa)8ZY7`bi*~th==TztdtXpp_I^_3SXRTpZ>yq%~I0v1G6UJ!Xtgjbttc8IwecH>(O{4Cg?l}&vgeOuc5ls_UI=Q%UL zcNk8jY^Wq+dSQEVpTaIP#i{p*__OuP!2m_Xv{Pax-HpyK;^O>a63N0d+=B;23gj%z z8zvQ?XFTFpPD`@|k2PxTeExWuU6FWiuPYWkpD>|{=0~AH`E^wYh%~Ze%K1E?xLg) zgHlZ2NcKxXkMm)6tB(-2NUoqb_;{%vB4kRA&TjzDmCam6*6sE$TXZ_(y7w68y5Cs? zw@QA!VGN=`P{t^r7?n!Fjm5l_?e!1YC)Eqlz`9ft)$1(NyKMt^7<&ygx^#IJV*25Op@YE-btI+wpdL(e>`_D6F69WM5K6*z799wpp-W_l z+@WsvWh2e-#l(}etA=bZr)^vj$fqTAnl6nrcdtFknyOE9UW+x`?u%_kc3-jAO)?J` ztVYZ6Yz4nsP+*UKZR{>7e+Ej=JJE4#BoFhYMr9qiOHh($uw;(8ewOzEI*IXoRShC#``GuGKWsUSBXPI`H8ViS9 zPdUPYb?@CENy(e$gNF)R`m{FrgH^0x3kY-EVINQj*|$1s6o%7d@gGR==;4XT;U#bqqd&{nW>OXquW!mJ=3NffiX#=K?Bz^T-x5G6 zXi?GWH*Fq8nM~1BE4!|m@Y0y!juI&W*r+S)y!I+$b?N7MlMmNw?BF1|_dD~qbLpH8 zeM!5l!=7CIpAOsY4GyH}Zh$Z$H|md=d1h;VKqX-IAhbsYe88{3fX=o)qZKP#pKOMp zlR}jztPHx~gVezwnNlqcC{=#=oE@XM^aT`SJ-xhc-_>R z`mv0WQ>MT00uP{`W(3d$#Pd`n&ZTHyu=$F)E8f;k@sksD@4Wp(vKv=^LaP85STryq z@qf39$bZvw|I;tw?+%VSf*TlT`wA-Xu;G@}Hs|6BbBq$2hW`4#jG?ApWy(1=$+bxIr}c4 zdMrRJ04P8JKm^bOAOo1;iR1YiKZI@)_IQAh00JyJ#!bB!NIjK+WV{N-pZym+J*pr9 z;OnRa`qvPQ2NeqdxkX8*zhNo2C;GrU3DnOV9jBvus_1C(K_g^an zxl8xUEYNM6+zh+Jd>nMgjvnTW;A7ScpxAb~8FGhmz_aU4xWsWqG#GS;Yl%Re{ID%T zZtP9IMm#XX%-_$C_lcBdn=bnqztzN>%zS-|3E}#u$)>J!CB`%sAC6_coNDwB=_VJQ zj5<{Y<164fQayFUm|Vu%cz&ar=v0D&h*I=INw#|2-8h0x?<8yHbnvV@uLa#IK5CHa z2&GEY_cA_;Mj87@j%1r81!0kfcO@_JbuK*EwTb6aj(pu#ECE(3Cxh~~7^Q}YGRpL& zuDtK#CB$@$mc+%>b9u36bYErfHOaI*ZS?0v879KM`^MpzZM&F;7tvxiGvP%lQ8MGk z_>vgRut~`$-G3^+c;fAWolkfid{?k1FXS4aWE21vu&+|&3 zJ*vvAb?1E%#iYoa)@p;OSSh-+stdV8E>p#Vrk+h#VOO7F6i8S_?C#WAF#UXM3x1%cct; zEb)rONfrOG`nc7k@+GM>Ww_$*H|1S61V(CC)<(R$@9}BUkW5b$5E>^|x-fpSKzWDB zFXf$;CMfJ-d6F&A?vt#%saxW5PIWqP+@HiA&!)=|QRfP-NK!aMla>gd`=Hb?OX4Sw z@gCo>UW7M;l>j63#vs@6__ISXk{b7Z0sB{cRf|@{%Ea+hgSCq(x(*z2ct16qZYvSi&1 zR$7ngIQ(O_L_868{#-N?swk;j&odR~L$l*W_L4QomtG|G?1gC`)1Nz}TexthegJOH zvFAxWzf!yxW5o#{0Ynn?m4}Fnb5uc1zS~3_+nc6-U7!vFDSW178^YyCXVoWiREp{I zLmI^E?^jH418jC|tr*NA8}-00kfihD#lJwsPSN>=a{^KAi>4I5v;OO z0hf-ufd-W;Cs!nHP!-%~e)HHJR*I(Y^^Cg&d zqj}}?qBLdB&DmTjJZ2rme!J;-D@k!m5Z5B`BH1=$=HI|3wM;CU54@5q7NtA!=q7ad z@GWr5TMA^Y9o&zLjAugb@%^p0DYbLp%SZH{z49A1Aat-6EqCEP*{7TG*)YypuUwE3 zjjc9t2+D(Bv6>A+uBhw;;l7-s3iR9{g0*nD!7! zcTsjRVou6dS-DpSqZ^FIJ0zs_PWj5%SH*IlAmw)Py({7~{70bLUsKTePe(m};Gqit z)mi%gpvl?((j<{;+Um1vSg(-V#+1;#2jAdeu*3-k(=AJX=-a5y>b+4!<5`$9HH8!v zVK&9U_Nu>s9$T7QTgLrVIs#eRkl3-Ib!zoFy7bxj{TCJ({p#8YuJ!vopZ16 zF9`ZPz9^gS@R%fR#N7lWZA9HXBy9xUULAWM3|kKp3&pQeX!Jjsxq88B~D8Hmi;J7!=@yuT-9o4HjGotBF+ z^meB}qW_IG9<03MH7iHY3NP6p{W(#U7z_<503>HV1#AG9i>E29f&f8MUuG>&8dwKol zEzEQ5Yauqh4zR-j;P+B!1CP+XP`UuHovCG^4Qh{usauVc8or{$xCd8+hCxrw6t{8eDE zOo*3J;qa}86lBl^6%j-H)XXyxnA_@UZa2(I-?J)_>}9uxKv*}^@C7}%Ec^Z1W{pFw_Gk9J7W#|XOYy@l&IrdPCU)&Un5_B-T-%=qcYMa(8y zE@+kXwec|&2s-oUJ!qS_9a;Q>{+n4%h|Ri9v{_eknxd?0Ru-8Q^iRx;Nl}h1u?5Kq zu>~+5zTXkd)590`Q%%3yXxt?6Iy2>)J%~fku4PU87q2WTy)Q_br78Skah(8}WE1;f zyNnlUg%^ntNw80~g*QXMR%Xq+olM2wLAF*NZJv2}x4gaTDc(5UlKAz2C?Dw9U+uyA z-5GKYJ2=yzpmB;im!~3o22^6fnCZZy997YbRYxmO0I486?Oo(O&Z1<-bJ{)UNVQVh zTwE;@Wi%rYe6*tSJ-6@{o%<~X2k|742uq|)vv2JFJyW|1u0;a%*pk|~(sz1?Mrdmt zNOWDDzsk99wBwVZ$E{6eks!2$4`5A_T|hI4+1${58m_h`xfFz%UMpcOzc$}ebphE zAo-Km<+?XX-}P^Zeh9v$zM1&?iuF1sw62y2Zv>#S*aJkCpuhQrZu<~zbdwr8$e{XD zrW3{Ub#09)pnpv}#8wq5lpjZdHUWEPfh`mc)6%|S>&QkFgWeq}t;r%&SVCA=C>Kwq zl$5M8^}wIS(aZ9vV5U?MrN}oNyTBPGduw8)*j;c1M8%dUNd2La16zm*#D8oG3CWM{gHThlD7}gngMb*Kl zqE-+RJ$FD$V2cTcwwXd{KhPt?k^t9AdD+SEv}3__?kv21w@-uq0 zu-XQr^%}wzOVOIlDy_H2Nyf@fM5-!@rfxO$dKUjXHCr1sTTqV5bE~zIdd<<#_f|Zb z<)o_ehF-#|rVbNSH0gO*lBMRXkqZ?+tW`7X3gXKZg}Ibg>CYbWxaCXeVn{uvwQz=F zF^;&!R01t{6BLfk92WOyo#cJ|dVZc*{_LhWh^S!-9UUxZ~glHeddMl-P$szO^OBA=pC% z=XkbnyJ?>ZXb*6Uj+??umW!lhvv1|S1}Z}uK9cz60_L;LV8&nn3@qv@s=a##f7})V z{!LB%zL@pDGpfM*m;V`puu=Fw5mgGtY5ze~nQVD!v4k31So~jzs=;QkyYmmCDgX)! z8Hxwi75M~jfg@IEN)(3xhn8A%0VJhh)gT@#0prr|x7BfiVVqX3?H|8);K#2dq2O^I zu?n|WY zdlRrhdjo}2^7!^uI8Bx8AKbxx@W21vi1=Og{hy7czbn3v(umR@sxLzph!DUFoe=y5p9#W1v=2r>jij3v znn4uFl`o!>oh_A^hY#z4$`?Z00fN6|Hc{pA!>j*p26;ZsxAZ=r^p5+?8NU1d^NCLo zqhK(8D|>;>=z_h?N-dMswqIm0``(g4t^g9qN;7XVO$;AOvyLJ*k}mY{_)AbJN59eTXsEj!XOAHOLMJ zai0YA8jjd_{NOvrQc;-Tz>&+$VMv0<8?$bkaSeV;E@EJHwQivBq_0!5ZL0A!MoQ>` zp2@0gaek9lC4_J}y`|81Sowv`{N{ZvVa!gd(mYeM`8d%!f|+ab}kMsGK4o2}OIs;oh_qI5x{t_?cOUGv?kWRJuN) z3)!Jb`1YFfaOzEy1O8`I0E@J`4za_pdi&iqzhgRv70VY#Tg_Q-2D03`*1X7-O(_vO z-LzJcx*b37<)(8Ds96VdW7Q@Zy)p9=y+{!OgG`EDFW!*8CzPJSUzs4*%$LuA9ubaR*&nPCogK1RvnnMNK`TxIUp_-iB2iKFa2j&Xf)QEUzkyK5);nCzW8I zg850x6LlpP4IO=Ol{8X?s4zrS?@Xe`{x(r zPEKEii){Z=MDQrGs(pr0K+n#?UKOV+r>(;Rp%_YV&n~qCG+`^@y2FXX%4XBZr4P`c z4G#oD?GDiw?$$QwWvUV`B06g43 z14b1fYh&2TTrgHlhMl>LJ)fQc^C~^i?L; z$Os!w?Nsdb+VpZM5O8YO-+&;sFV0(9Wnl6`3)zkl0AQnG*mE0P+p(wXv#&Vq*{A>5 zzMFGLaZ5)xdUD{gTR}#>JtU^t>|7IG^@?cs+I!K{`lIg~nVL%5y%wtA7QOr!Tu2doq z{fGZ?3Uh+7^4^SXwK;c3-Xl0r`S!&6tB|e(RHgp7=JZZ++BOE|V5K5$>NuFBbbL2@V~-^8u?S%YjAiFKmcs2pNsW`Ab%K2Y@o!Qs4Pu0N-EtWN*Y?fx zo~N@}R2^nNNUQ5j27SB`Qg9A;5=U!CJf0{r3ahR@{Nd7L=44(_fcsUOaQ{{ue_yiv zpG1tkqn(k7(|-UGE=tSt%iutxG-#aGJ^7}(T_t-c1XYGip+qfm^1Os_4EIkw+I54A z^(QVc-m1l(u}=!O&2NK%REqf}6%^;16X`5YWvT0^0=|$7J?sPc1Kb0Y131AGmvOn9 z`ha*8&H`N(#@!s)2@xU|;>_*hVB}FxcRy^fk&Ya1Amxc>V^hY^XY<0*sE`> zWrR}oqwZ1M*ON>-4idv!A&;V*Rtwj#NHe9aE>EsQ0fSY7_ znRkX5DGp%9CVy9I4b@}`+dww3@2Y0Dif-){v=Z2cYdMl=7X_E`V@2c~~K zvdF)qn3pT-s_V6m=Y^n=r_~tmR!Bo8l@;^0eBvu=OloO{#AiR52%+;nK>bwQB0rQv zJ?kDlOXIL}UH=Pd?#f`TwNO)KV^9dfC}BseIx_F9FoHS;oXWfLZlkWK>a4e_ugN}` zb}F1W+u=oVZ!heJwfYueVH+3e;z5UQW@4Q6iu%I^Bxn#lNn2Ws18IA9vz^i9VUU4R zCZgm8+c6)-oZ}FfSqGHDN60Tko#RWG2w#nG0{9T(2XV=z8F21j$nBp1Cj42YR3Nnq zCiK`HY5x7;XD%Sc(x70|zMeJdS(;)_EmPs_9nh=)wtKSak;N$3_ZNY46Uwb_4pBYy zlYEx<``A+Yn8tBw%-qbdZ=eC+_>p*@NHE5_Ecnv)g$7~<(iCO|{h}d)`3&hn@C%6%vE86X=QA@73~y&10EvY;>7oTd za6VYHQ2>Cm8?twh0Y!T;^Y_`{Wo;4%lqKmdyJ!tg=C}CSc>>x~Uw_6`Tbn19%4sxs z1Z{344Y8+-*_6~7C%x$I;eiE&81wna%|wuU@}{$?S^4TE>7BKy+v6bS2XWZBVUUeE zYp|Txhzo0}5`uTGahldzNobphqKETEGrX7cjsj)Q?N90Q9V?3Y+j=QC{;Dx1r!GhO zDJUXO%G$+6(TzYKWHD41OFG*Ud{gnb64OlX0`quy&uQhgZvk&b?K_k)W8EGFcIr#- zFFx;`_315_KXT-*51(k-!}Zy80|Yum(|n%@E5b&t07SWGZBn0Ptg5aJfFd8)97lx} zXj0e8%mYY>C~UZ;hiCURVdj}sm$Fu@+WbwzQ;e2PTZI^pxRK@zH}FVxkUuWvLqKJv zKk{U~;MfgYx(H8FNthjDaSv!>2 zMX(Pli!zizx!E)q3~3LJ!+!gzQI(NOxD0q(nHIAl82AN+a>k$?_O0Ty(>P3m^3w>q zy_K6WSk+~6=_moQ?DFYytAR9(qCFjO4kOR-5|f zQLq)D@sh+=X;ydXMJ(Emqx`dJ_#+!3u||OKcmDQVh4j%M4)W)OqlZ$umj2k-#ZfHL z7dn`iIdRq-qC}QZ6UUAR2}8;Q76y`RvF`%nEI0Djux(rB>;*f8n9*$&K;yT)WQ0l> zF6q{H5wqcm-PzI>Xq#R1#ve$&(IWaZ>y5)z;L1px#b zayMa0s@|yYJqoxI^vGwzsh9-gC(w_(rW;Ujs3~ zh%4foXZWToH2+GdKB600N`E^wd#plF3%)28YHYZ0k7a_tjp!xhJs!?<>Q(dF9Gi0E zc%AgE&fE&ANZf|09EVG}X&R*g$i_mvea-Qe`N~KK9(8oeex7>2s_U+0HLEy)H z770tY10eja z5)wbzv5v}weHKoFWlMOcY1ae|Bab|jh|+VSF9sON)zW5vKgh&X=W=jhbI7X7j;TPa z7D;WzQD@C7@F(1}7`H>VypDSYW_PShtDN5+r~?-CfJVt1|4S%ayMY2xWs=RS zkU=J0$_--}hiDr(9Mon_n|8{SZdHl}(^4bUa&>e`76>OfNh??Cfx62F&6Co4^g(!# z>=6CxMnoYYpZLL{AzTdfeh<&Ld%f#KeaT>O5_nJ>m**3~_A!mT+>=!L8M#p$9vVT9 zbQhuKX8UrAaN-V)XWRwt+_06*t+*c{W zxvvxgmp+Dnoh-rqO|2-K7&sc4|GnB3q+$iWg;;z@L3K<@@Tf4N(6n`%=wV^eq{HtX zD7wvQjs{p^HK^oLSPf=BQFMSY_yV18cir964Em?Dhen&-&NH3f9@_r;`EpAjh)!U! zB4flB`E0?faG}se1`~~4M(3WhVFB9?924wwfiQyXhgm^m%ogqn2?JCV*Ab%ID|hCB zxMY!<%Co zEA5Nbz15l7x-FV*2#)-jpU+K|k1%Z&HQ+;B&T>;hA#cgzzRygZ<^XK*?PauFtUf2S zfqjJ#h)5GlGIj-(_4&9_Rj97of6OyYX(_uvxp$m>5)F=KbvMwDBB8W)G?9D_3;Hnwmif=4-`f$H;pJ`jP{#6lUz9JUo5A1XvaBLX>`+V zEz~hu(--{f5Lc5mW#ef-mI~EuDUN?*XLP5yQN0@^&MRfANgQ|(tSwFbkfqgbUB(!Gedo4V#~+b)#P&c zS2z&P66*`B?p8GEl!`e;PPjM}RKL0o+s#=P@HMI|yD)FY?m(8?2ed{(bb7`v( zKX$ABBdu-zL3e4anl4K4P#bmj2jf|Ul5V!XRkY3QOPzuPl@Z|rzVD_77}X^6oH zjd20$jTY8%OI!fPZeeD5MLV6fzurENaJ(T3`~#N2Y3uZdSyMgi@EtJ`0*yZ~JGPt3F3Yks^5ik7*NDy6ng(FQKv78ZIE z+LwOPSGL!reN||8QQ{87eRF%{9M8IL#$+ozX)g;}<$2jR!=5?H@q4I4eNQ{v)0nN5 zK<`350tzJ=P_v%ptSM87Y6G2-+~(uwUm{^d+Qy-d1$YHJ_C@Q=&}LTITko6nHC{(- zaJ1}}XBBudi76~o3X?F7jBux-hLwtE+zMgU{oYFJ z7M1h h;-t8ca;UW!LGupE2#GkSHP!8&B33FBKm1);x;qUbe=aOa5h?6ay{;A)<`aDnAS?2}Zq!0>j4TmtK?A^%FN)BYOI{hHa{P}ZM#S_}9n z9 z-19V4S&^3hnz}ygf`uB{WmeiT+X5>e6a%eAFGB2iu9F`7d;*iS@g&9KRotXs(aM#f z278hN^2n^PX>-njBm|M2999jCVKw<%{ZV1L6*$v;KDc9@)n{XO8q&6WRboObiBSN8 z6XJT=C`&#f%HlkZUlOuzq8}L4ObEZ$T4VdhD`Xt|isG)eyqn}vl@GE2ZiyX+I8GKJ z*vakcbKVL^0+OS7>gi9h9wY8=sss(ET+W8eJCTHxQ9^Mg`s_EG#_`;Xl{7xQ&j;42 za$L}7zbT{~HLZQeN85MXVFb*}m(wauWe~bkJECQK-(W$=&@yhgd@nolNtMAGzLMgH zP4po5?qR#cY`0Uyh@Z{6W4;Kgp~W)B=7~i`WR++l{~8AcD4s}W=%(CDCUbvUM%r1n zv^&0l;rlt@VM_*$cTGg@`4cL5hJ6vO-@6>UVL@4L6m|XW=2=WJ22 z?cCcCOrY77ngCr4&4$ppta{CfPBUy)lIe1R`yHZyACYBh)&gXUN?Es&SV zNZ5fC-9`9PyD0Qo5*4k`H{^JyUa(Y=ROi+xTEs=W+Su(_B4?9iTELhJrE@rcWk)%p zo+&eW7cRcqGV>QGo(Cw6!Y|4#75;!SP6-$P^UWpu`+A30s0kbdYn@@LJ^urk zGx4v$9HHMTz;8DHf0Y8G|1DYY@3|zGSWR%W1Xj?5ijB3k?o!JKRfK6Zrn&jBcQ9Ec zIjZYa4rOExN^zk@-=(`?{@|M_ZpoD^GzvPJjlb-TBkZ5PJi-XVAi)+Oj{;NJ7|p)v z>+=VZ!^$$|$=!6zl7-9S#{*@I5PJZ8ebY=L9U21r$t!nFmP>5W!3Jze$t}Bl`#Mc6 zT2Bm2^sWKOM3O&#LMBe$O$lXudqtj)mp+mM?TDLAX#Q^Jp~!31z~sW#GTDv2;w&Ste*!U$ju;zu(`%gGj? zWCZ-G8;`N!sUjeaS&8S!O2*~yd#9wO2k$k4;L@qvPDI1uD#szG6BN}zv_5~=__hXE z@~fV{!u&1G$Z68O%Cb)QF-xiN5G4&SV)&XhA#wg{0G4=)UqaGg1( z5NdYpaNbjM(%bX^)nj_S5XECu z8N+B`24V=hNn}f#LgK<;MkSf(`Y34jROq)@ut%CI48_6-9yI#)9|ZgG5otR*O|W74 zm8$(bup#I148LMNbQ=xkvE;X8YkQ8{)5`!m4i}nBs)Ee$n>|)LFjD6F{r6`P0fh*y z%9RhzVcQSQ9s_HvrGAAY40J8(xWopqGpR11+QaE2-%k^oExKb2>J0}g!Ugla_LNu) zuI7u0RG8+h0MW*D9`jGPd@~g|%#B_)fMk)FU+wwpvTLf+Zq`VCtE3hs7XltgK5p!* ztzN1tbcEzh#&a)Xs}wiVEdu9X3zm(Mt;=bDa?R^r5~{m*&>Sr}lLY48;#|In&iVoE<^s`PmB+5E8^O5vm( zD5STMd|)He)_FJfb#P4esH2eJ^%^ld`G@WZIf(Se(lGLrQFb`SoS9r}U3TvEB^dv- zzbb7BE4I3$I=|@V10E<Wf(jba+=;jdMRVp^2zHgJ8o z`PU}Q?|$y@{F=YsfyBS#vl8oTa2 z)&1GvU%$0|9w$53nv?b8WRTQba#<<;l-P|fIRv7Vn90y8=*lLCmAJ`B9t>}I1TvYx zZ(rUvQrsAFf1wIN$c1QzOF-Qvf0#ec745liu6{4+I=Zzh@0T4xE*5_1E zS$1qmdD?cW>J9@y;`o+ZQ+;Tup|nVAXxK+;Y0k{3Qe{@Br-8bVjBv4d=_BBJ6F=O5 zPHM*0nZfD^;m43gK|vXYhTvA{o>K8iN2XJ#X#s>T8@%dA+GbF(HO{HOo-^`lcCX)^ zXbC?C|3{ji@tfDmVn;7|h_?{p@1=7L`nz!(--RT1s~8#A&jAgue|O2k_kvv0KX|7k9fW`!TFmk3soJu*kHK`Fr!#x0P26 z0z??97iq!$hX$Fx^!MbZ7nVW(kBOd>sK^5@@HU)ukKidA!RrBicy@vLGT|W#F&Bo3 z$@SMDEgiPi!)W)|9YA|;_43IDN2K)FoSp^GM+RpM>>p`pJtKzh%`WcEIOjGYwVGJu zT{5$Vz;;;)(1QtyX6=Dh5`A;M6W2J}3!Lnam(N2^ zr?1>2fF65?Y@&q^mxy`H{U1wE-=2m9-$JKukWwi4eZXWey1_CUQ0<1P(X9uPtSJ`b z8~ULUbBj!U+E|}-BjoF|7v~eXD7vfoVrFQfV^w%a@^wC?e5P#b4wH2T+nvQEqDYj* zK2RTqcxpf&#<$Dy1;D}IkFOE{4Fzkij*=7IpO!O2=4zC|E?P*vM;4WR;C7Q$=dwxl zoGy==`SNG{mXLsy)eN|Xj{mFQBleqo`aeIC{)`gFdvg7S#}^1KI;6l=v;L9w9&)pZxq$bIuHmO3ZSg%GV)-SB z8WTZn-k1<-6dBzFU4A0$MH-0+CLdZFe6w$lPT2R|0g=f+BB){PZ~gK&DtS4$+}2%_&9NMQQUJbYSN;*)3;9Rq8KE`2-3Y9X z$p2Lx{r^AJzrpvBf4q(0(JiR_<$oQ*8!@N0 zwW$8ScW_d|eIZlPl;CK=3>yTyDyfOojOP2%=31xwiS=^<2w8otuBN~81amfu{T=aj z2c??GH){e*F|IHwOginE0^|`8bYkuK0?40K0pIe3d%gu~(*h1?PZboHLJhE$sY~2L z(6OU;4w!72$=N<2Hcw1`qE?#&FBIF$x{Ya6%{3Dx;c+ANnSdsOhE4T@K)W8~UP)CZFo^mdOw(U=j)6g4E;|(GESPYLI zZqG$CzwuVJ+c=}WS}`CMynMx;tj*$_8VS0NrDR~)VChD!qXBjKa9qc3(|dO3Ki^x< z>|H`ACXvzG_}Ac{k|m+iWTpA-TgDrg?zRp4{6CC+gLfS2yLB5inAmP?+qRv?w(T^U zBoo`VZQE93Hg=0R$gVv^=|GkP~8!PvR*&uLRPGEj` zaP65c3|2@BQlv`@0@|3!{<2_HcOA4Dx1Kn-s%b8=}y7_)p1EYN{|!_rv&ohFuFKGmEII{GJ?sIqz^rye&z;QT1(AMyixO#d>1;17Sx? z@O)i3Cc93HWH7B9wHRxq+o!=kV`ew88D})vNODvFW{E`3EBi$DycUl9vGQaPk((|i1pkt!z z3`7Q=an|I9!@(xsgaqPhDmx@7T)op6QqUUfa&retB1L3%lxBwt!QfPMCZ)qv6((gy zH56z!mafj=2ka)X$ur^=x0uq}o8~OqqYZ6ie?q7IvfW(7wOwcZvd(5x!F0te;ciRe zV_VU4$i8f|Zp)f>*~n4yVrutXPiEN2-NSL$`Mn-~c)RM{owjPT=*c9f;!>fy$)CG( z%VDnyL;uagm9x`f2n79&+W=AINAHrAq=@vWldV}G{Jd#KIK(@DT%V&y7O9O4}}o1T|z~Mmp{uWaog!y z-x-RLSB<$J1<~Ac)v7(;CaL>~gGEoX$RTTk2mK0ffp1LnFlWBPu|{8F7nd|8KuP%J z%60@{UF&wE=su#^-1WIZ+je=M_S~DrMvI&4)34xER@-dp!V1(XX@=6JM&s5*TlUQ9 z^vVfZup{9C`At$2;R&_SDqL-$V7r+t$J9&Z=Q0CnZ>Aur7_RC*iwWsjKm!qY8#umH z+@mYv5?Lxon(=Y*jj!T-cwX|U7ri^%q3mXtY~^#4z%u(ie-$%9FaC^E6umX;&+~S3 zx`hL*q=KuIjPaZD>|5W>yKnRD!SOr6vr8Fy){YQTg~u4Qw-ZtH2gE%~K2!Z%x@O(Q zJku*2!u}&GknJR{nE}BQtUSV6#P}7q#xE3JvDaK zjPZxrD@@CXpsm5ec-`%wvJcpVKmA<7wrK7fX}X_tW#o zwZ!EtR@2kf(b)>jn&QZhNAX-5&+G;fXm&sZOTlMTX9umG+johc+Gv#ELbQZ<|?vsxMY9l^Z9%N%(Z4#8K z$}eS4&`x(u`EjADIBWJ7Tw7Ru#-zjnOc=g6oK2ndf-a^$xQ&}D z*@kzBZ%9v}!vZFqW6est+=Qn(r6EV{Z3`rRJU1pru2#{zon*i5X@m8M4Fc~(tM~<* z-8{bfTpWBTO2@%h?5U{x=;}kppPOAX3XWzy<8W*)G$=MtlKS?iSopVVccWkLlP^Em ze?!oJ4r~8T(f`8FD$P%t+AHY45uvI*yoRv=2dh$@i4ev%#eK=)n8PqI15h!e(_Uep z{F-A;;3E@UuE;m#wCDLYb=RdJgV~Ajk)Ofzc5!eOTfj1Y;Fjv31Q9vrt zs8dt~W~Y59l%_9T|@6iT~0(0%R?N9B+h1eovBy-RkAn#NYVcDw5 zgG2=*MEB4fFzXZ>!043SAqZ%J^HJVuz}aY37;x(x2_N!EK%H=?LH+*Jh^l?#NTGFu zOwqjCC4b&~1}V=|2loiwDYBCVmNRRKOo09r(pv*TfcezX+k*|wO;=3(QLdLN_pC7y zk2?uAaW?~O8~rIoe&X6SYy8GJi~3q6AP43}ZO0z$ZMIBVl==od3*aMF^8G1Up61|8 z`9-v3;+ijuegxB1E1dA85w>T^vT1f1x^jrY)oE-6{i#k~V{e9AH)Q)nHALXFQNB@i ze}cOTGY;oPs|4yruSDEOzl7XJyF~6OT7K#&NS^K~N4e)yuH@_nGmHBMKC6B3*>3k?CzSgl$@JGX*2my{H8s5)Y=jOCWDK>cv(JgD zRcE|r(Wy;txo$BR4b3Ry%A`3bgypuVp2hSNxesE(&HfhFM!w&ypC{L{4F(aO(hkyz zJi+;MzN0%`{Ep=^3h6iNnK^BYnP9he4qqJ@mC|u7Nf&jRVrcw2U{y|UB5fB@ z81$gETgnhm`MnL5rl6L7^a37I{9KeOMa`5@WxCcF!}a&`sNu?CIf+8q$T=WnQiV^b z4Mw}%>F%*oI?WfOE@K%PuSzfHqJ?A~I;3hW=B2s!>(FcQfPm5Sd4Sr0%}Kf9hH6{c z{U8&^a1;scN#m=!yG!df>;>jJbWvNpgX1mZ=Et7zdp~bk2~9aAQ|4II4e+U3C7E0$Ioc7a<6=jF*)$H4vZKy|%V3^qLkSx)USaEugfY(C~ zY<)PSUXJPWmqpx1&pZy3G~_%)&DS|1a)2hk47B_N!`3EdQ`bnEO;%VsKhp%x_u|jw zfHi)7A0(0`gRkhGF-~Zqm0=Zio+={7z7Z=+wQZ|5HkNUGM+{UeksuYqM8ti0XoLoLW@r@uX zYM|~I5HN_+0{2~pQR19DnKsm6+umH-f2`k2g4@7+!D??!f!&?=w8PLih5qE}zAV(r z>w9Wt)0qCL zB<|_P#=#Czt4*?{ed*Y6BaU!);&WoEzAurmV;-N2hQipLOql zIN=_CXP=%hO{h-Kn0R&5$gcqoL9J}WFPB2BM~8jmg}q3-2Jk{k@$aBJ%Mkp?$oiNt zA<4=NmDqSDJ@gr9hc>}GlwbdISZ4l|4?*)aVC$J6FNj7rtkLsvvt_o~#Uj1v5y#1pS- z^&@Kc$GsMR^y_5cVx5@@J@0*M1i55aa%zxQB@pT--C!&s=&1rpEHP9BN`EXQ# zEo5mQiyJqGwg~oxib$|PvGm|(nTnIQh&4JIi+jc0J!>Evo{j0wd*kO(S<~=n4`N42 z-o!WlDA*P!%yziQei)CaKp23>M)Z#ZRy@aQ5%FX@=EU`r>+dn1+2hX zh-`lNk`Y}!t)1#?UV!T{YcdWx(>bol99Lr&kUvB@Mu|T^yk+W1gAg(FYZ z${eUqZ@p-zy!%+$gAKR6oo6FEJG<>7=H}5E--?oATHv{_x-fW@BC2K&Qu+_*V;WRM zsnxsuP-Z3vSlztZ3HqCvR6!Zm#qZqWnS`Lb+JTux-r$OnTuy}BHfw;krjbUAXiSzv z$>_yV3?>SeKHdl>G76N!-YNuw%FzkMRkh=hf?RBC-^aI-+GBh021NG$pw>4Gj*{aMaYBDhMg!sXAI!o z0>;+HsYIHV&p|Z17KYsN6Bp7mc2zIpfTFck_wKV_TCgJFbL}NbN^TiMx`cYl@NYWI zvTQyJ>RFUX>Oal+?t(D@A;s?+we2pZ4zxOQu4yC!u)J+0Ml8f$e7*yWH)l-1jj!02 zSqTH=0|&VU&ZOXEjqR^C%`Y%PqnfT%S@;}{0oUhxCY>H2cql(I7gB9o;H~Si);neR z>;)%7%$f^oU6zlCM})F zPTKga%S=Q^mq!7n{q5eA9`EsxMAuU)CPmj15Oe!( z&s*G%0sB!Cd+MRSJ&OaT2Ut#<~}kLdbS zfB@`^)Xof;Kk`#W?>5A5`fJ1hf7ln9oo%qU8$))xdqa9w!WWMq#F@TW#E<7@-$opG z1}d;F@A4qMO;kTm#~N^cEx#R+-YJyymmskj8}>f!v1lMljefO zeCknvz&&tgg3=RVn7WG&XkpsuySBoPKu%Jcp_M_3o9nGjOqsyK!A?gqt~F&HFp(UC z-n(aBeUZkl`lcQIQ!?53qn+6|{pgTZu|5&bQ3t{SZ!G@NYePt5&S{50qAAAm&q3}q zPpn&IJ1kF$fhryyfh-<1fejuRff9~AfzBSGfit@#uml6zXuN$B6xV)``a|02K2aRq z(+wq_``Vn9JL{a~J3Jmaf#wd9eeO431NHX^fT8G_N2{LMx;v|o*{`sKir3Z~6+0ZX zr!9aU<0rmosjtEDg1Jh86tk>&r*Vo)Sd4T$vCIWp#FpCXE^WbIk@km+nDwzMqN#^d~O zm1VA+4chAKB267sWz#*ShTCKfU|;=_rNJ8ey-^{NB}*i0=3&-EKx+KCcx)OY+)7?? znT`TRD=?qA?Hlf4Ql!z;43y0YsUBwq*`vS3$L0R1YQH}q>4N=Ej8Ztp9}gxA^M=DnvLF6H1+th_6G{gxyx z13dcIY9yL03`V6DIS=@0lEN%DD$$VG2v#O3H)~j#fK-}6?f5GD@HCX1ae)xX)`Q{b zb)swtrQ#)`s<_H3K)H^!DXJyO+k+3Df(i3Pn~Y(mqly=FR+;^^+6^t`FjI{U?U<+g zc2(FQ3+tvnHh3D1N&+2BU7>Yh1-ty<1UczlNRc=y9Xs}YDh@J~L-fnL;`6|&bYiZ; z41Xgi1|rj62g>sf^UKFO=SFmbO00`QQJM7Nbpu=9x--M-zjc>pe?}N9EN7*I;a^zm zJF(Q1I#&m=MsQ}BrA|+REToJc=4%carfz~r&)AkB0??K@2@}Ed^xWf#>=v^9A7m} zQStn~I&@FPS3mRRI|Jf)sI6%fRY|yOMDnp}g3elV(nRWrB?0WOkL@2p&BQ;o61Q^G zlerR^xf=S)xL~=zrs9^Exkc=KacEx1`L>uDvYP?y+TS%jOg>@c*)_ie^;cvTa9laL z|04T%MI*buF|pyZh`rCVp70_dm{tHzHlP)sP4V$L>=W-Db?%jZ`qa*ql6mJ&^ih^0 zi9g%|{P=B&?x`Xzur|-I$5oyx`?twZ44|hOP@FoFMScm_v*o zf!MV8SVQm>wbaK*SDXP4$EK!(IZ7n|eUl0&glF0})GJAzTSyblLC5tyV zmr5(-Nyk*w$En)!2muh7YHrJ^RMlIlX>^j%-M)9GaqcDr> zSkAeC-~AL?ZFU?fUw_w+C^}-P{jO(akL=Z;i!%IW*>gnuLgKM@#RdN67(AUz9(4pC zEY^hbFwZsTkM-S;0iO$^M_+XY@Jq+4YWoPf+~Ot$O2TmytC~+5@ZFGW<})UV2~74Jfk`cHF=bYFn29agz{Y>!`GcFG~IzW>u63-%(y6JNnM z4{pL^cr;Z-a2=VM{VLWi$$;otmIpnrUVcF$0eM?TkVT0SjZ`WF3~i!SouHPWG0Tm{ zZUdI1b#69_azFh1ymYr~gS{u8seA@)@nK#|&V+w#ypHsKHQ+FIb><4DxXum2nWYr( z^T%C4GN3Wa=nPA5emlNzFF;-E9&Z4|TFn)XD|0N?eoU%I9oNgv?d6Jd_RNwxWX{8E zvD2{fW7U!7beGKLvyQYMLjlKwvde5mdqVcqiqgj|Q8Yq=k`* z5{l?HmOS9f?6Jde&oRweVn3h>NR^#iT{;1;X!n@TuomYE1uQ{c#E*xx{@B{x*Q9pw zy;LX*wDSHZSnNk27EQ%W)imR8Ow&aDTLy3|6kj;AowA#B<9|gZYA7!Jti}1xsy@$| zGVE2Ei&ZtGs~Os1*%`C^NFsYbD|EEHz_uElASxhHFsb0pk#pe!?vQ+RG#)E^3~MGL zgrFQnkfw-eeLN`gv%-ZbO7>m}EC<&Ii-saOM))brxy$eNPbURQxDN}7@8ZO|DF2qO z{xiAxkB^J+-qfg zo{_QSDLc(8@sBsdaJ)B}gkOpCq`!(XpoLGs{b3@Wxk)B<7GgB!>|@rQm1b|o`|C(w z!4HZtO51V~O-Jn-7|4OnicU+@nzvINm>lQ^MLk@IrGY$PiSC%G6xT~dr5C@4j&9jm zNgRY(G}|DVpSw#uEyYxp7hMnJQe~Yq79RkU#=%k6iCZC4W$TP;z}f2a1bSH34>{zs zzWd|3drloJo7pb80<4|m3^NyB={Y>BtTT#_+kv)ATGbA%);uN*;;Soo-87l}SE>27 zKBO40SXV5W)J@T!EWcELLz|x%D}krU@;(?_LA0U}@DV3Gqy|5bF=Tef^BzbrFk)xQ zjU?8>7k$ON!Yw*l@Z%Rg%xpK(1bQbCb(q}xZ69L?8%WZw;sy%YJI|cj(XFb_e7mFM zZfiUKWjfjfFSGH`%+voRfgXGMcaC0ZMe~u%P(4x3=*H&WYt?7st2OXu8gt&28^8ymjkHq?jWSWYGsk8wv(;FCeD64zVsN00X;C zmLOZhI6h43-r%4Z z#M-KAFkeg%kMMg5ZfE zoPEO^VdQyH>t;;Em>fyH;i?1eNC%6NaFyH75u39B&5z=l-JO(!q|LxPd@T~m-#^RX z7xY%;3KuGt{2FVQfL?xS02QextwMAM_K|E?r|tHGR@?K3)mxH-?iddXb(H?Q?LRha zM0X)^)8E$#IsSE>knA7z%in@WLhlxuW`@Qt?`l*orcQrdc3WjR1vDWfUp^WDfTXtd zDV&%v-IU0MkwEq{IrZv7K6!5VhL@Mu3VGLNLK;E;Z?rdXOw=gCPX!upAumYA7x^iD zA}ca#E8eo`q^UuB zU1akH-?qtr(%~dc27N$QLi@h+oPt^aDLhZ|6)c8m&E1qW<*{`%*r%eEpTOb7Y3LU{UDRF~wMjM0mD6w1$95WEumrlW z*?4v{t8@Yzn(0 z#iz?MXK@%A-AuIbqj2Z!oX5#pP57$cG*3Dy7@rHN7T)I5aHErwi3RaF#tJOs9%El{ zcKe?*x(K1S^5&kuhDTjNcN5s2Hh1(_yY( z4pYXWqct=d4lx42Hglj}2vF;|f1+BCjnX0VFSG3J0F*L1&9OEu1ZqbNF z03h}Z3I)YJ+!X+(bQ4PAz9oi)@&v3jgwaehP9%upvoscGn`!t@&0FB4%K+n72NoEgE+DYah0AQpANX@H??~&FFGQ z;df7E3F;UzC468=M33P$gK+jp)7-DN2M@ffu*uRoNfvv>`Wei0vwZQ3V!e4g%!^{K zvityR6DA!B>+H*jA^GV=naUk$&!r;AviLtdGj@63!k!0a9C~tZ@~%hViV$hu@ss5IWE{l7GTgn zvVJK{?`FHuWb#xo2w+Mt-Ije(8eW*=gpO?*<9giqahv&LBU8Y?=k@%s*YUlV%a{Hdok}wI&DDB)Em^O*Nt-Y_A>PO}M z17yep3-GZ#;mO{Iok_{pYyZ-&!_hYMZkJ9gofKVl<@qA&;cBOuPb(0n46QSsi0eAA z0zYzdPpADHuYFrE%WB8-U}#`HG@ta6oO5}!`l!XFwlb7&xqoB3E9kGE$n~6D(ad8N?>?jG6pQ*9~nqqatD}$ znM$!0NuNi{;WKB*^qT1Zt-rZ$awekNvE-XcT?pNi#JeMs zF^B#IS3KYPATM7hzt7|yn$67RboBPF(m^s8c3l6pHBXmAa3u47RC)fDS4;Gd9*Iu z7x5Z)o=d05d0Lyy4=5mLu7H~>%`@-eo&2b^wUvE;F_Pl{L3U3bIX?_eOlP-s+%amO zdjvk3DjI5pb%YR)gUgK5?n;O-IyrV=4CR(nNBE!GbKGRI6YjeK2Hnk<7(or6l~+nfI=3o^pkC03p^Da0Y7F0r3{wi$%xSMI`+Pjq z*-SIs1_GGAlnCsQ<;;rsdIlim;#DTXC-b9zAs(IA5CyB(^U}*FGYwxGY#l9nUP4rs*5#Atrb_D+rUm~ za+hXk8aeeRFN>s`YLLc#a>E1e4%*ug-MoeduorT69F{RwB^&D7w_uY8meQF}b{$C{ zFS+hQ#-U{8raeiss-25*HwuxO5m#t4lj&R$1^dk}lVjvX8li_zi#cjEne45H3LRC$ zCRtMNS8dy)x^{-9YhJ){^L#ISMQ`XSeylv5g}igg$g2;bEtizh@7ZWiO3*e)#H!$t zo@`}mnr>LP@C|niYAV{%D0wQsL%q)xqdR%^$BKRsZc1kjup}#XOR|}h7&fF_(3W}? z0#3#UOz*Q24cs-QOeXW6znXa@u9bD%!D2Jx&EjCcBKqw9elo{1(aauKt5^B?9sat+ zUa9&}Aomt($*Q`)7*@BjN9*$)o5v-c)9MNqPK^n_F=G%s9?mbg*FqP{Acb#lEZ%fYc#2CNWH`biM-$Nv6Yz6pFfi(y*s@i9w z$S(OCI|_7QL({jd5|7&Wp$|-52O_Jc=XD-v#N`7+Z)2&a7RwPzb0Lgv=~w+0@6))8 zyKB#j+`v4AD1+HrU}M}n;=+~ebKq8@_s;RpGaKD?1AQnWReQB~lZ!2DC@!JLeM1pc zGGYC;AFE!Ce@!XIv35>$+UnzqfiHke`t?alx2(23{IGEBdo!Tfe-G-WNUui~uB#aK zHrm0Kv-KRvTDosWzZPtDUBD1H7z;4k3V^=BXEYTSG0w+py;5%|VECg~Ohb%hIN53Pl)> zTzWFY>+3pF-kU{)V4S@hew6UQ%pS^cgz(?7KQEMH*Mj~5Bl3E!mMVJb<=Q|vhr4SP$x zNGq5bMg>;AsbW_Wj;9cdm}8u}c@igF@~@W0S>~?LZ{$}7%8bL&V3w^F_8gy0quzza zw~E%O2ZDnResC;_H;K}jgVKM1#;4GRS?)%-RMe>&U;g~FU4t+liQfP0$@lO7J?Mjf zzfZDKB4Y9?V*1jumUgC9$};xroJie2GyqJKOB)y-WuJ%vm_Ec(u#+JZ7SaWQ|b51#Bx?Pix#j7tn8o>#n_ zl^(}uV;`_}b_0Mce%NePCKx;nB%U^|DZ3NQ#AE_&)LoBy&PHW6fq0 zJj^Dq33ST3Wp8TiowmfiXmi@2rH^;XLe%3y2N_kss!>YFqSDbm*__(2>u z*<$UNLYi!6-_HT|Q?AIZ%aY=Gg~~q~$IF824BR=WygfcQO&RMx}ApPU6JK>65J{We9e93RF;`x3>_gW6A^w=QJV=sst$Y2GkEl z{SunNk(b9%Rk~f|U_Da7JCgI#^}4Z#JFLW?j#L!&Zs%$Zn3@I_35JcEo(d(F6dMf8uRGw2<|N{*_{L_T?&DhHMM-x-Nj zoA4MgFn$aPf$4)!U3$d6zFIeop|uevP;wLapiK$#r6Kr{#D6+0;kG;O@iFt*E;xH)}MeNcR^( zOH((j+pJx(b$A9cks&oL0#ZH&ar1t-FtljmHuPV_NPq{+RMGAYmDkYB)-HGgtjxPl z=(iu;{Awl?Wwd{mMJ7To?i(DAe-IC6FQ@ofjhP|&WX3Spt(0fC;DB0uQPvLl{_7jd z2As%%We!x-6C93k=ri?PR3pD#}kf1M0kU)m=Y5rOB##$;Ur^?{bS6B@zi_9L&J@M;7}>V1p+f4#wUJm)P)co z0?30OsltRJBSt5&hK7!3i8((69XFxf22_ySq)#{o8Q-!r-PKQrDU{N4$&)0dFso)E z^O)$rF+Pj38%<=fdc~|K*CvV9ci_DD3ABwz)dl#oa1S*Z-|NzbHBjpw;8j~WBI}73 z?cr-E=$qf>AUjGpy9lN|`x4o}`AQ;BowB#M6~+0X7-_nc0x%%Qni0@U7T8`JPve>7 zmLnsCYfUjDT1(KJ`8}>;h+7&b$^D&mEA>$5+A^lpB@< zxQTax`>*(${oY#bVvkdVrX&X}SYChX&kUVhr@Fm|^$&fb|NBk*U()~!Q)BCQ)})8$ z-*Q?}YU|3l5=eX#Vimf}29tDdHpBoZqDXX}y8RDC;U7O4IfgSKabcR1!5iIJxp(m> z?{KM2V;=NP8{ZydW>V+Ds(Idtbd&4HNw<+9aeTTizll`=j*xtia7o>gy(*?%{8aK@&PIOndsbBOyC3Tv0s zn^V8|?L@iE!-w2nJDSWKebDSQp~0Dl_|0R={qdnD#8z>Z{Rkr%%ved9!G)F+9dZ-i zc?uACrQ4IcBOA019bA)3W6Wh#q%#il_VF?10hg+7;|^ymmQ*~I423Ut70!t{%UL(= zw?ZGZCq27ozEk%ZjJ_7JK}&llcTe0GET!C#EisN*(u366(U!yK+ig!$MSb@PKfR46 z&Gm!i6Q0G4GSMx%^^oBG@nG}k0Lk;&W&+R5Wa!{TL%{vy9q;b!=>==uqm<0neg0vo z7!e5>Z^WtFHSN579aB9-sFgY+LvxuzW2_w{O?89fEoc5wu)NZ`e#elD*+|XxTj~9) zAnZ6EfJ$QYuHMqV!%qs0{n8xc;kJLDNB+|W^yh3pF1xgQZ=Fr@$Kw+}^v1bd&jQC2 zaX+mJqczjIcDa>za=!ZPs?Ty@7@R%7j~5M_*VzZiqn}xMUm{Zhhv8qyDniA7mrBec zIG%iFZNDi+zQ*KBXTtIK4WFi_UzyFATD+cSc|k+bs8_jQyF>bcClEZTiTdLJ+RAMY z37kgh+63KxDK>Z4=Rtja<~V}+dCiJbd#DZPxmnnZbkGDoXxD^uP>C5aS-+oCnAbZD z?kJIZt6NJ<0A5X}GXLx=>-M0^^RPv;e7Sw^=kr32_6T0SR56CjycoS5Kda#nAXfEK zHU&o*0VAXy#m?y(Ti-{KyPi4W0`|E4tc1Iu+LSXx!Zzm=stxO4#FYUaay=m#xof{@ z3V;e2-`agJ6HeNNbXTAd88ADq>q6`-!dObfV(I&E9AGJ-agnH9rDu2Y28Vlhg|*q> zJK-P5FSAivz4rb-v;6Bcf#x5`|6d$y!@qsGt?UeC|5OM5FT_uJ?;bBGV)426wnyI@ ziQSljS4EiZhx?zbFc_6zuNXy7x_2yPs$U{-%~n#ge-#2p1TC+Hf5X2M)6P zX8&@^cAjze?tXi^gY4yAmVTFhugoj&o2$(?RFgnnpq?x@NLK2V#oniu-3f%ngxBep z14p04Z4Dv=cXu568l??o)jIqEm`~QAN(yjxTeAKkaw*Qs@8JI03_Z_1kgDrDHe36n zOR-(sko0s*X+}X;e|+)XuZ|WaX2=gK)RJ=lnNp5quPd9vQ-*Z#ewLu@hX=-|nzOe) z8E*EAA4nvhI2K4F5hZ09c97lOJHS7PSdxUH`6O^}V<8OV){|jH>ddX>1{w~-8P_Ga z`owka_=$c!5olsg^=}za;2m)MK zjxSOLlJE_y5~dOSNp5-67L?5$!f?_(8|31Gd11G|mt+1IB8^z6CE1YhW03QRwTsgk z@{OSt=dW!z4e1S_YP*Cd*y45II4n3^AQ*u)OHJha(OYj!-o&7r)hTorL@Igs%H^BQF z5JD{5;yA%+ zE|ijL-ZQ464E#jc8;U|N+zAdRGC>~JXU*ICaO1G5-=$aIX3$yAvK|J^o5c^71=<4W>|QlKnzQS zB~mQALYu51Gw*(BS&cEz2e5>~dKa1B@#vZB8C#sr@Z)J>eaqdKv|iFGy+mlC;BmnT zbTStS-#FGF`K4fi{)>1sw~z3B0N!c*!Y7M_sJrx{!#9Q4xlgNPtrBcBCRMw; zEM$H{7dM#Qa?M~zsDE7X8A9k_w)ZUH`ClvBf3jTvy5gozf9C+c??b|VK_p+AMCmF} z{!@5PX-E*f9uom86BCiwZ0fPCb_M-1Jr!M?e-Wk>>&>Tk(UAtlD6D;gEtkWtmWGD) zKYKuGsxFBxHmBd_4NoX>4Y1f4gEIrAz^Nj!C5?6Zf71Yv3SLjRWQ!~scQWTnux*DAr*QMZaD(0Awy87|3bd$?cTo{TjUs?TU zXJ`r_nXUEv_ib|1QZ0d2?ildL2SKQnJ#vIL<)vqMv3^gYCY&u-U*s^07@~#@pF+fg zq!EJ$({;L(UzvuT^k7$<-R_;&kv#k`kt*1s)g{qGxi@ADSFvAQ%}4dZXPrr~m8mG! zvI2q>%QzBdf^dHgTE@mkcDr0*-TH4}ocJRSE55id2Sjxr(#7o#x*H_OZPL04i7~#J?nI2T%;fsa9cM#o)H#L1;<( z6)th_g8pjxrsF`x7l=Q?2_7NW`-kpS&c7ygpZ|f_%C2_*=>7eDqvHQ`IRtK7wp3MF zQ=vmx(Wu&w6U!f{ix_<-Cx@34Y|GIFGPh=}o_`{IlSaWsWqA7dLb;bV6#LbB=diQG zJllD?)BEA{`H}4FqlfN51XwoaRPM@5; z=C;%}AMIRJm7vZjBbso`<#iUf)((0^O(MMOy$&>DQ@BK06RY?@hnYv?q5f4pVwS)R zR`EzbaZZ2zcZ6N+2(0CZb_U9u8>qg8Uu;R+so0@Yzf>1(v4@n-@b{k!%aJp}v6_3(HAls9#DGBUPS=@Huz$nLjRA&0Kn2 z^t}E2sqvv?r-X@NKl`1fe$1?HvXOF=1ke`O1(FOT%$*BipaZFZ9N1e%9-+Ie18soz zT+`TITx`-CKPW0ZbIvgb@*slF6d@=ItZ{5FVbfHTQszt}kBnViV8a&|W8pqmpHT;~ zKKw;AA5aV$?QVUbsgBW;XrJ!82XoO53%gJB4*&-`Rws)`3&;|<4lBEyR=M8G21!r& zjvc8gwPPc48;P-$3X7zGM2f@9GsL^{DxR|?v3D`|`vw$osZCZF?HZhF+YF$6n|#w@ zM)qaL*6Pmg`bT;h%0!AwAU|!3*J{07J>uob!!La56+K`5shcU+1vr9l=9-#q3Jmaf zSz3N4{Jsa-B2GqkZwjcWZ@1Qu2bdTWO8IskmsSx41(nh6l>6~at7FeKnTSa#`mWAn zuC^-fqqDx#?~X8?!HYxU1?(h5To={pD1(_>PLWZTD_+G$)D=J zLw9y3H0*T|jzne{o5|*>KH4xS-@9rG7GLW^F56o2I5_$_y)J*+S;WS&yO_H*6s@B; z0joGN;5WBZH*fi}(6iTVw%EJKA+4(Chh)vz$wtqz*I}!Z{Z5)^?jC{#ZRO@A{<`IF6EyDK|53dv;cw?2tm#uvM{-=g2AEo=*#swH38#2+_zTRsXab_dElM)kb-%3cU>1+LZe&Oy(oggy87IANf4AH2>P zwcXcd>I~YIcoM&BxGyCau7IvgMhZ~F1h>Io5f?s_H0UB12Rm!TGC50Chpj5=%97=f>mBk*=;KKj zs|FHBK}t0}-0FjFb10=9SM}E`P6(J7M$}yJ8<1~^x~d`y^}N7-t0MWeTW%JyDOdxS zA+>O-#I&09V>PKSj4VFwaY6a3oUF}24`U6#o2W-K6)pq3=3Q&aLB&R1H}x zWHAc=jiwt_$|5S8kf};zdsmk5|2TW+_B^-tTX;2SY`d{-+qUh-wv#)yZKJW1#4B=>_4tAaLqYSjd6})gSuGG_N*Fj9fVE1C?<$^W=mWO-4?BP z&k6q%aX;Q}E(GT<^Vc(wK)h8T0*3S3{quiAB>&2Ue-}8#8c^;yi%4&|Uy_$jiKVQG zLJ>(Fj5PUBxgo^hB}W!B#_D?|qlBb8QkEY8$!b+|J(iLgtP2w5#=#k4S#$kFHytoNwtKcIpT`uG1cW;1M14wZgP?Tp40? zpY*lpJT=20v|Z_9_#XGs>pnF}FLa1>NQ05`knK813Lpz1lO|9ki=>l`ktz~GNg~rp zwvrM`7bqcPNE%g>bS7BD4RA^7(1hOufevIGlHJ4sF-hx$0W(SKM3GPuF48((WNwl= zW#kSLhjNm35{GmgjNM|}s^QyBi@s=Euqz$1X=BI4DC{gF$BfbO^v;w~S{c4ih3L&u zs_q@I2zvB|@RV5h?(B)$>7D9i_g{=}=&gdc%h)E) z_K|7Fr_2uZoy!Ee$3)rioDPheSP#Qc*Fmu}dw?ghGk7p3wx}90Z>fZfbrvK0c8d3g zUO|B)5cMD6lQGLoF}J0T0>feaC_C!UKll}|7!vh&N@aY94!%MA2FX)B(Uze7gwIvF zg1k__@*?x=iADQ~l&c0b@S=KR?ylIOf655_x@Qdi25^FYi)ny<3;06uM9x>Rqu-jh zv)!uPTiChJ|I`}zeskv6Dvbnvj;2aNRB;V1x-e5wHYkp^ODI41d80IpYXfNW?X8ET zeZ?tiJ!pb860@1h3CwE@`c3D3hY6lF2bkitrW3~i4rB}AJ*p0EUx_XOOyt5 zm3z){xbg?jM6KuY$;CQuVaZsy-S58V3|2AJ(dC#HH)^cSozM4z9@VlLlvHDOv|7$O zel=Y)QD;Yim~5>em$2Z4DmJRf|48ZEuL=9Gl2N=?B6^w`S$%r#zNQumON+Urj4M|D z^@m0ZK1SK?BWiT{I6uV7hkhB$&ad31FZNvViyO1LsKx$YDV8FuYcZ^TM(&tD$h{j) z%9Av1%3E)r(GADHT5e)4VSa5lS|80WA^N$Op#3VkNAvNjq!|3$IE9Le1r{c5dN&D% z!zoi{wR7Lnz7~Vgn(=_04yKnLAmXOn@u%n$@8mLiq>DDvY#+Mr0r+kk#rim0Ymlfur5_eeu$vGQ!HiJye*j6S50)04{Q5qS9Y5wD@rsQ3C=~EbG(Rj~q~()4d&LS2UTlcy$h|*8C0)l$g?a|mKU8T-DSu!u7?O0n z{lIJk`+{6@jx&qW->uONSqD2Q&MtG9T+yU2Yx!EiOQwLbgrY@)M=)1Kj7!}#qLcTU zwDx0sP`Qey(vd-AB6#Sq$$=A-xV@JNPWr=Oq>|lsZ{e$+^wwcM}j_}zLmXhQdvbP)f z6@iM2t$os&i<2l)Lj*V;7H4W6YtUassjSmkXXZjCh4xG ziO+gB(IGeZ(`Kx`uT@@)*Nuj!NwhC<$}pzk*61cu>PG0z6(o;-`x4d`%Q3o)Qfdi; ztQfQ}_-+Od|_4=NmNVZ3#=Pdum7bc;M-NZ}HII>bB7wr~W21HYgxdXP ztFa5Frx%ET(JrFqoo$c1{XDQ$fcfp`1GPLUTsCm^oARVwFgtc;4An*wd4>3yQp%Ky zH)(89Jl)1#jN0bb-~pGBc5b(Ra`lL^JZ6pLhtNy>PWL|g4Ctt#f7(0N6S3*YD7o&5E1(_z1J;z6^6%S9$$`r zDGPFA(`{5CrLU6^gb%{<2Z~~Ohv$#Q>7ifc8}|EG6#+Qgf*(P)3riPSZul)f21x37 zvAyh|S6&@v_1Grm2M-NlEr@|)1@kT@uCNE$%t}T9%n_rSWsFRqpTV7F;<9>T!_-U` zmkiK+)_pf;Njw!}w6Mpu3*ujD4`FSsI{ay86*NDJ)8+B0TokX)DulG~ka$oF(S12^ zFttC1U79j5X4y%$_+fualC$d~Yh(~Out7rRwV6|2wSx4;3tHV{ti(YWsvw^#Rwbvm z*P`sm`$zSPBRsd+jEtQAe)+_w@6|+ml!OVr*RVC1;?XT7jq}>dOPnF4@ScZPPy55X zW2iaEaH?CeJ|C3QdK*d%=M+U6PK6ekEuuZ4qE=Y;NYeyyC<$_gpmJK~b;k4FL9mAi zAv%)}9_NLjA@)?YR$e*FT=OH2sS27_1b$c)ctAvl)_p|E&=>yE33uO$XXOB4dauY5 z#*{$2n~UryxhDLBuCUJcBV1N&spya*C#HpAWqfnEH52Nyc4mX7?2Q1-sx_%JZP95w zr!GP~S;$bhfK^#wf>M0%7Gkov?1HkWy$sO+Y-(yx7aev2^GdFuK6;T$!yn_YwPyz0 zA$49|!R{nMTa;ylkDO;W?aY{WN{+D}q(rk4=u23QwU@&4tvR>i9xPx}d275DrmexxuP>OncSu_Ptn zF~S~2j%$I5bZ1w)jwNsNS)Sn&^VI*ubPD?c$)*D)m8&NB8+zEXYVCq_eqy!Y&CeAf z>KDS>OL`|YsfI09rn80A&Dky3hpYgZb$SAEEI6AtqlGNmI zgl1b;2U69?RLs{Mjeb8Sxp1wR!c&pO3!V*fBA<}+G(tF~hye}YcF^o8+=!M2&57uk zb#aCNSV~g4Q(#mxq__7Lkt2Qe`q%b%NMThE3b3>^19sQ{^Wc*5U#<6_w2=Sc*N&!! zCclUx|IEg1lz~GRMMPfdx>Q??0HT5d71I#kUZv{}5n)7p8OqO#MX~FLQYmL!9jh1E zcj^HFZ1Exll0cqpTcgOlD}mWX7Eq`%ee~>-ALyP)QP@}8XTVs+#AD<>aTUBv+3y6v zpp4q{gr=0CX089KEhY}Y;YvuG?L}n+WPUt~V>ckGoX`aYKY*pGHQ6D&z|!dN z3@~pkB(xmk*dvTTyjcW&iL~)(UTJa7gxCnDdbI~?tJTS0RR!{@+gm9|q})Au*CUu5xpYcuf)(+!JV~hBD(9dPP|vDM)F=(JA=!5p3dwF9!TXI*6T+BP6&mI@q-ke1x->;Fp*0{ z6gv#*v)|VMmxCkIaX@_sw` zVgJ|d`&V=G-xNQfD6WjDtEu%r#4sPFO(`5f)Hku{#Hv|~Jv*DICab6}`{lT|9)ee8<#uo}mpclifQ;*qS(|_DPp0N9Kv2joV zO++p^mN>SYR)A(Ai?|#mv{43gM3n}Fk>xx`ic*8t_|gx3QfAULBxCR`!V|0;nz9-ad z&?o$u32@=}*COK$95nwm=8aL{VoU>0{c3+Z=Kc57?@wQ$f6$UYP38gHRkjYBSp>H) zC?tIQG4w_j(KJX|-*+hymZ4uSc zWlM%GNuj&(xp3dlsaBb75FD zL7iSXYp#T`?B2pc8MGfHhkV>clH;-OGN>>anOpG{Jro ziy(_rrq`eXn>(%ZBXeL&qSgJ+BLlHRI{QZ3D@vYB`;Vtbe%v7@wb`5*YNGzAN%L1_ zn+8HNcsyJ$CS0At4bbiuU?*NL)N1%==+xXS8r)t34LgC zL796d(CAUs2|lsrhVz8|4m53@@r)FN2!dY2T6|H@{I)8x&;o)!N&pl078}UVLfd^Q zgY59gy*;L=<)$lNv!szgD8zdyK18_>m)d%l8gfn=7lP=h62y@i{nNQ322cGEpHySM z^3D)w%Z2!7kXtvQq)sXW$ma|t*pU@T%jd2$|hr!Q8kbC{8(S4*9*9* z>JhIuAx(T~+m0Bede9<70nzRw9nVOxUt#mshA|00a3ZTusD+!vfU2w|p24do2@6a* z)TWh=P#LK&Qs-GnV3uRXlH9h1jLU}^DHxFVk!D7xzk?7R3%*oVfz#B5dUgwv#0<`| z9?5`ydeU>h(ce9`Te5EtpHQf_ND*RMm4H5NpDN7$M6q2L6>+JsG`oYw5?CTBN0N?1 z42B)JTULx@-3(AoaNM#ct*qpx_zr;@iwl)<)DY@|R=n3^2^?;*crs{b^$81J^NPon zNjV}EJC8`rdrEZMh#iuDdMN)xp5$o8G&T3??u&_GfT%L3aGjJ zeDyiup*NuZ^dm-et;Hm()}X~EjdnaDbF;PQuL+;5%o8*nFab{gmO%LL4Y%JW!M~S6 zqLsCODTcq5wAhS;D4|t5D(ci>9Iq9G(K0dy^CfF38}%io>khZKp0Rlm9)C#yQzi<( zG24q}%CW>5O!}i^_3|u>^EP`u3+Ptq1EDZAkRc`%6jbZWW>HF0bHIpIp)nBa%ZlJZS4AbE+0BU6ZSvmpI4vPh1|K5hyw)lb+GAW-XaSyQ{N&DuY*}{Qv1$kW zu$k{?xJ~ONnD9duhqRA8Y$Dt^(xm+~;uagbb^TOU>cdY`P3EF?+vD^CTgjM__4;6F z;Vid~2l9j?2t!|!v={L$SeuSRE%+wG+a6S1NpS!hdoXSBN~+R{XAPxyE(i{4DJ@Ur z`qDSS#_y{UidjL$7+e8O7p1;}}NTS3Sb;$A-(G%OT$Vw*FJo-$;9et=CvXtjLtEJZ~3|rk*i;STn zS%w}|E^3Kks8fk%Wv0Oy7m@S2ZRRmu>hX&{CZiKD2O1 zk-orR6bAMi?SW!*k3+8zQZh1~@o1W4=Vtx^Z~HjJr>17c2I3pNtvSemk(>2rEu=G1muXH=i@o z8Vo@AI=qYg{k%k)@3bhY`QlOYtAJ%Pj}e8nIsXsWmE;9q=mu8o^!yD)t&5V-oG3jk zI@r^9%YK9N5X*ycJkvSzK?6i@1Bx+`6}R(}O%si`$y>>_(@_c6VQ(z-5{w=NEIV6y zF)6U`#%#S$Oj0fd@Wn4Hk%_A76%-K6K6eoTRM0KNt z;Vqh>`4R2857t(iYq;qfx6S);U4Ih6;R4h9eG2hbzs^3eO}^yg(g^wpsA+xCPL&E8 zr{{;tttTO~u(?^vAEyUStoR=`zDAwwrq5zGJV z=mL4QayX2rxa2IE*6aaAI%`T}qKX+XMuW_dP-X6GTS*m{TZ8_yLouCOWrvjldqmg% z?iU1i<`p@zLfW%b)E>k5)12m2Ki+O1;Csl_@MyT)4-H0ptsutX)ubqToZ>@IjiBXu z7Ze_@K<4rFjB*IKt$hqFSb~)Kc6=um>)r`@(Bd~gze+P!z0#g(5iec`DSUnx7zz}j zp4_oQ*5Y4SdwywOUY4SGnLQ5RK{cbs(b$MFDlghJq_B=1`t>XYA^TQT2;c@uFhI z_XEFJ^1W&nlLRI@g%ns$ttJ}5G2U3@3JR3lC^^#z&X<)zM7~{4s{P83$%jzX=Tp2Z z9nu`9%M{t>MiaAN1t5Q7Hs@e1S1HQI%-Fcq>@?abbUwxU1~7!K>i;_JioO=mMBr(M z|F&>P|JP}&I$7G9{|!aiD6Y!&0qtvplIKLhbsPj42R@ZW+hK>%Q^>2Lplz?O8A#eh z)6)5YU-#0^y77;>n|~rJU>?o#vYTpK_xA1T23;Adh$6-Ez#3{LiME_rx-4@cRvt7;_*QW zA59y;p6ou9h2$Gz-Eq@*^EePIhO_j;&0iH&ePaG}AUTCXGM;Q+)K0Vr`xv57+;P*tNm?S3R+*%qM`kaP$6)Oo`#f|MidbCs ze93|hRqE?Wg?Lk@}}X9-k~gu+w(zLZe_g~b49t+|HV`D^9SxIfb|CP zPAq4#+ok<@etQD54=#_agV`P|f`F)j09^dxbN6v&D9#XhVvqETyNZ3XcsQ%l;V`Tm zLx)k?$9FH*E#7#kHD$nqjy?<^J1v!}BiqV__;${ox`|82v~vfxUxUg)Z>zMA#$5|> zUB6{U+S!uHfy`>?IMzO%PSgzR0lST2UR03vL>#7{+EA^@jONm!LixI&h{H#xm`$pw zS&`_hcov~=h5R{0c$m3ydvpkIP7#k#(Leu(PfZS8W12ZYC_CW_+TEb=jOYz0L zMPva6erG;jcj0Y4fd8|a^&4=41_3#Ye{hKf{KL_}?|+`C{XcA>|Lu?%lK2m&31;0E zBVH>aP-zzJg;aVlHWC?yBq2_vXCZB8(-|*=bbWKuHAUe)t|bALA>)`7$Ub#%)-8_D z0Qx}zHxF_+FISoQ{d`{G^CHZlrJ%>4si4)+RM4s@f0;i+Lz^i}7?I&RQehsc48$oS z4a1?K>{=sh!m-y??b02wqB>~z6K~Abg6IYXKw#q=#~UiSfl_({tn)Sa55zZW+vb_D z=UTGVq6zgVx{WpHV9(Fl5JD)e1}kg)K!`^qY^xRcFIb9aopmj(xl~1s`VrdC;3Nv&zgKKWUVF~F?0C%I#l;pvPMZo+UszAt1aBAmx$7%mmz1Q)xNYmm zowNZ<@bNnyOtZJ4hb}nJ;y&b!arFdndEy1+_4WlOf%jWs_=zq zRb&zAat+^&LiPyPGj6>6`PNfR8@kDWF?!{Y#m3ap(Am{^f!Coiy+~sV_;*CY}2VJXHq|*#x1R-+0(C z6A4w`1h+Pf3V&=fM;@Fsjq5NWdpKaY0R29=rar?E_eWJP3X@!i?zMn{YZ_N+fEv1Q zso0s=ah+`t^DLP?AjLLOGzLwA`>KE zv;s82mqR=VQiDCTdqMP+YbiEJ7lJ*+1tDAj&Nd~3_9bld-R2i*?KWIE&lQ)NQ$x7@ z$Md!QKCg+%Y#}8SEXV21<&Qe+N4(pPcwL81(|5XdbXAX({i|B1tAVt`-*9w_hULk) zHoa7cq7cN*`S{UdJ;Fh!$Oq(=(8{D}kJ``LUImh@L$u|=6SeGM3*Ww+Z>Wyf{XB(D z_h^~MT4=`N=$iCc)@)$|5kh)%)bP*WXvXbEwRUN98$MeLt|4K> zQ7@KW!^mp)qJ(oL3Z8c3e$#?mxZ9j-k{)E9%vPrgGy+dpSuHoo{Q>X{bl7>yzR$GX zxr3P8#NS3Q!Wsfx#J^#d(nkue#ss(TYQFgx5p>9A+Bt;Y9CBQ#umF^BH6NbZo21c* z0x}ub=b&CCi$?Shb4=HkQsW(?^QBjzz2oOFtmnUqz5U~sqB_MR)Q z)Ih$$bIun{*Lk1x5FZxd&BME!XB#@Y&D`JDoIXE8Z@WUEyV(12b;mixH{Q)~Pt6ev zMA>6=N>I%Cs}E4j73LES#V!%q&g)G1@50@bSeQ1NH3{=P#`!XIin3vR%)~_L=K*`g zl0r^05p8!ZFzrrZR2BeEkEM*RB;V) z&{PbX#mdwGk;YK}d1$0uQ+RG6iwdh=k>TdE;3SNQ!?_(rZu#I>hWc2|CU^fjUL6I~ z_-uwGipt>C`ifvp%7$1OPlQ4jF7X%e>C)O*Z!TyvW`w!n*JwmR!DlOqCo+=vUB%qR z)@kPuQkNgS@ve!C49T*{HQ{1WRv+#!ub-c`RxENI;$9)hai8y~nu zKl@3T5uH3)i4#S>AeuI~D7mG5C}a=D7p6y2V9{Cn9-C(oElyz>%lNF^M<&b?%>EbL zcDKU#Aru&nXMgLYEc&lU{ojq!|BV&k|E5^;r^t~)O&30RWEtd*=V!|RP*hPu?82+w zMt>DX@6L{z)`M278aJ?4VP9}!HlYGv=4){*^DpKU^xsL@8<<}vn3+4c`Tg*Gz!`!< zp-WIPq0P`X6z$b}vw;3_M`%0}g>dR~68Yj0R!TbR@j#1P$-9c!UTq5QYL-aa!G4`xIf(HP869)8xx$$tA9n7 zx&zab9t62pr@K+T1*HF`vxg!*-|DNbg`gUUeyet!xcgAOy*sM9b+q<3u>Fost%r(y zu%z$JCuu{UX%TazSC`*I-t5{b)G*OT+m5_C3dVehWNFu8>_xA_P6#Pr$#)_$$T&`NjrVEH)%HvrF`qjBHEer}LXm8u!Wh(pNMs!y(+YkJJLPT0TSK&Q*D zJMDwg-o7z=81rUs9tlx;VX2ky@Opo9Vp?i}dc0?$Y0}u2G&J*U5MBaE{Y@~y3;=~mHEiiuz%fhnac-ikvFw#Z*9NvjlxNV zqXL&T1_Bo$lEPpKq(3VJw+6?lzpn`DcF2BWwKW_K2Zmy`HFTYAiE%p4t$-z-v0tR^ zCc?0Ag9lZ-4=x0LquGt()uiEue4tj(_Q3}U+KqkDw}9x?g2(ROO)touxM-7^(P3$9 za=$yZRS%W)!nJ6->{E1zLY8of0-;r+SJeMni0 z3cT-MDPv8JS>A+i({@MsEebvuWYiI+WQE#cxWvkZ?==Rxhcbh$%(V)h`2hnZG+Ja%|j!8b;? zluF-;#k*BrBFaBto|JT}SP>YiPTSv`M7bt=!%Or3SqZ*QQ%l@|wlaZ;l zsmb3xYuv0~?rIoIEr_I7BD1Y_> z42AEod+&OMz(i|kUm1WsXgPrP`0|)8V-!*4`YproYggDiDV!=-V%@|PRz#Nx38q#T zNq79dj#H41LpHPzPYY&6K-8eAF9tO4K|ax%wrk=2`(rt5f{yr}5O(o{)m?vh7}H1X zYXu5GmmR%SQbCrEU)c|p;{{yw5$*bON_Ec3XJKqJ-!zhR0!+=QGU-T9V~Uym^sO@e zX%+E#p={)NtK==$Z@30-xr*LJa6Yk1b38xud{8Q-6}*s&a|hQDJO#dKu~csMV?}Hh zVoB0d@Y2R(v%r-3{Q0VbSD$bMfu{ie+nV$@6XO5I>;E=4O;*!XUk7e*m_R0ktPG$d z^`VDiLUKsYvjOM?B-+zR1L8O=Gsnab!3jC*vx1$sTZ&$~TTX7~LdnR%a~;>N4szym zX9?!!+in6s5ac~EJbu3L-k!)#_bUGS{`xBa$IEqteAucjX>! zkQ%5~x*eMj`Y=x^!>0J^?wv#XF8Fp=?xDjSJX`zMe6a}b-Xr_O_yo64!Na9IeEZjU zv6mhjJJ+DG1Rg&RM0sPF93~?1Fz*V$#XBwfUx`e3J{5dKh$tHjmLPj2!oI2v6KkFc z3r)kss6&a3urO*jgAEg5ASpnLWk51AN()CK6HDhH> z3k-^sSwTkTAT`8igi=p;hc+kWi6$rI0hR4@f|PcLQbyJrY7ACSb4O!F<`Wm`!*Cbs zOI0BC9@OaW^?(-|C!LHrO&QG}Fek;GJW|-dgw$1_FaIFSY7Vj=Q=Q4&g&Xk`FA?|Q z$L-QbI9B}2bI8F$AW_!ZW^JZGPK&!Le|PO66<}hBd&<65O+a6ufo={;+w{bA7I3F& zBaP`p*YPz@?u6Tk4Q{TwM{#pzg8|`XxqqlBGQ_b75ZxaSvcf8+ozGdvISJ5JtbMb2 zn@M&bfD)mJJfn5e%9UK9+Q(Nu=U7a$c~j^{a$>0(f+k_>2oD#Tn1M%Z^NZwgbg;H6 z(YEJm6D_GuArasB#{cqlYK&4lda$wFcU|op6y4oHOxFwstFyA(MJY}mJ#+bG>>w=f z_mXbXG3ZkHQ?MNc9t&AZ+$5026NhZ4GRTt>wuH1uiwCgVNkKZ5v!!S@-U1xHddso# ztv|w5_0k1z$>90}2=I9wnOF`AH>1U~6ukgJ9JZuqWkWG2q8&%x4rO$4n(#SJFXY#OO}wgQ#XIPNSYZ!xcld3u^-s7kJJ zE*1MNTROtAu)o*v8$djWZUAoChi{R@rtc2#K%waZxe%Z`tH}LytXv)4?ZvS$`*niP z^!TV(kgK@oT`{UutnmT00{_kp{yA^ zEHlP+(Tveb6^2@(Zqkf7cMR7#dbW|+CBT^PVSv+&O1SP2R;*7GYr8{#XpPBGYTXUY zz$~MLSxa|Vs5+Y{swvjCjE_XOm*-eX2|O}d!z`hVw45X{O`Xda8WBQvbEBSwIZm!j z;D{61W}*u0CS?gl)1y?SO8Q)^w;Dg-6Jdws2JY5T)sd__@OX;+d8D7H{i3y@WTT3Z zy>fi=V55qM9mS~una!9ND$)chy62BkO%Z0@bx?#?|Mr@%3hiL&?fJKI=F4`+$2tSO znc%gl7I!^A|HKgSaP>ilfIxOX?C++Z|6W)5-y0l~f52cSn9f=?(E%4R?6U4$K+JiGAi4?LH3zL>lj{p9{hT~bFHKR|i`f%5(d1w+@H z&n=W%mr#1gSrMe&4P35_34 z4wp+wvD*xSH;+tYS$ct06WlwYVJN@8>{yc6VX8G$6&z2TT)fKNMk-yo2pyxwrqhkX z^-y!%R?;QxlJI@FDZ%^*T5EI3&BxZw%vxE9i1{Ipw=a3ZVX~4gAF6LAvJ1cUcODBCWYj z?Mo8uW6p6+@Y_RMF1LgIs9ei;C24UCDCw9kn~MeAuQQEy_hT9)Ivl3($Ont}KVSPC zwS=mU7j!of%dcxbFH)@wvYgciTa4aOX|eKB?ndD^A2eH}6?6cOumrb|_tss;Z>Y;Z zZ_%xf)GUpBuFiDntg-=mMGZIpIH%=bg7Xvk3At{E$6VWo5FV~2*zgKQq=w0*buBHW zZ=PG3VYQ+cr{Wob-U~;;Wkjd0fBD>inL9FiB)cvB)o+VsBg2AyUxb&O!4T&Wv?+LY z-fUoQjhtlHyMrow$vVN#{yvL?3#xnvA4610oISmxJ=QB(dGGBEnLGA{oARNAzU|U` zl=XOAeAkOkcucg8iLy{4FUrzW^WcQ2SY);GEpP#aPvR*F^E%lt@fq+-5o|XuG{h*9Vw-b_Rm_VJvlLyRb9&2gEKS_wRpv%V=FO z8~w`t1kKJBMX`l{K)(mV2Vd?12n#b<<+ndV+@v`>Q)XQk(806&RIF!ZFG9>~giYK9 zN3sYC`@Yb-&t-~{^4FB)lR8=V4`88B1l0NepCJ^v|NG|gZ{ei%uTzOA8g*h}@bQrjwC73IAMCIMtIs33`NBxx+UV)ZVAD2WbiOMF zMofaAiV=s7Qh#@svRRQF|Nb19{<+q5i@U8^UKXV5Qhd%74}aXzndbo?O6uzZ*ka>% z=F#%u+_~^|N=jm+8>EJq>`zGxaNP0s`ym-%5C{P%S}~WQOHsPAiO_>$eYn!wrh+r( z;j=1J4SrmHT%-IGJ}#+QTpxO~+7&;FCgoRt)>HjBCr$>XV}w;~Y31~Jr2Er5u5@jl zkse`>3QKpWHD%Q5#szibDC|i>AE!$9pRL<*c_xJtuxa=OMf}gF`KxtvGIFK|eso4a z2XZ>#nf-zsElr(F<9luU7+?XHJfaBP{vyZ%YawnsPzCrhr*jsTx`UK~vYjtyDMXFg z$bN6ua-(v@d5po-{TYZE;jZ20<9~xq+NT1XnA3*y&0jijwj)aKI zLm|D6uQVjC-UEtVgvyrf4EEhz{c5?LxRTZiB6>PeT@XFFk$if$tn#A{D;_(9(Hp{MOYC17UA(xYKv&lEY!x@3(@uus3h7D581pYQ{SR2l&9{Sny2oURh$97EN|7CbvX)5n3$4 z3A0#Hs$=*)1*T2r%tcBN5x5hj!~9?t*-Q7pcix%T#Gz3RP+4O=^&C^5#T7PsFqw}> zXeQ~mP+ahKBPV!Tuh5xyXt16bol#fm_SUXUD$-+4f*oDVE77$C7)Z3t^v_UA+x>1rMfgjCCZ0iYA`zX;) z2OAa@Z{NM4Xl$^55cNJm|0esGI*(Tze@xdhAKej4S&v&k*O(Ey+#5UNgo(519@s$+ zMKHod8N!8pSsKeYBET`BAf8$$ju|r}o#~8=Y4r5r0j=ElZZIYQ^F=o0KExd}{KG35 z>^Gk9u^dUP!bNxP(%3j2YpF>jzJew=1Ygi!XnP-i&4m;2;wJre&dB?(%lq3op{K6cR}@4v zgqzgR!^j8?aKM-^YjcljkL2WWyas829Xw2)hLs+#HojJUik}gGE8WomJiLy5N>~-gILI`)aJtVjF8=2BBX)(f~WSjIbr&ut8kj-%5H>%)Dc2BK#iCALwY$7tt}KAL0OB=f2_UbIXBNa zNnK(PGBIebdxr3S1W9K9s_^Cr{tZIRua9=eYbgvX!gS2Ph$Fh(_Z(2KhNifES3e@_b){*nWd z4ggUT080i1xCdsexG%XAdVaq91J2jqE=-Ya2e3^*&sZ+6 zhy>>WV#%{1KQ|Qsm-PODl!lm3cF&CN7lZnrOz4#I-vG>hsg*2!1 zRnyFdSgHvj@TF*8k1< z+$7Pn%9XTVV-dlWcFj(rix&HXspDcZIY_SyDz=~gjd%sM+o?zaXubIh^NKdrT#nA8 zv1^6i`ihzT^YNGiHK5U=DG)!It$1}IVrtbqjbj)d@bjxREp(u)sGbC`LT#RNVr0sd zp7e#A=Z3PDBkelXu|)Um$>MXnR$aKZn7dtb(-lSH`S@DG8b-i-GrjADUo`@bjqsuw zFLc*aBlPAvxGlrPkX~yYhOK_}>BoxIuk3*9c{xT@`BEMAU@C945XS2-1I^m`4tOQI z#M~#7L;d2!St1{o;|nKneWH=k`OkJZc!r707~7rr@9~V= z7Um#ULNU1qM!Zb62E8}(T*8R1BA0k-vKuw?*`Okn4+$`TL=8+KWqm2W5nm}L*5Tp^ z?GRuL(DwVY-V6-kocvX5I{g+Zko@fx{13+Z&*ii*bdt3BD@6Q9K>i;n%0^KVDB268 z+BcO9wJ!SThs{x8da5Edeu6BJ(9LU+%=$j!tds!k8l>o{@CSgJz226pB3%=ib{@C* zw_RQQU;m&mCXAJY*+(a%kNfhNwX~J*5&ow%Qri8TeuUvuY29a{^66!1RzSSbd8r>aZ1PS9_6<%zf>wqsU@CfA zTAY*z1=hz)De|>wH}W{_B?7Ypn@sVOs|j@0{t*_t5i2}U4VH7ux&j9fz^{z?>~}d3 zXIaIFs@0{ZDgu;yUB_oyCo2@eSw-ARA;=7aFe9H0h|i~+$FAFr50@`)GpMa;l^PJt zF3}n8zW&L;qUXg57_%F5X@hs+owcoKE#LE{#NgSoVM&H9DyP67uBQ_{NTKA4EXcRZ z$80zj`p^{&fV^+y@ZW41BOID}q@-1L zS>qsuz;OHB7>_Wi_xMjS*c3NPn?PXPQTlDz|92s)Y~(EI>hTL%{KqcJMQPm@2rKfg z7df1@v&d%N3Hh_Z#c?PoU&KK}&8kHlfGAN&blS`fVzxP$*nxNm?-30U+@;g!1*+@? zAnX((?sFU>&!skSn@>$l#Z1NKZg+!f^tT5}LocD~sM*W*x`4N!si+LtD~WrQjy$3lvL`fz-sK=BO?VE{K8ASaM(tR;4*%aF7~CJ8;n%^uS}c zmm8L)VH}7k{!Bo?Jb_V-(b+FM%uMhsr_6VaNBL7KZ46e4&Th?2R%wYZ$8RhSuUEZ4 zaG-tO`2Y{_#A~O{AfqX#ykU2_81f#rtZT&d4lg(uc&Ylf57Ey^qwyuMo#ix#d{RqS zjazrT!DbhOqSTsyWu=Y{23Bj*>IJUuFj)SWyIPlS@bkUw0=>`a@0ghn~yfN&u009&u`U_o7>YA)@k@Gp(^9k zdO%hhJgrCzM~SdFl5(C!Q<)`j3S7O#v?-`NKi==ql!__(6=fp5O44Z)V* zKRlpIKEOww@usZeeD^qYn`T~MJS)dM)Ilic4}s$9<%>(3;6=o+^k?K!o(-CRk-$C_ zmVsCIA<@ZSQdIR-D8@Scq7W^dg}L;VjD-SQ_y@+XoVWA14nqgLbIrdEIG_F%aDWZe zzsk_R%Z~Uz%MQTypb+?Ys5d2~=l{poJB3%GEnUMMYsGfQw%HxqwrxA<*tTt_W81cE z+nxN`=bRV&d;WJ{taY>Q)~s1obJQ54zyz^n@R&)#_d-jc2E&VKDw;uC4b4J7sHBKV z@!o;HL=q^W;8v`uW+qvwnVC;}cNe(bK+ckusU>O4QAB>Ox=6li62J5odO79M#Fy)3 zg?sqHgn}e1H}S41nhNWt4rFl{P{{A;?YPadqlQ$}rHo`u!>Z>`+3wCSTn1v=yxR86 zP97;dRAHd#n&3{b93yZgRNTn`!V(Xl0;LmPO&(kFmzn2pX&&+|Z}GJF_Cbyst-HPW z9Q-;CCZdj~SI;feJIJ^PXH}3G{QNoyN3B=U0cTPead6ooOr=6g3a_z?_hUMoAYrro zL*bWzgKgn8xltHvEv$^(XQoMczx%Kl>Uw3)m7i#{^w{J~PsDI;{>)SaQi@3*We#{D ztlGNVGY|q(;kTiYJeEE8&U?T|3kN8HQCTOPd!mPtb*lkQ0k%tjskH#S^+Iqaf ziq^K$a@Bx3T(48M*(MG6?^6bo^iK;@{5hKeYBp6(|ktB~%^*G}yXsde=~` zHTGqv{u**^WC*OKUl-e=+W`Y435`T%2m-=bM#h3tO$FLtmNl!K(soZQ8{x<>anq!B z$puogIQU9Wo3w}}qjrKl8^#aXJ$%Lw)Lt9@XDi4q z7(I@Fu7K+$giXLsCBi1$&L0Hcl0NB(#au8Zr)GQur{&0iUwl9Uz|t_T{N3@uL0}P( z>_poNz=L4iB)a_F#bA=q(|ya}G!fA|c0k!M23+#MMWBYg%f7BDJy317l;Z%%O2756 zDd-fqxv@Q9-rsm0F(@%;x`Nlt8IW0lBsmB@9G*mecfhpUfsnLe#S*}@FwKP9Za`ao zO<12f4H<~6>y7%f(p(n z9}kLSlC1Thp?Ip8sIpWNFKNRuZs^_o*jQZPenW zY&|-RVo7J2+{6U6;H9jdoO5p%vTVtvR|r$YTVhmdiH0^lCuPb(lfOn{WzfZ2h$`7+ zqb|d9fl>#z@mA)$(!%8@TEfLL3J?#HvLiCHcw9e4z}^*>#-}!nZ{;COo0Pn?KFl0V zbhnJqSnp%#!YO81TCfU=a5XQIkJ_fAf}|RA?;7#v7VtQJ{9uv1<>BxTqIf^QcgK~e zyRB^-rGw1vm;E$-f5v+>V?Tjg!GwbC>$U3A%Wiz2*rR6CR;CX#deeyGDKtUAYlL6L3I8Ek>q6ZEpEah+y^p|aAmTuplRSs)> zWdfVnmrlc+D=Ka3V%TtQ$AmF*^y%ftT%+8Fcr|0sUx{dV#WSc%jCTqyV^(n_RMcKi zO9c*2CNiw^j9OEpg=b=X&>YM(#+eh1GYt#frA+7TCrZ4FGt*6!D6UX=Ho>>1V#lbj zTQ|#8TTF7SC+8`6&liQ+YIn45#<$r}YK!7URzGrBoEN zB7Knm-jE;H8aiL@JMWb^Pg9TuU(l!|-xTXk60@o#wjq7W22xrpeo8Cf)xc^_ReCoJ zCJV15k5ByIp0m`KI1f^w4S8RtEDs;&&MvA+CPmrR)<=h=+34 zwdQF%7@9j%ilL6sKld9qtm@YK)%Zr3T!UOm*T))EJI;iHIQmY~zwrg$myV#$i_dep z#a9A!dLQT5M~KVF@pT{Jqshb(-4AQsFlqL13yq!#d20jmVaD$sv#_{-%gyToJQ#Xk zx!deN-;=M*$#vwDZ3Vqh;mgDY5FwO=C%)?G-iy89W z9DL;Zj&dB!doIUuo1NV)vv)Ijs3+M7KbhrVayfpr*%IC2a#Zc({Rg+3{!7%NTZ3^6 zkN|w~R~g!eu6g^cg5WVO>DP^(V|IvwqA_{(nuo4QzLqcOFnRV})AV_ zE{Nc4ryl0`QiqP2_QNn@3>u#EiN>fH#j(>E;0uFs5XCP`_(=|5Lx-YqlkI&IHmu(b zh+yTW%Ez7Z*zO6(BWI@?ZibL@73x`ogt3>4QRAzEWUT&0l)Fv2DVxKmR1>lU8cNX| z6atKkj)IY*5YuA>rY1?z9AySZYK*dPE7l`bTN`obM~qsrEd`bh{Rq=ek~^@4uC16n zpHqbe(_^PKaUe``KVwdtdumP}JL8m9Qy+n-xedYjEVW zDYeUl=u@90Oc>i@Bg_5z(y_}JQQ-fMG25I)EZcPV$lO$A-2V~NHMlstLQ>JeS&5|{ zwsxdlib^3aID)CPq#Tf#-+(9h@_?HNuR@2@{NG`Y<^%`Cjd>eEzHEn8?MRX%{8;o+VxEC}gEmG%4CfSVk zdu(jS)v(K_T=(;+yqS5+ zuwt@Es8I3S(jkIVv?gM)P25kB85md`m?{cLkG7e`EqVvTcL~PCBM}KzVFA;dz)k1j z{5v@(pk!dAGOJ!ei-zh{%&7Y7{et6SJR%S*Di6?5Z)H5sTk<=-qNZDU#d2E`FdnLD z;mz^9@E6JUGQ8;e7Ie>}mbtg&{9up3YLg1}7nN3;MyV^F%11==krs6%vsGz|di+-E z8asvyJ6Q7-rL?iW{3fbxgD$@WBS87!E^L`H5)`%QUr8(#HtRP`jiAd?5^LM=v~mi2 zOV)Gvn^gg1EXqUfB-j}mB{D3G!jv}8K_t&EzfgWRVVlm3lKILnayFUWB|N1i?Fqp1 zFl*kwm*z)*oH1h~!-HaBn~_D{#azSLZ7_>7vw|+#o&!k{_glrN-Y@r(9B;_hL!X}Rd4MWra(}cSrY!em-eY*pG2C-(U;|>Ah4S%kF zkMDEu$M7WS`p}aRT~zEqj))>rN7p)Y}$KQI4fOKcmi9#)Y+GQGVHz(xofW1xYX<9gu0pq&|-r3qFp0K3qN#2ClW zXu8#Yfz9GQgy)7e={>N}Xhb8r(fmOntqBh_0}B9A>i#8!Dn0t~ZrPd)JE|0S&PK}p zu^c}phJtNsO9k0z3ky3+a12eIa1zndW_V2Se9Eled;p%wHK;$C61V@5B6l<*Dn#nl zcN}n+5_V9H0loFSU9k0nU7_`|T@oDG8^&2mZQs&eCGhGUW7KMct}vGvr5ikJl^b3> zyvkY+sG&47IIGW!Cwg^n%xnd(h@83FnWG)!g1a~vYrjE40ZQ+2jZ1!iq9F77qN zv-Rb1q&5ZRrQsu5s2A0`o-=WoOt@dosx@I#n2sc#=2%gv|;4atL{Hn)pl>Y zx3cKJ_?4`#+ZJ>kgf>rr~WObpq0kA?u-aGUGn0IO|0T zlILelm9cU~^Aov_7K#-p20}7hhgKVzde9c#qbpD?LMGWAYwm5}VnzJ0hk7P&BgICH zvy$$z0(67q2XeoI9n8!))u5^LL?=jf7-yYB4&&b(()d7?>~}fG4Ua-DW3*227W(40 z8U3_QA_&w%2aVQv70?4kc^K0Rv*@(NL4E?AtV}uvSm#XV$m>Kpe%y4*{6O?Io?4U`lJ``RS7%QwFswUUX76{Wghf&EqXezcp zno1KYF=}xYHyOW#gYfyHRR3v|9Oc=;+?WAUc?zp*Ib)2EQ@8thSC~qR2`So(5FMwr^@o9hDw$yZo)&BX4JCg`XP5dz~OhLR-l{Fua z`W;4OU}uz(zZkws&oVKnkVGQs{#aRzVHCuE5y?uOVAbl|YuxqhPaEFWt#E)IUXBKC z<~vytLYm-Pvxi!Qi=9hs&z?|2`F3~`@ais)TZRh;*V3WgCG+WS2U$(?f(MF<;!GSt zyTP?GKEH>>gPf*zU=G<~qz`AlUE)^(zE-i5p;yX%t5rKYZ?GNwt?$MZ#myw88 zqWvGffd;Yy-b+a?p3pFR1Pq}LQEgDJA=_Z}1HO2pl;+2ZW+NKHCLviY_nX2;EsoB! zyD2%`$Mv&n2?dm!F5(pFMG=sk3dpK+jIcUD=y+f>xNeqdz4p*ob=;Fzn0=$JHh43O z+>^Ar4nWB`(Qd^E5e}4`WOi~>t3-<>5?4I|~RwSy@^ zHZ`3j;wNdoAFdD%EcppD(3a#lXRZ>bE<`4=Kuf2)0opsK`j{LuMIt12-?_qTK@J6) zMm9754y7DDWuOAdD`kRu`HB2jzaFVz;-dSdXCwJX$|v{VFf>Us2S+JA+y9yQ{m;PV zzkjkfva)ghZxb0S`G3jYdFIfPs=mBELUg`2|J0ZlbNfz;gq(Oi?+3mMLTBily4Z7a z-paM6`Wf2Qts+K`gdg#jk&Ma6E}BvMsT8MQn3Vpz~L7-jMOtq^ia0rn~fWY9xWK@^BX?my9o;Zvt6yk=97 zUgMs48+aq*XwhpSx5TPmLGaEh$|Xk)Vhb2|I;1*@xCrMSo#xkKYnjPr1N@zr;!-5! z-hYycn?9J8EMud=|5hE73M$k~GaM|-+NblICncDsu zqsHf1Gg-!>d+il0ipTsB{El<`$rpMqB#7BWZqO{H7HF9O z@9d1ZsSCv>JdLJC34xA{-6gwf@q>40Jl`RK&h~&QV;4O^9No6-FxW15(JZW+vJ$45 zV^@_R3RtMA_6GJ~(8bjR`SnVHdi(NU<|2Jd$7X*Gk8l4Gk^Ota^Djfv{~+jce#&fyR!$}pSW#|i$<2Cf02Uo^M2{^`34AqVUkmZsfJ1L4WQiuj5^HWxK-GN=gqT#hojx>H*IE@wvk#F`*I)i6-b6U6N?I@Ik_jv>N7qi^pum;&{R#L5q?D`|L%GDb z_?2A>+P0`nHhMl)vp-*d(*`SD8M(Djpd44ml&}kKAf}R2%Ax%Z*P!Tegf%r4UD*s&k|KE-S~`_n_v z*X)CbNjw%og=f+tZ#bqobk}>U7#ilSEXsl!#2*wXXnxb4Qrz3_{DR^9fta29Coa7(#kuas zpJ*;X4>6r2DO^GL-pv62F-0GkdP%h6yndh%QIPV=r69&g;=3a0<)1BF*}wxn1yiac zLhm%%%AHi69Ch5s5c!pMLk!j_XJiI$_M`CTB4cRWmNE5(edxj;yE9)t>ZXnBZF60v z`Jv^844On1nU5Q~LsSpYF19VJScu9*9Xyhq`5%WUk!W0e4o@(i@??;_TySf|JhMNe z4Y4&)H!H7oo8M4Bu2ImVKkYxzPOz#z!T*W|*-53$-(S%%|Bun|<8OKYZ*!CXR}A(g z@%$Q#{l{wNi}(3o@!=)cBaOm`;59Kmd(Qr=4LYQb(AWTtMS9Ve;mh%4C&14)rhDKX zFi|9C^HfkAT;j8f?iwhVccXi~+Y`;WSc7fQJIFhK_Eh%jq;$G;WwQn3XqUamI)obq z7KJvX73F~=;S>-;L@y~^vZEeBTBN_;YZ)P=X#&YP<^h1`Odr}6Bvwz+u1mxl8~>rx zNy#wo4EM31qh4M}pOB(DQO`KqrHi41tKIA5L5_L2RLLRVF$+~)v4U0=b14=JMT1eq zmKa+-=1sX#CCZ}2CgO#Cu{>*JFQ9~KV3W-_FPL@q1c;=Xh#4q!t0T%pQuo4m78aO! z4DAQ0(>PR*TiN~9I_tWSJfg1dO2*hFmsVlkGS)2Ei`*7&>6e4GI2O*j}vN>;M%Vz!isss%!vXo3fcJ1DyC5Z$JG|VmNIlt-@tzW zw9}gp6Yy5;opX4qapH^;!C&K;Uw;Ub4kpMkxhNxaX$} zb06{*?5PS0o1@}j$J*w{!}VQOwBrC6`}$NtwNkP8Cl;{jFdk1g9K3t{Q_8(#>_{Z9-_M(wKoZ#;IO`*)Og<$#&_N3V zmD(fT!ube~JtZ-EEmVo@BjCBBSu2Pblyl4gYZ((U;-~^4>gyePZy7_zE(~y+I=Nju z`@2;erUc~J(`WwW4z?Aln~04pQR3)>V1}2)`GeID*I8tnwp&=B#-U%t3c_%L*X)u~ zy+jTPrHC1(VZv)Bx+R?w0>}aC>D$>eUO}s$^1LehCZjg-6*zIQJfwe6%aP+&aYjq@ zBMI4tH3%72qP3#N6)X*SWG>>*E$db-{{VBj*?(@5C3NcDz>IF~FnHD*H49n9QeVd` z!fK9{TPSLMKV#L%mNWMb^xy#L5^DUF?(;8JUbW3|)ACoefdAu|{O|OPf3t7ojU1dT z|HI5nRI*Y)RzdNmbyKUO1xCF7+8n101!Gv)sg$=^CR+og_!Fg`E`6U=%j7q<%a!^w z{slzWJ$iqta}jts)8xHG=1KJF{W=LBa>|T1$J3@mmP59~6oeAc4RzF;ob<$8T`oi3Xa3=xpV}Fku~+)6dNkFYL> z4e;i$SZx*h1jfx>iKDl~Z*Y0%u6ZVz1R3{SFoGEOUWN`8=4zyh%_|_M?8`WUd>dDH zdYhZtYg7_#O5fwm(g)?WRIB1)Y_wnw!21jjJIa&8qTR&yAL}*%l&F`nXV>1>mlo=X z-sdb<>;r~|w^b@EzZ#mcw7}p z7UOEH@E|hAtekU1eaGoy5WE8kcn<5?gBZIiG($*Bn0Za>j3&oZ8gM!QbD_)1G@*26 zh&Bh$k_nEf3(lvyxD-AuD5tngUqr9V+?ArtRdW5^99$QI;%00kVH3z5~PqH zP?G5=f0Sr%i|C-OSISf~)}tKFlG@kW^}!o;Wryoc7emO67RXUMg<^{W(@DKrEupij znzhsTJbsYP(``Il$X?8c=^1{`I_o!i_E-SOoVn_BSNqlWjLauiCXnFc7ou_&1~P}Z z{XcFVjMzW*Yy-0}J0CKa?t?pkr;>Ks@5G#5Kzr*VfuFp7i!0Mu2D4ng?7*P@AWs|? zjwD=Pk?P?Ysy`w}1$6XUk+G1j(*x~@)8Vh&hxd}V(i-K_QVE|cssm<+!)Dvh_~fJD zR!I(Ua#^GsgPTEvupK$2sq$HhK7D(@tuQ(PeDbLT$KbT*Ah^#~@Ch^AZHs z-aHS${;&Hmfa5ic53gv2=!^iG;`p{Fm!C?&Vz=bNW?8h`<}e>H45AWzKH>fS8vS{& ze{9I@(any1_&kDkmO?7deEc|Fu}4Bl;lu>Cm~is{$RcMm+j$PH_lxd%7a*EVAE28e zP31QUWf%IwX5`(OK_37LZ>WRFZ&3yrz%r-f1G4hE>z>m(lR-x`srp%<)r$xui1Pu_ z*2LZ)sI@ak)rdMvFVeSaAD=hepZMM}41{~F5z&~&n7jt`nBo9dfmFSy z9z9!3Zt!fqtR1+%WPlEX=fpK^UlU-9!E@^uc>BUPRCKKA03d=*>z|;-r~&i^-y@z- z=l3drgLuIhH)U(^p$H}+?X-JBmxQ)c3XEFg_belf)}VyuxAiR1$D$x04QMXP3yBEBj3}qKl?x1%LxpM1 z(?>L*ObWPgkSu)$ZX!NElN1&kY?DWV8*H2X{o0;I?^K?Wa*xwT38xHKOpFs&?3@*K zWbC$?KNt0UxA=2g_WSh2Sy9&TT9KGXUn3I9)|>-rwU*B&+c>Zb_`n~aO9i@`t>r|O z%B1dFcVORzcATx-2qc+^l}*5k0q750NF7@TZ9V`4>~@6A#!S=VfInut9^-Te%??VM zy4@SVy^MS4RfQt@NC{$VF1McZ2s>xiPf#;5LdOS;P0&ca_4Mc?-AA4qjw9W;L(aps z!yNz@tq##B2mV*mynz#&b~tjWHCc-RLYsurWkT8v?TuCNsX@-tvt}~bLo3tBk>Ge-ZGUKZ z)aR4$oPoz zFeodJ#>C@|whN@mbJ&m*G&L_O;mnPXTiDA%@~y+-s(YEAwNaoS5(S4RrS;%=M)z*g zm7$&cTO`6D)q$k2PANe;Awi2G&4O*%^%i*@&tg=lg&~M?JaN|3^YUU%S>r|cd)b!| zrW=Cn0`L786{au@0Y~9pN4w>9Y7giKYyj z$<(R?8=h?FUoE8>l%3s zSWFH*f;XoLiK`-_ak0rW!@V(@i4tc>s6zxPQxc|C@vQpSg{b+`mbv>FKyjEzHSD_7oJbF`P(f@Z54Yz(QpS z@`8|}?JJXu%>|pL9Z&~s&0^QEUZpveph(f&*K#2%#L)aDjoeHo#*@$Ilhs>W-rt%d zlR_;>tua*DrkQP-qN&4H6Y426MDukC{KJH5awhU3NffO#yM?fm?M4R6q84R;+u^bV zIa=&t3Un;2JPl@XUGGj`P+|`3Y(%014vpzUc11S~hOH+3#=T~a0FTZXFeCU9o#?oX z37m4{_>pj)wAt<-zpyAjqYYJ0a7a7ty$BN49u^*-r9=V5`7f^{X zuY+T=6ix@K^=~-6dyyG19K^aVVc)WLo8r2$T>&uKU&89uICO9}oiXNdbZQT{K?B0(6`?VM7JCsEe zLne_EBFdSIxQP?7bCB4j!~MqyO{>ogB!?rUAM$&%nv1yn>Z%(Y0-GhmT>HhOg~m-H8?P2s&Ui1w|2UO0RqxqWyCH-Q%nKI3N_V8tn1wCCz7cVgt%3X(U(`+> zEbx$#iR+4qtMBdQ;R9$J0KvM-`e=Ecte2Y*WDf?q)&qwFK{Jo*SR+xh{t+|n_?F+| zil9w?KaQQQdb0AOo`JA>S;Q(CxEnY-s)=-~GNXuey2PnAtcrnpGH3LipYQue2dBLH zCMk^0;8QPrs->Y-c|<-j+bW@L1F}mSbmiYpMsl&8$f7OD8D%Ma~weXtDt1HZ0B>JEltqo(L82Wv|nP6vnLFg6?SfnTns3`In8Td$Gpt_nl3 zS*;JzV27s|4YPByG8$q`pqFJ2bZ*zm;cX%GF!ljn2vxq6cdK^=7733poIOGl@hDdl z=)CmTo+7(oViU{699Ek$FJ*ApnxMTKiJBMq2+RrXrDqI!r;PRjs2I4niPI&xJ6L(` zc8Sq9I|>Nf5J*{!S?_$yqrK zau})szfyjdf+OoiI@vq;Rs_#ggEB9ROT)qF_gJa0$9^#HQ9xq209Wl5F(O$g`%$bGVkH}hOv|Vu zH=@wUH~wPDH0QJr_g( ze1m!jo#`pK@IoR0ZjmBfCT6Rz0_PFK5~&8^ZNakP37b8cu3*fY61A5=qbul}g~+82 z=T)H}+P||!2yuvx-B+f7{YQP{--GvWej5Lgwnu(Fo3V{hcqE`S;toi1f7TWjT3HJG z0x#Q!L9xnhnEzQjPg$wNmJWs%zmZ~Dlb!Ja=OdKK&~qB$bPi$ijvu8iJhTJBp4CPT z#M<*paCGUA{rf4)BiqEr`{k$&|64?E31NP(Y^>5vYFI{CJG4e%eqPS*opOM!^tO@j z9WazHfv=pe1n?ekb#U&sUw25iAcj;(u$^EIroWzi95971gRcsxHcUQPA22$R=|DMR z07KCZEVWs4C?+iaZd8!b`m8Pd63e=XdY?#h(GE}%n6yh9m>MkSCYq~` zf2f%66Z03R$#)Dm9BYxjP!CrH?2^(nl*?#|n(|m2P_*;_%ab^Fu`n+wO_xZDdcWW? z7nQ{lzRu;Wd!=l=!Mx7-6sJ*tuGA_s_opVoP62bsPJv0fb9=eyf)*_k)`CoX-bA&O zDzLZRdgcD%^67wDz=dWgEB#-2y6utDkUN?Io4sZ6#qu&VnqlbEqiA2+a0S#cxN zFn)ktLpP0C&!!k_TsH%oV3J-`%o(Scc9z$s9%+N-oK3Q#O}NAS?J6?0FKQ0o$h(>o zvMI1O_vz;!x}Y-kc8lLYdCa??~FdiA#JG-~u&qruP+hb|lssxU7->~hEFc>i$PGr8xsqCmq1gg zzvGpDh@e}+TPc3%zOP3=yO6b*km!b-kUBez?efRD$#C~z!ol!nzN2k0G=v$T_H%>f zrunH#^`KPEqhuV60BLmk(dhJ`x!;f>J;M-rM)me;bf!6YZxKo^>Ih9m%lcQot#KWj zeFnyDwPMQU%ila#d8%cg#3FvImK~Sp^vzYgrX@l*L0UOF&(BBSG{ZmItf_Qely4nY zdURv?C+j2Afi--Hw}*0Mrxnejt?uMzBfj8Cjin!ZrQsl95}{_<>WKDR?z+}hxA;}< z+k$24ED<*TmJem`)^N*OlInrjEX@A=m&&D~apacwD+}R6{ZkqJ{QF%D|ONy5KanAc|*B+!x>TvwE|*+1RNS) z5-2Fp?E$|yO|IZQ_R_g}Yg!Fy94=?1LTQb;Qm|NguZNhqb+Zj3>GXQ3==zgbR-QOK zyG;#U0z9hFupi!@Om~M9=`Qa~cW64VC#lKbD)J)2k`>!4c2QBQ%bsyjyUKT;6yH0; z94NIFZiG;Ma&{#sJ1cgnC_Bq`EhsyycCjftOLyUw2ot#}X=F#ruQ1ujz7OXtD`E>hL=<))*CQPs=kNLnvSQq~U9|m-ISMiu)lHCa`3*e(hgLqzvqEI!a1F9g!Hn{I zys*}Dt#H#W8J1PwmTA3K(gvxz^L7`@=^(y^Bbj%>j8+{X0rK084i@b04;Iev*tp$K z%$%aISQ}E)#~1oRw3C3HDQ5BAU{C~Z?;m^*Z@~@&mX!ReyH6$#h;!uoVaBuMf|k4l ziik0xrNEScUFN){P@U<{0PU`R!3%>RznoVoK2l>%QqK)N z0uOR2dALXzm&;2vwa)ETfG`Zp_0GgFpf*)`T&&+DT8|cS4rRMHqqEC&x>||z!Nh2fY_Dr#e25VKTGaYAESDD#J&>8gO%bznW zBT#8Z1f85X@y~nsTC^HF&g9p=-OG=G{`{ z$1u;iNy}Eu)z9{|o`vcOR1}SEKF*T2BtZ9Oajdlxj-7mYoF5-f4r?rOZDnpYuV-%B zr_gb2`J*>Z(-Mx~3e~j9%S&<%XWltf5ng#~FS1N@*k3@zU9E?2K;zP$8UM(rlDXHN zBhl_xvsn+^6n1g3w#3S4D;;ZC+owG0jURG}p%RcOpC`GPF;3s?J&?&rm7fOPjjeUe zl}FLSJ-R&%iH$ULP%fO`MIzJb>}=^>=~qf@NY&Y{(j491EuvBxFR;{`)1`~B^G^)0 zHlboCN0N*Z&E1G%&g~@2WO)rU5D4P<&UaWg1$(!oUANzS+Y2U=vn3oJkj0jvU_jujdm#tW~8d#A9tuW1iodlxA0tZ+5w?NtcpZT6ko8V!e17A#PpUm-P0 zuXCcQ+-ZYaj+&={4&}OtU2HfOEU_HGLHQ20>ja35t5Qr*^WAf!FAm8IIZ&{-W+4K4 zJ28ix;eI2Zh_j5hIN5J>IIPtc#L_|3dn?~u46kIE*~9*ho&+TAO~^mFkI0$l7tAp~ z>>Y5kjql!G_Ot|Yi&z~3k!jlbGAZb08ji-q4-N`++@IrI zEHDYZ^vVt5TqNS%&DRn|%{8d)nYuLUo~VtquvwVY&Rk?35U$kavFWHFkIx+sBmYu9!}>7x>4OfCdVf@vT$4-YWNe2p&QQVK1*KQ z-9po5U!*qvwGy2(5Wohl;lR>DX?H{A?cmk`~f-FekbouNwxQQDNit2)C{ z%vPLD;gMXH9Ih5$c_z-l+go=p$ObE2At-pNj!p+WI`OyMPAt!6i|pQ^u6k?mV4mgzo9-$9(TC z;igULrawBB5IfMa7}>B2=7y)x9)Dk3zv~FO^1L0z3eEgp&R~y}ygo%5RQFSn+~I?7 z*5F?h68nbtVpN$|5Q3!)g2Nqd6>6H7EG2p05mtpR>#y7RGNslb45j071kDJ9uOBh)pA=pLd@37=q?Q2_5>PG zmb@eM6Y^^wje=_v1W#qW=wJtpo(+@>DW^HNOzsP{{`tIC4=M?u@z%$dARg_6+C;wB zSFFIy4-+I8OO4I;crK*AfgPmy0gqBo2dYgvheOc;SVFKj9eRO%eSik$oNpZ+_KnnT zO3-LsncpX`Zz!pRk2Cb!Z!f|kcISmU)ET5l2>*2`swk=anHt7G7q^&@@}rW|0boS= z;%k`xZBbBkm4PXJqdZ1CEQr02q{bK-%gxMVNY^aE8lj-vUzS=PNKP~dD;+nhwpR2= z?NtFhH|i$wHJ0*gr-0k&h>h}i?A z89g+v{&ZtkxyV~X%DjqND1()rKP-!l*M+gCr8Mjy9`b^De`Vf7I?pgQ$7l6fcV@qy z7y+g&mIb!@dEIecW@KWnsr$ccxJ)DWZ)82N@6Y7^P87fc5u550b<_UUb5slSgEm_v zT62yPe^v5@`?1yU&GUA@8M^*$a;NrW*t(pwO%J?gt*rU6G4VzTxvF&AqWpgJS-juN zv)3i-C;hnw!l41{w$zFUBlY_S9LYKC8-EzACxn$r%26grK~AzuQRwDLs!b|HlDf%Z zhZAYsVgBXyz{14O*!7pAYnH)ri|4Y9+m!5HWz01zOeKNlII|!*mO&S@B!)x->VsPB zr=P*0z@Hr;Xq!WHBSW?*+5HUM!F$iZmqt=gJ59aBe_F&8pR5BwTIZ0CIehnm5xw3m=LS=TyO}o zAr~Zft)r&uxvCCHdVFp8f|2B1ubJiRI5m;wmG1^nRPyb?IutIYJ~;yVI)8_dJ=ev& z%wd!k;7-_e{hoqN1mVPJAMLa)Iyk@fhtBl8v)d#+|IKhqoWwC^F1W1HKyzLm(jMXp zCOm;`xzg~CbaU`&swiumKA;Z|Y5T;P1`O;+(Jm1414GmB?5hCgVST5glA^BRx#1|c zPU{RCSZ?`%va6uXWtXGnI!r%YHl4Ns$&jfE9J;m>EKRyKWC$3IVR%^u;tc`k?v=R; zetvy{T-?cD9{GmZpO0~huJmj~vQOx#+iw5{kkZ zLK+f?62YOuaf?!lYJlWtrz61h_3MFbwdLc3prwS|jR?>K)2w2j6vI$L=n3H;0YRE{ zCWs(Nyu?zl1H(jBAsOFJ3gd_ z98x^{+E{C0nxvQ9u+ZB`?!KbzzTqI%CV7uAV^anuL&Y9dd|=r{sM*Ja%hjT%+Bv+a z#D-z4;1gc-U_Mg=s619hyEutazTwH&4K4a;w^1kAi4*l{+ndsOE~0SL;$E27zP)I@ zRm{Yb#Ip}gOL`a4?q*@p;H)dsoG;CcJwE1Lc@7WRC~l8B0Fr&OHU{*ku$IpniJai*`LDz^JFdb+E?lWZuvh`U7- zMxvnIr%ey44HxfWU?Q?iQYQgp5v^aU_k&vdEl8>G2!U1!k^ZZWMzRHME(+u4EQIHd6qrq{CLm&L#LQtX%@xchzKsSf{$r~ZBV^1p`x#!mk| z53rIOlkTHOz)=uijgcq6ViQ6%XGdVSGYwS4ON=2zD6kchHmA&=FGYd?(h-tT#CiV4 z3%$b|K)GGFmx*~*)sd}ph5rpsc3hXQuPlfK_6&=a+0uM@mLVu33I%{`CW)0IwAsyA z1Y4mSq+4ak0e>I63WO^4HtV6=NsU|}zDU#Kd{S-9Q7Z#tuG2^YA6R!8k*0}hH$$!A zL*XYm0N=cQ8={-X$Q_X#04p5h(yWUvHfA7~>CP8z7k%bJwq#Olg8pzdvMyxqR~KN_8g7^z zF|H{^>p>q=PG=Em=c($Vd{m~E%!&?B$dlr$`@m+DrWmlxi+Jd(9#O=r-JwWB5l9U^ zTy80C)WQo9BaL4*uUr%)FGnDkLK8#FsAZ_*!8=~gK7$UYl<}vu{{Q3at-|8amUi7h zfCP7UcXubaySuwTA?L3dSdIlQ2!MVRDIh&QMAp4Ig4&A?w&Itd}g8JWbg8woSMIhoi+A27hOcqF7mDlY30zHX1t`tY z2R!2a(>4p9TSOC*@`yH|8xkJ1q4m=#`Z}@2hZvFVTdpjRWY|XY zz#P)zi_s@}A?%fo1TOGYZ*JjBy~Ay`75ywQUcr}2v%G|~i@$D=#teTSZVqG7-CVJm z;;z7~JC3KYO6;R8k`oHw%W%1ZR4et486#I0oMv7xilwUA*=77|&WFgmt3LY?9j*Vm zUr7Fs=#cu~+vxw-Dr&aMe@F019ZvYd;PA`JF!^rwZYY;zL~6-2G$9zO5NCxZ7>H7H?j(Rcr3ny? zC3z!oiP1UC^?Th%aBI?ia{Z|!&Op_9_h+va`|F*MBJiNfPe4GztICUdtccc3&Rl&o1(YPA)l?D&^9cLIZHxvGE&NpUypzLzvkDxL+y^i)v zGo+3Idl!@TMdSdhaDkq3gw*QPp)$hXbCXf$=Y780l3`X8Wmq&ONR;LagYVGg@ffIN zR2iSk=}1F_f6K}&&Ja)Uj2O2%aw;M#@v!Qf^&GKak&eJq-2f3cBni6hu8LGu&U#SIZwoceZ~9X0 z=zn(^mPc~IO!xM8#dPQnDZ>A8QyKnis`=;WPSM$nOxnm<+{)I~%=v%sf~-n^z~%Vo zmx{t?*E)XbEUNbJRALzD5)?7)K=sJ&DVBmnOA1d)PdJ}Q0s{jHcO=3&tM`DPU8Xl& zer0!d_waXr(iEbO)JGLVRiP-6n@Dq{$FgjV7nUHo;b5_BoDIy919E)%bK=SrMf_#| z#EB^&Fi6Uk*|oh`I@-XI@jz*DeNkF`ksR2h!(%y03hY|Hi>q&C5UEdc+`QZ_3!a`> zZZjTD2~reSW@N=WjFm7a7r=mPkB9;-J(n{s5Il+)L(!zV8F>5Q**Hjq??GuKNx-TV z2~SL3EdCkOj_)T~_u=0mwy5*icR>#;!W-)g63Vg~qbb<`oxebdrjG6vnVZn8wxm&v z5)iY99(3D{3LNlgPEX3;uWYpI6^|DzGjD|ReMgsrWwCr^!}V4Gh6~2j$krwy1;ir zN?A>t2jquy{)tY5%SbYbkK^L`AI996?tS>jC8PSUDd?YY!G9G!IGY)n{=L#c_J2{y z+5S^FXRG{|e7W3WrbHD{Me?LsjA%A5Ohzqgf+NKSZzwJK%GHUV@fTZpll-JM0U~Ht z^x1qbmSyWAB}q~+tTVmcWqOL=d%Byo*YEpZey{1lw{Jua!{o59OFV0j z{R}@gQtWA6H}MU;7FsnVVzAI+jLv?IB@~1$SFub;L@saU(2A{B@uS+j2!BmnryZz? z$N6Iniv?4^jw0=>;TLsJKb!~CE#w|@KX>^hg15hY10A@wIK;wML%;_9rI=Ym>LrYP zOO#WzFj1kqNGVKSU^9cvxh(`+819AwS@0ZSxMhsY3Cz`3)J!>7Zg&QPP;nHd0Ku&qc32a3~y9GfxK)Mqu^Ob+!F{tQsVP4)2d%VAkI3E(zGX~8Xxf< zADBM?*>mt!2>btFZe_3Mr_ev*@cdsB9`-*H-UsPW*38|^_P=*ah>D)dM z;y`c0fM8qXu%vyu|FjRgfNfqNkmu&Jk%ClsS^_LO>rJ>WjG6cgdLQGFiom*rFgeKW z;V{+h?e6lSxR^Hh#Bn7#f{zc6FNdFrb7|F9yBh!ug|uuVGx|1A5IhcbQwJA;rW>!w zkymJH437>?OJSd9K9HiAypw0Y$B-hsc8)VBU47}tcSbfTk48S3BI&I^kw}E%o*7!+ zIuuvExDJQQ{zy-FWj-3Ry`iut^rSOS;~-b=M7o{I4V;7?>$bYkusMb)vhnT2g(8t8 zV4bjy4cw4--C;iP=$1c2ZQbLFpzfwbAO$vztIYZ~qJ|IlDJbA_#B}f-z}?ytby--| z?hi2nD2ph@Rp=OAy|tHd0r_&C7pJ$IsrKXMOb>-@mtG3ss|?dGf38rk`$sc5Z!oAF zv{A~3?~EPC+7bCMnkGdt;gVf8n3E^~CRA^azLtt?IBZ0r4S0kRblhqYm831`(|P`C zs(p^x>g2}HmXtHVHpeOqF`8~36gpHHsjcUT0Sg#Qb5+dr^Y|tnB_3V|bh2hj>1Zdb z+@jWGQRzz861eLP=)2{q-yRc)N_dQ(3JJ}PBF*t?t3Xyz)-7a-N&PvZ?kn)ag~x>^ zquJRLMxR^VT%0^=zcDBBjAnb*V6D&wRa01$bFQ@YXVUb}(cj9;me6wFuOHzA`>*-x z|MkNEkB;|07ge`5Gyr!I`7M{;+stzxB#*ZblFAY#jyd01xk>&kZ0sD67{^n@P9M_T zB%LCuEv6kJgsAN``%==>R7~HU*8~X@Bcd(+D`o3(bMB=_$4kh{qNus9IKGS7;N|r8 zy~y)@;=@4P{7yZW5OBBk^69S3NKk)&gz@#Mn6USH-y_3%Zh=LPet7{#uGz-AxJ2E` zy167UL%XoNk-b@6<=Fa+PtI;};W=Zir2N9|YOqY?Q|fX)bZvzQY|Ym^j8v$+-HcC@ z0_6VB0FpsBFuJ}(tYNHSY*`@Ipb?N14r4GGP68VRP6{fMc*rrT-)NL2%djnD*9vah z5I`~53^#2I;23nl${D%x0b(-gPG6A#FLa%)BzmL4Q@rDM(C;iLl7U|x+-ptcgsM^7W@sjUX+m{`NH8`-fIC3tEL z>sN(jsjw#RJuyX52;;?MJaS=5V8wvd9HZb{22W9C$}u>n%9r^gK-g`hgC8c6XDM3O z3Xij6U;M`L@$H&+GOV#(lkcpk`oIW73N$8>?u zNL*sl1f)s9g2!qXH8L4-8Y;?r9UR_l%9^0d1#BTjZ zb;Gix-4WB*2@$gcw3eYjA4J+FN!0fA=z%DW$D-&_1VrfxpFVf<#CFH~u`k^Fpv^Um z@jt==Ct@TxI`c{gda>u5(%~9OW|`wI!4+FpL*<$0&57a*5eJV374-PSBmn?P43(0o zq=@ksHd`{6)lODB$^))Dw}}n6Lnh1x7^9pPs^;c$zf7By^9yvA+BPGB9Q@!Aa`WHB zR(edGDXA1|oSE3|xCj#^>NO`8n^kGjE;AhqB4J&{{!Pn!ztpC`RTOESfOY4fMMn7k z#HRYe8Ky@-2e6S(ot#WKUP{V}{4=6bxa?_g^?jMDC`ZjUm$CAr1ka&7+~iP*NUp6{ zp7pF#@`2tE;ACa}<(303jxKNzpmF5I%0a2-fEl-GVct1cO(H<4;?e_x^VX`_+jX*a z@b*zR-|#)|;GWj_sq|~AWD<+DBXnP!qA@im&)B?VVlX&KUrfPh9)sJGLfL7sToXIi zldd_u;0wZY59VE-f{Mt8@pqrH}$1GhL&Vc7m`z;i$*fcte)Z^cUJ|O*=w}=jFPKGY~z4Ok-`#pdio( zivYp4ziCo8YpUGJ`_aJh$7oERu;Ic~2`Po|P@;=k>Lb+x_N=8qSQ9;IGRt#nCXN?s zZ^lL})(`xV2JtLFd^sVJWhXMPSmQk(UQ=uFJe05X)c3WV=%ESMItxC&g=Io~#uP-H zKeGXvZK+ui8O)@(9;3Q!cBNE8d=M`$By9Mnqz)6PO@(mX;fMBQ2O@8i0-4Fi7~T~_ zWl+==2R0Ki5)u<>gD~!EVI!VPC6yirVZuQ=qxL0Ks)QWj*hCw-Tk-PiWHMYkB+Btb5ysw=SzK!wg2ZvGzErw?`h8L=QLnNE5 zgwcOabf43kPUTP`o1LSml^R*hVn#WNX-j8Hl&4skUnj&p34g#Ce+eZqY83n^WlFcT zU*;_r$xtywPopO#rRmp$h(sIz!V$~~C-yRsWKG6+U{h^!9HhFxQm6~-hvTA9q2h4MPn;Ps*NR7NV8$)s z-jUoE>@6GTEjwdp)4$V(%$=cPg-$VVJ|Hl)PIcO(szu(AXJcBzWU1%|QEt(knjUGE z_>IPr5v{@>nC^>0I~%O6n3~@!{w9aphlH?W0Kk>--qqj^oKpWmYqkG4rP^vCQo$PDW3jq7uNcz#Z;#iPq+~LFu%-&EQyR zY99*94&_X0;|+&T=+tYg2A$7IWl=h*^`AR)YBzuIP?lhH`2rE!Hr$_1AfH6C?ZzpP zlfdiI2;(_grPelxwuo&n!GDB*-|B7mX%;XHn_6j{YYT>dOZQb?Tl1eto~t+Di3q(?N$r4PzXJz9 z7o}~!SuB1PZ$|d$e60r66g%0Su)UgO>rvMGBs$m{R63|k{_dy#%eDG`?so>Q*Mv^OgiZ`NDlm9i`CG zSN7@?R|EshhA?oT-vb`oFk0F$0zHhZ%3Q!Pv&BiNFKut+3^9GT7K^@8)OmwrV(Ms1 z_OM~#fN(e`ZdX#Fn{o%fg%~HBE2kS!^{UlK`>V$9De~O&p@6>Ck(>(v_eAN>b;eq0|Z4 znsjH-fPq!jz_0heO^3o2ULvVrK7A@A{kL%v+CSbd;#T&i|D7diJe#0t;J?c@tgdEv z*%C>d3c(W7nf~aPE?#3m(}|(NNs#~w>1OU6!j)mwYu4S?%^6-T1hWy+ z^L0`;2kg=LPd+JpazNOr z|CFQV^leBCeOvKb4t-nW`ZKzq>NP34p!zi`x}e(ihfRmZH7t6s>NPETulhA5`gA!! ze<)Y&Rcy#W?Nx4QTkTb3NKoxnW~etvF&>7*e;J}4?wTlN-w>(!1hD{tC9EWX+0WWLAb#ICxN1|{f~24`6`X6hUYB(h)3%hwS3icR5RT{bKbZA-?sP$vqH!0cHl zzVuahq{|C$d9-NgAQ%e&3tQDeh}uf+F3z1*8wOP3H~7kHFL;|PC<=$U(I4yST`50) z(kO+KCL?i898e|Fq1K|PjY4OE{(vLyXUlF49)M90n%*!6Fz&g?3QQj;QItaT1bajF zOdPN!j$Hc%DESEQzzI;tjqrn;05bcydWe_;6j-q_0N{j3X&?*8oP z?kUcO%#ID9vTxZbw~zJpp58}vXC0up!~0a$=W*>G@M~8Dytkhh z!Y>jL_BF<{*Fx>P;qqJ9BMsGjAdD#2u4as05lo60hdUkzRCL*?=(?PYy_zEDWCNTx z(b}SiCPkO$Il(mhML@L}!he_snM^|boGW! zx4B?F4H%n}dG6_xM$@LjiG~aGV4jqV+XCO1eK8ND(|COb5n3Db>hl&Ct5cjRvJD%} z_ESd&Qm>+23S|&OtnE4dOc0DuJU^cnetI|he9pzR8J#Eo54v2~6Z(Eq-*WCQYJU5v zhl!@FwK?C0@0Y^0Ch@;gHcT+pQyW;9C611*sh!tTC^+fWWIc$T=E$#qEAMq{?99io zCGVsz)e*Dwj!9$a`%)88x8*~852sm{C~?o@x4_)n?z)GmW+IgM=00J;SdS>Jy^4~) zsacGoE?mXIQn;}$$E&rHJeIJhSnpi8UGR7I*)i$Ngx|cW!g~UVOFNv?4QMUn!!f|5!|}Jg%~Yqhh|^ zEbS;dlKoTA5(C5;8~t#V7Tds@rv>bIxp$}g=2|_ktU)#?Nh{X)S3~taOyJAcHT#Z< zHKim=Ng2jNGTaGUkwlTa5~x9Wjv|*7i@AQ0Ls%-U1ZclB9{cpmh38S+!npB*2tJ=!}^u7D>YIm%tpQ3+FpT$8tAN~ zx~_X~BUQSgDkVX@L30O&X7aqI9P5W0bK{C-sSFx~R)!8jqbC1IBSC1fmiSU;i*gPJ z3k>)v=}=E;8JFmmOIfihc@5<|y6aQYYoTJDb$}o|Wd7<%P2iQSIM)RJY;NOVYG!D*0ZbH( z?Rc`GMoet8LFW!M$M{eC)JUHpvta=#IeyKPQ@U!pOz(x+?dh)hSOO}I`CLIE3-Qy&zZkS{nma>L&T|}CoAM&36Wv~AXllti!Ux7%O-I8y$oym$ofIQ@&v@cjs=#DjVjeP%N z`xQgr{xkS%G6W*pZUFdeAOTh1GzwpADtx{dN`KoW?x5QsR{9b$Yz$G=7ZYtIRobwc zndZO~+3-ZKE2&^$ogQM21QkK$gvq$)lX2jom9uYEMOlHhwAdmX`iD@Z1i30(6#ilf zp54rIgX=;hnk7IZbvAN;n9ta=>3R6N0GSOzM7N<9)qyh;b}{K^J|9Pn$!&m?rPG!r zKU<7DKN}D;m%;RsRa9XH6{amBC&2AaxUvL~p;QZ=zrIHJ%i)u|SNi>|N5U}gsg^ro z^yB<17!G0a$m;$b2d~7*@yW%#0gi!CHwwsFY^E)1`0EmlPMv%^ywM@j5-o~cgq+RD zOw~p-MuT{4it*%*c||@84yh_hwTNYw79L|Rr~)vK#iU-jnLepC~o|w8KUom47Mp4mFy)(K(8y`k!y28 z-o^9UYeAVmk~!jd3-ORLI~eWmO@4Tkf?PGeUyORfKxuxDUFAS~BH#&y>$*K(U8t#W zK7Z#K!v|v&_u$pi>n2J#JW~2*Tn%fWOo)L+{fOsOHEpV7=bv1v6^oN#}# zL4?kW|8yLzkOm6C2bt#Vn#WuWC%{yKFbZH$J8rRp@$764|-E0Ik{-u=aGm zHC!z;Y}k#z8r}h0&Tvvz9XqTI%+j`6VqBp-m)*YDt@He8Fv(#$7#?pq!)iNijF$Gx^B)T$@q5z&+lsDC`qYgRFp z{N34^vOlMl-zed2H}3v^SV*A!vNUsfHVyCO^P7}?1xr>pNy^CBEdb|Pj|!rsVCEW@ z?a*`Coh32mWtKFOY(?hAbp`f0~y$EU*(Bw-!8ps&Q6UpXV)tCA+ ziEE5a5DS*N6}y6x4~k$s-^gov?`Zse^d}@GBGy-4Wdmv>P15L>ArWDj_%b~4Z4~V& zQH#M7USUtN41_{_VVihMj;J2WCW7#zfih`Jc9h9rC3xm*PPiF@YEieuT#?;vrjcZ9 zMCuwczERz9@3cdh+qk2#q)`f=4c}5#Qu)#ZA#cwP3W4di?o*4Eg0+jRp)i;}>wFGmSHz0e+!N#mA zDf8d^aYy*ulQ@YK2HyZ;+cq~pU?2ylkQpuLShsJ! zvTXgHpivIcB3o7M+37CauW%RDY1GOo5>17XO%>n0S!10TuPM=8c7hC!StT8E=J<8h zaj7EFvD4r-NPk0ca)euO_h-8t_A5XhJ73REv63SKTVecTc}wmXHYJ_?hy4LtS;a&N z>b0F)nD(Q`Tf0r!UCYx?BD;leyQ!I5nFGK0{@~7tOA<~ZC}ZRc>bbEEyCZEu_16E7#_dqdwt(6%gE zP+1vFrxz6{;1)=H3z!)Pd9XR;^VfedLv0{QqFxWRlpT4TeJdW5W)y>8rd0?2_}YpR zKIDNML+(C{vo4|Y`so+}nFQ7XaprzkQn(=Idb24nG|ro?W^m?ja-X;Ta8K1o#5eqF z#Q$>^{SO%_%YWj&SpD=b%*uN-g)9p;QLvk15C*#t)7}!#iWpcWI~|O+2_`Su=O}rW zQ&Zvk%FT-&M%V2N{bnQ~?L(dEQ^LbjUsgjY$t0KGMt-cn7=G3KntC~2JEZ-z=Cl}8 z$_H2r(%=U~j->MS>|L|RIsjfr_bCYkZ=XU&YWe*3uX$so2vqnQ_)0t{uF?*j(ns)O z1wCJf_9HL_HpX}2FwMAj#2_D7xF&YK2JP!F?Bspoe1N7h+8I&;1LuKM{)r-aVGni~ zr4*wN74b=s1kMZVj*E9?T|q#dVwz2NX7*$yJ@d_8l|URX&;$ z;hE#W7wAV;KwqdATzD8UF-c6SXjaH@pJALa)6@=J(1=)9ASQMe>NQjj<~2o7tCKc} z7oZI4l#Bon7Lo|`p^^xIU`Yf9ev(i)N`ppb#C*V2K&5q}K0pakpO{oJpZ>OOVf1T! zaZ#V?9ll}FSAPjHT_J6dn5n3)ag-p_z&G0n?2dTXI1#DD_bLL! zJa?$n5mg}(88Q|O73dES$Vjj0u{(_BtfupE%Z|kHGvTwOtVzg|5OUtY@y4F4xvscn zup`Z0t0=3OKhgb=29FjAEu}iVFc==bOs!$#G!3K*d2z6i5AcVThgv^=X6--k(N$c< z8ENhkkmk%TGPr5VSj)6(!&Uhbe~Gz6PR>#uS!gRfV(Xjq9+@Dcr_Tp^!`mboTa$FZ z?IhTd!f4@MJPFDxOX#_WWNSJMQMj)7ef1;}VuF}y~gBdsud6N4TVghAO2m}7{%qBFnm>~)-o{K-+q1G+sY0J?<6jqAU$?VfC zxBynCWN|EF+tvvjPVwZ~eh`uCYbmkZKyJW-6HdA+Bm(;x*R5t_iZomNH^D=}5@kLK z6{jI(>>vEL6{ku!j-t(8O|ENOeKL^(jXDaI2thq^^Lg(np%|a#Lv*#XPH(k4c;g#J zkMG|XOF0XF{sE>G-EQ&|s9fyD-M@k##bsga)lE^D(aDvPP?%uPt0thKRM=Sh*n4s=zEw+q|lGD{+63kpT^*4TMzd7tT&m@kh`!@L{^U8 zf^KnfP*=ia!(~Fd<=>et;(HAyRLcGsrj!yn#gBl#jJUnGvahJMBcxw?6eZcgrZF>= ztPF{%$A3C3iFx{|b9mLE0Qbq80t#u zNh70g%aZw19bsqrw9a$-F9H^|HaHOVAAn@P@Wu+~EfE~Gnwlm9hsk^))R9eRGsHA8 zMxkTLM#0*JA0!T)e=w>^f`xCnAQdPZVt6>E4O6ts*Cd}cSf*NAASL`X3s*YG;D#tr zjuz#UN;oU6r>D`FGDb`W2}bgAjV`9v$yZE~a0Tf(FXH(ql}lG-4V!BH-{a+wnudT+ zA?FO4$YVQxNgk5>6!3eYJ|_HcE!DiOf10M^jafu*f)D1_Q>*8GC2L z^xNO9JTzxLdZZ7$m)OU4`2YWAxcEOx`F~xBaWryvG5c?w@A-ek!~cuJ@!>SCGOvjA zhLA)dH(2e=Oru#WJYF3LfDLn2iwDE&g6kIJ z@Rp9(2_)>c6rC4++oVizsO7&4>gVLO?t8bODLw_KhH}1dV*mnco+2psJa-S1gr zdD6Z5nG*O8GN-$CwZ3z)>x^p0Bu{z8h_qa^pX%=DV27qQj(lF%ItrA`pe^n-PAm7% zg_Oz;#_~#}{JCAL1X1H~4Dgz|H9*bb(x3O^YF<+UV{Du_HcKro%Re~RCf+Y)%{eZm zk^Sk7p)Ly?O!un-Qxjp` z6l6@}&T7r{(1LbKsy5})KJIsjwaQ?C9_V5a0h&B0ajKLt4KjFcLC^&85T}_x{%jOBS zqu9!t#{T)2{>I;~AO@cY{WpMr_;`H2)QG(imgb7#>)GLubu)XKoqkb{{yKL;V(MR| z9ZW>{!D-3aL+BW0B=1>9`9W^;}IJ=51c=8(*tg!7bf+xr*yan1<;v94bCbFWS9=M&I(+?|Lv zk^-Uvsy%q*R?$~|5TWR+8c2s^3*(v-`8(!yOqhq*s|ZL?>{SNTEA~nWhdeSE$BZ^i z1IJ2+O$SZus4D!GFi|non|xuv5Y7Z{AH|NBJ|FBe%0OG}t_U2VIoIG7Qc%Akiz&He zTHH+73%0=M6>|7_PBN0wJ@N`lhGrXf5L_viIF>6gDHwyV&oOZK8!!=$&;rYBRPx}7 zOeA9$6-9j?FNo9#9tCA-FAWYcH}P7{2e^9+hl$k#x&j}JgflP)NDa4Mxd9zUpBM;I zulf0C_Fn+9!-!5&!?9PvQG~ZQlmO*nh3lxO-0Pd;r;;dx9c1iY&^(LZ(0YlJ(s1Qd zVAS|iQIx^H3XA^WAWO~O$4`SzHctgn*g1bNrhh!)JHx5K)s6!2kIi&PEX+0xaXGKM zq68a`1c@2d8xO(hWQk=)cBYoP-D2wV=Ar3TsIpE`N*-|~FX=6iHj=nbK>IT!CR$&}r~FT23C{*oIgrFT`i>=d8~C%3ZtJs{D=6zh2N;VF z1q7O8E*#o&)VZDrehYZ*5M=Up*wFBznc8HaMY#80wK`w!^o9@(6ZqBO&!b&Bh3dO-Gv}o~ z$)m1+k@vKU4`yUC$2Qg8&KAyrbIVVXSKc&swsp{#bXr9A@qvt`?C_+FCkk zRkaYMd%9UI@Rk&^F-k($6*tcs3THY49dpd(vN5rI_2j&YDZ)U5@2!hTMIs6+W>8yX zs%u~xQpv90&7SRif3=|0dFdM(j{Ul#*|S|&L59!+9CBH^lW(fCGvIm^F6_i<)N9kJ zO~?J#70;%RnR+DciuDs!pJn`9mq4&__g1_LjHd?4Iy?{m|M;sG*%-lhe%H@nC`Wb99MPV)bwuN>IofOvhwx&?@J%;$#=t?$< z!sLhubq{mrc@)VOdfJ63>NsdpRtp);6!Zl@SS{sg8c>unl8#6o>J6u}^bCk(!6DC0 z!xD(#JF!%OyjbpNvtzjIKwfOG$djCpRj}Xb5G_XdEh;41*Wk&0FmKV+$B!h%fhx`w z^q#L2B&jniexKvPc}mtD3QkcvtX7LcjKwx4$9Q2^e9%K5(dyiwY!al)!rs8`bNkm! zZ4ql@&>YwA;LZ{*#BAFIj)&#PIOsA@tqffVy)@ zI>z}_s=kF*)k@6P%k5h^P4G7L;%nrmE6rj>Sf_s#;2Ua~AW;UJRTZ+Zan$hY#SGke@v6+A z&GQ#URKh;W*;J++t&bj^VA_EX>oLbRH|6nhu7lvGs@;&7Or9e*x)aU0`YIE#zXe{g z?0mCzHM{L#@SJeE{Z(L}Rbb4~RN18|K$m>iH-X&I_5t`^KrW)iJXfh7UPqzp+~W6q0u#Gha)SuwAVE zn4~!)HqE+|4wGu@zNLZtO)%|SE1#5DgLcvo(`-^aATcyY(p^mrZdg8xvIjt)JRlI~ zL-s7XePgd7Y6tugP>!rNib$=Gh&WwgREEgblX;aRsVp`h;loUK@Se`VM5k}>1Iilm zHLo5C(rtRcHjha^65TjOvmv(s+!4vs9kSM2|~ z=`FKu)Fs#-tj0au{5*E7qFWrQMp2e+LkgjWZpa*X_*Q71EYJLgzKoq@m9&GENLkl| z=EJeeH5S|}`4a6hbP=aN5Y+ouyf{vpU;X7SBKxl^)Bp6A{O}~OwK6gKKf#i%p{IhY zhUJF=4r?SuMBGQ5*CGlZz)>9cNv2sj&n`(=Cr(<)2^v>3;~Y2%m9PD?m2=B;@p>5d zB5FR8hm?K|yPA_{Ki1i2+Wtv;sl~+nfxq+DZI;W{G=FCA`|~~Lr$0{>2o4dNDDmj= zsQHmhq6cDKjK(scmat3`0i^5vuxU{rhCSGiWzfM`F3i%llVg&8v4lus2L0;-9E+~2 zypc$pC@-Z*D$LUMxNgh7I!qLKuh}qthTmOR#7D*LLHcY)ph$B#wmbw%vI(QWNY1vj zge!s)a;miWx~OIHqWFZmKo9aV&7`<~UKDliIv_0yTUK&i3?wwJZkjHdlhy#eZn9%$ zA>lqaW+RHgkQ>%s03Uru>`Yku2hDX;{A7id4C@_W zOj&M-MRtJH{wb^6w!-|I)>MVc8hodDy?=FTJ9VSMtvY?jfPI=+HA0H&*a(1 z+IXSGVJpSd#-+((mRW^$@3@)Hpp!j=$JR@_bwMq89fJpEg06OcxJVG4zX*StCS>3w z$E_q|cZqvVCZfhnb-h8c$(h)G8rhfHazlGl?ERfBGP&ZX8SxIT-yd6C0d}=%mQp$g z`y+^gVyjd+lj6~!U@IAoEon*5|m}?)FqY)`aztHk9oG2%OpQSD(4P@*q{z)+7^7rp( zDp0UTOQtKI=L>dO_bhlif9W!=mb2HQ;69VXZSTsj>`>b&vqFQpH5I@vuTlJ@)&oJD zueKNgM}}JZ<#*^@{QRJlnW1Hv0;O*_qmV9pG)cFB<>TkV3|bTscF(7w`U3k&wCeN; z?!vNKG>Xn$l2m!Y3xLMooWW6K`09uLr>kF_+MO#_lVti2`{s_dK_?zqy`Tn;!w zI*L{*9l)Z=C(USt?IP73QMj~V#P{RX^XD9MEYws~8rI}W^?%bO^rYV0Y7KQrUf09Y z8dHi29ivtVkPztxMs^e*E>!@0=sQ${7 zFCh8+*_ws5!G|V6@nt?}Z03rXuKbP?_$48I)Q}>-EPNL^LSgk(XwgyBWk$2-Gk-`7 zoDGlSgbORl9sN#eV>`_Km9VHkVa8nu(vCz4|J<%+EN-y{kKIK)g06QF=?X#Z%=2#? zh>3X>%xLgv_If?O@45C6U1AMO>JRhCy)ljG3%SV7g&8#q9GqvAST6Z3ol2a#8g_1l z67>Xa|(8uk(OP!#pzM<#wtWUW7{oh9w#gPeuz&B2x?mK_Y9 zVCUGE-Y9)MuIX*Q``7S30U^29C|JLkad<^C* zJFF$N_S-dl<6sGWLsk4kxX6rV5blorx)LTfRNK0v;BL@|q{x3u zJwKEGHbhnByOmasyg_jh;_LmSk;j(vjn%;i9yxrRi!4ygV!H)QXXW&%jSmj*h#CPxnG|4sz z+SiHs#np4Xa_~ePLaZA(1pgUY7VBih?ephRT*=)Eu+Q9|Pgjig{%HCb8e;t_?l&rC z4im;nb({y039vDTOv@%Qp+?5aB{AFV2?($#_1mEu?4*PU|8?0C8JBSyYNv$gxsTxaaK1;x*R==j) zYM#Taqk4_#$ATAojPlwwD?DV5RJ|hBmQx=mu&%>GM@Kq2G+-19cJLsC$Qs~z>J8G-c2~m%@>>rT_ zvvc|)f9`4GD;AgQ#I4S)>hLO$3$?pzoeY)SSSR<@K9(rePyNXwi1HdXPpg}<`?fJ5 z%n3pUy_YVt1@?JVSHG*}?cdNr1gsd?1&f+FZrvxT&+_ARJWu7mH5eqaC0;bKRx`n+ z;uRlCH|i(%i{ph5yQGlF87ZW|4R>tA>0&Fu4r(IyBz>r-WN3&65g)LjM(-pu?KSSP zlxytXoG+4$>TylZShXxPA?!l#M)!FOe6 z1^sa)`h=;Gfh{s?JfqBT&6CYSO9vdQ=`G#z{Fphvi}i}bS?yN7f@|JkI%HHF*W7wQKs^RuaW1YG4eY-aooT?pq zwq*Eq(ejI0fZSuuOD0^FHi)wcQ$3JiiAo1T<5>vdw z$S2%%y$@I<-!;>GLVA)X4@!L;_2})kmUjAWn{d7>4BI?)>+^53V(U$<+FzKpy^A&kmx0 z9^AigH?qGV3;z?5@c}6R%!~xwzO(t+@-Y4@0zf@S)3J~^LlTyksvIA&!`vw@C70Nc ziGHuKs`Bj{-%BCLwl!?!i0Rh^D`TQjPtUf%C$@SkqoIL{5O&y~7#wt}c4YO2q@Q_X zNt~5NQOSNr+XOmy&zHt7?2d8o2R4(qvzY~sc^?m4uqw2gOPOFGo37{VB>Xf9l=aRi zl3%1JrE+gG-E`bxd5`EAh|`tuMC|{zqx22>JO{=%R!j$}=+u!a2m0oFDruQ)mwfDV z7R(z;>}pItq4eaJ32vv)7b*(5A#?M(KSToORCcUK)TDV68EqgI-^;{{6Fl;(*pJz! zL!8UQoLzRCxGBF4=&om>LmtSgu`H78Nr(&R~{cJ`f-i^{@1G%q$R@M3# znV`KfBtc29C{c~-vqOy069xKHC5bg*NPL6Zwilrbh;v;FJ~Rk`#Wxej_jDX4JOtQl zi3(Y;As*-4X5agzyY#+4JwXcz=1>9hcJc$IAm=~cuM^aOB18TDeU_vhaqK|Llmc1^ zpgT2B(Mb{siZJp3F~kg+m`vE-fN~QsPqG?~b6hEk1062|O$ptDxyTs8Lv=$WHbUuy zX4p|@h^s^G;}wjKsw``yD5h)Ncw$zz)PefbSjKVuurb9hhIt`ZxW#eKhq&1uhoFB* z`*A$bXNal%PmZhYbB>wIpc|ZECOdxYNQhk|T6=93XMvtLBdJCSg7y1+oxaQgoDr3=ymrDp>v!PUIPl`O^Rojm9CX51l*88w}K>g5f$9ryTu=gQ^a4sbd}_a&d5VJ zFU=XIu3aJKu6-~!UYPdsylQR~r_5K&F|CzgVZ-GcuA?ds8cXSndtP$#D}z*FnAWVR z_#P-FwTm0{UV#HA6h9bJUt!7157==qX*TpB2Vt`ADD&FTxo8oTrFF@ zk};3Vo|!sMQD?BnHLvA$6XpB|WDTyM+PMB3UdwgiRRJ3Mb()-5EmmY^8Bb*BFMlRr zi4}$;qu`+k;y(^WsDHl@1}?Tn=Ks<8FmeB*uD5Xhd#>oBs4ER(MB`iBJF=V|J+~A5 zIubU=N0SHZwS*>s+tv+CbHF#xj#c6DG^yS{$2U4NG zJ(vn%8pD)!L@R|R_sz1{doo;)Yp#+XU`+)@n0PGX0rli4Y>e_z=`W)lG38En`JFI$dXaY99c^86&)a9psWrhoS7I0P@%R)gY%1ol0Xq6w{{zxo zrn6O8q0)jiV@g}^S2Wi=<*p6ms`5;~kPL%wREZ0+Ro=)14g#b?4{k2dPmEGqpI8e9 z?=D<<+D$0oMpxOI&gM$bITZoIrNwTPhJ(T$$%~_Agvj;nC~J-DlT06+14l+*By}DR z>B|q9n{`u$(Qh&-)mQ3;u_bJdYa?25 z#NhtVS@PMZt7kv(BWD7R?SIc1e^0E1?d(1N6IBCnx5ne|yfOH1J3;tqTZG^;h5D~a z=udAe4#I1CMABno5>S+>HyvED2jfoV>N43js!{SwV|(sTz{&}$>_W&}q+aJ^e0Nz~ zEL>pSgx9y9Au;-mqR^u#KAJ?qi$@wI8buoMMM|zn1#UAaf+nr*i=&d!+zXNJzhg{7fYoO;W*Ub(i%pT61xsuNzxT9bGh4&AHHkJJIm>G|2l z!gx|Zo#`7Bnv*5wh42~Xk%;5Z8Xt8DVSP?r3va|S2SD#@kWA}Uu{fEJNQdOCyrPMw z58HlCdh+%#QhD;7Cm3u3(;3m|N!oHd-|dD_G?{F`kH2uDHM7$1H>}S4+h4EW71r4z z$)GeamlC;6H8q&f6jlO{GSfsn3k~7*AI^o>BX-lrv9f*@Z(DA1flhiQp2^8IW8$j^ z=&yH4_G9QM2K4V3Nq>dqF3<*87x>OTU07v-dUYykT`sMgi3u|yDx!P zfyj7;*zz*Y$S?ZN{gcC2W`!J$2%{clVqXeM+}R~{<1$E@Y~AVCP)CU2zp|B0=Mv}* z@R8yD$ClpjhQWUxnZKYhQf2**(&88U;!fPCR?f3b1C-PZS+2EUB^_a9P~PMxl^oXk zB_~w+3>%I%hr007>?utBr!9n2E=en1{OlLyXOfu~ZMpdP9_1@Wr}L8$4wm(mk!;`B zM;<>>-p`bb)=btcXxN(Y zBvNlXG&%x~VRIf)ltpAW{{&@(oaAAi^yT|C`G8FjDRj)F18TUEH*GH)9W-H zWy4*71&h5Kr*y(0yqG-8YkYAh9|LyBq*?>ceH=&K=h+v2x@cUO%+JSFRtRTES|iO( z{K@J+B=)arBmHN;+HfU(FCy&pbX9OV%T-|1`1Y{|Pi44!IdSnIva>R&K0|5Kq2nao zWd3F}L)(kk#=5+@M!h*xE$6h^Umdwt1!}N^`|>+dfRaPIT3t5f1Pxw=LNNOLWy|hN zsnBIg+=M?Kf^WJyoY1+oUYE#zgHa5;*keR8zT5W)r4M{NR9Ht@?>i2ts})se#Y2+B zRZG#lOFq|z6o3WVyF5j9W$(O~ZtlY_j^esr9S`$TurB$|W}<_{MKbJpka|fB&nd=# z?#{F>6n~dUCL6_@=Yr309&q!#ti+d|@m?m$&`i6{SP{!nO^izqmSfVAu`*V-54R;L z*o|(;VRP7IZ6`HB6 zboX;I`ZIlNkUlIfPOg}k1HMs8H>W96n7kx~ee(5j0&YS~EIY9Ha`}fEInVcx+Gty?I8L(0$o2SM!JI0Ih{flOY!Q;&i)#G$J}}X$6<~R7@=1(<6_V z9h)g+>%X`{eFcmqf0P#H|5#cu{)XGXks1E;&^!LcarifS$1Y3#QEI$i*Pm*YH_Upf z88a-wSM?5K2nGcx&}tp}xTZ^yjwY>El*pTOLAq~3-YXcoY9lG44g}emGQZ03^RKVH zW&LLKdG!a-Ulvjuy3t9(XiNlx3so}315I(j2>r^VB3DMCYL1)ZI%z3j$g1slg;AXi z4ukEz4t2>Y9?h9#WZbM)c8=2N3RF}r-njDZqB(8zuqw+>W9_PqxN>j}V&y8C=?N+J zZDKPnGlV4ML45`}E#cKOILCPqLVl%|ADsx{y&=>)({Ti!0(K<5P)*%NRQO!~bNNx6ObyK9(P7Cw#&R z&Mg?18Hv8u?CGZUl*g1Zx0<`ljxGJ(o$`$QJqa8y{~Ow zX`>}=;1y#H6J}$!r9t9kLgQmX5IbyS&D)M`mXjAY@2bd7`38mvxr+6|J@gV)Yj#}C z@{(MS=8}m#N9Qt>SVZ*Slyyz%u*5L$e?A(9&JNfxG1+c z3BqI%0eXsDDiBk9dXGZ{X>0($-&;w{!K zvzB0?HU3bJMc&GZe|gZ!v&3Q6=8t{0=N__IEHj8;3A_mQP{+CLpaWe)Sj_OzI2ITT z0=wm2Tae8_BA?@D)Z2oT!3l@up9pJ{TA8pFteS<&Ca#Ffp%K zqit|gH0S9HxJb{DGm!$zP#)@GxwV(B4J5GRf_=E$@$qixkcVt1^*j5HYQsi6 zIrQr7Q?s@Q$eHu;gY$%P!vzT&lb)e(1eh$i8?8d-Lp$S71!^qD;{g5AjWn3~o}$n+ zM_9sTxPe9bf+&DP_9a3`;Wqk6Dm!JZ@}T0JDn1n{pz&fjg!0%TyoR-l!tlH7u&8UV}KrBbSTwu3_e+$bTC@>AuM#;H-ZwWP@X(RzeTx>yh}u*c<&fa z5}6@zRgL{-c@MLll8X3>`uL8lh~afb$*$5>Mxfy#!J0p)ADDmvcoivyAj{Nes9 zpN9qdEh`>(y7B2t3eHyq=xN^Gsmp$e^))Dc>HaC7AxHXI(3Ce!^*-w1f>HY=T8F&7 zD<;)$>_S)Y`lZ?j{H+~3c&Mt_h-vs|DMu87rY@PB;;`%Pfpx3}(TFPkD|K6A++xT4 zWzJ08{*w=P{^wEg&xW|SK~^A zSPnAI_mEw%dp^kIAsJUa-P8XXbK1<0sgsGsoGBW21kHh;yN1|9M<5nQg&&GQE-a59 zLVtywyRzTS6W$j^QJu1F)xZTyeCWxCk7>c4djBD4=o|`ecJZCphUA#{FHVm0F-dH= z-u^6~qaZ0}^&8^NKHsKmxg~z`la-LD+Dt-)+qhHO?#Wk(aJW&i?yvotbUr8bois$b zAF@T>i|)9n&Z#0(^+Xr3H1|-$htI8b$MAW>@R3}h4S&ls5+hNVt-0ni*ESj{`k?n& zCRw5!2eriePeaJGR;LqD@caGxk8e2p|A>-}y`8OztuvS=?P_5R){6YUko8|*{X0;K ze>7pyGU^+w<|}8*Zk`}%r>L86t&vqORGvRwZqT(A;VhDyq|+T$*J7f7*aCMpI?vRQ z(VX1lQ$0oy7|sb8s^3^@)G%5ZjCHpMLm^0GB-t=VEJ$?K#QLA+gtiA_M{sNOU z@kJj)fwT%8Ky53C&8?2n?YNuir$=^SxhgI9rRNrSuvG(CRZspZQzwkZ$AQwO`ugzD z#a$%3`gX3~`1U{{VQL4g243VW2ZkKzCl&ErhWEzVfJ;}N=6tk8$#)d^6bIf&kjD&AZI%T5Ke$x8ExqBl$_Cd5TY`_6uS`oIiWWLw6<53Fg8#-%V2j zDpb4CHV67}7f34100P%MOrmVku#J)p_n8e664b!C?3m-1NiE`}?j4Fh#)70K{WqrI zD3bqU_k!fNNd7hP`t8>Jt1wm5cA6GL`<1YeWHg-2IUk9t_2bhPDHaY2iePRG(2xbqC?zerh$96~)W2ukxx`e4z#e9Ow2|^c*lPgAPud zKzd`NgFbfvQG~sdUpfhshyx`MKf;j(LmcagEu-6OWyInhmV!;EryWQq<0siM6_C0T>Fk-7&SaWP&0&oN3^I(9W#C?7JAo8=t_uH35D zmF}g;Q75UVN`ZO|wPuvZtbTRc8S{AM#*gIz+gI&STXXqA#1o?Cu8y5K3Uuv?sf*P# zkFKriRj0eG$yUsS-2JL~SRA>ptZA}PaJ4fRMTeeAi+P@xD)4!1AkD`i4r}VdK|aAr zb&sjJzChgu-uu=H_0iGV^TgwQ+|Ifst)+_8QNP(`qAya7+MDnyJR0E;mDnYh8CD)2 z&`6i3$(D6E@C=KV{4s|L&>KkRb?R(wlz=%z(x83FsZD!nNJQ6s!#6|LH7d*ctjIU$d99MwtC3+r+Z9J!_~8iB^ZQz{*|i?usHbL{ z8NSv7?vf4rHD;W)dk-)x@0F<23_FVaX7ddm#zB*{M$Y;-{-{0|j2xL93?m3O4FPe~ zM+v_u*lT87d4Rfh+XmhokXR zVyV#UPTu-ic}(KlQ`HArgZ$!qRM1(ucW${dxyHHPHOfKn?w0flF~1^jZAHJAk5l1Z zSIRXzvN^&gF5CIAPtg=RCT~NMn!P5*J1(>=v7_mZOPVmiUjNK;{X#kD;_r<)_WY-C zE&}PB!C~zQOPJ#y37M>=5<6ESnwlpr7yD4Y5l49Flk*rs7 zM;Z22TJIz@Y4Mzp4Tt7#65LHA1V`yzSl{@{KVRtWaA8gA5 zNw-KtqZc`SP<1fZO**^Uo^yCi^;{>CTAA`PZl~BH&AqAS&y+Hw6KQ;3g zo*Lqb+W9@BJZI@X9X;zHEo_r@RA^j2v(G%1k^yaa(gwR?MWR@Xs$yT{mi!|=C{qcQ zVv{*gx~bqyFwulwIaz20LGV0&WqdsMepmfeUlfrzaipzovFmXTo+G1f_h`%}{R+u3 zGD2R;>@hZR!50kk~HfHK-}8 zABlX^;W{qgvG1*OW-8>!=XSS-u#Xgwbyfl~>uutj$8&VRkegCi=qMgd}ZYMlfwf#=QU;Mdc_SDu9p_e|`+5ajBwwv=yTBtJDNoy#K8? znj!lwq-E@nxc!rnlhoz0aec5WI+kHuFfM;9|JnV)L#X$P6$n)LMm9)}mZt{gGn_%F zi?h&5cOsH@GyWF68ur}Q7tbHj?s?5wb zeY!Uyk3ds;OvnI(UXO1Q1K;AL=(3XQWK!no2|#lKTP)V?C|5#wVUirL(^t*IND<*v zJBIqL68HkVXrA5n{_l@SJY;1ffPl=h-5SO?nuk)U&KF;&9P0A$q8YU@k)N#T+90Pk z`5W#f;WU3b>i3|d#VBIn)4nzG{ES>F3CA~4S(O8lCDdpPzg$7z{tmuVIaC`RvdB#w zi{TwN0v^2|(kp$tOKhZCf%Z-@U&OCYskHE118&DK@840t^7G)cOL_)R`UscPHdilY7M zYq~M9rYOQ}Bu|K|cd;%We%VS8!ue=vzm29Yx>=}YM}Dbxy!vge5@IV7(|wCe;EkwI z;+rtNH(|pF63>)9sjD_qebPfx{M%gn%Pjle>-YEPzF&T!{|IkR>OF?1r*hZs;(=E~ zYcJmT8W0t5hh9N_K&3J4zpZI8wiD}@CXO<`AKN`WH6n4PgRhw}MiQbtrGg-_jV$Y^7R+qTkEWRjP#Z#&=5LKGm2IyhHGHKcVGJ9 zz*}kaM3U;47e9?z+vX=eT;ym7`{TO z=xkNX7?7b0ZxLi*_7Qbdaz?R-b~ftdKCEESk_8iNGegf*kd%qomE-EN&JG-o>aehV zFz7@OBnCRp*Y}nQ;K1Isj&$4a`NFbXz0+j&$!+V4AzF9yo=2i78p&wmn&}69D_W5q zhvPCFz-e9m)iY1$sW{*w(+PQSl0Re{nwfQe!_L6d*}-Icj%BEwHNwc3D*&1?h{c{- zZsjO=_U1`28f5E`$CjFGv%&ey=C1FbmFsXbZbBruzaS14^7-$WF7Vp}FKX*zW8(NHoBltq zyp4&Cp@}2dH2Xh)`fZN;w|CSu{IV@(7qJp$0D;x2EsgZeU_uHOWGC9pXNgexy5XGg zwz^T*KD+SQ{Frxr@B>ybYi2YN|Cq38H<@VvN8wCA2H^Yk0CfVuZD-p0CJIqe3 zhrg+|oKcpZq$OF31ud6mD{IkG|3|+e10jT&FzI>TY9n>iwv#%MN0$Q~4~$dz zqj9LM-K>7dF~-Hq!UY-hJF7G31U6qTfj~abRKTNJLSL4~9r`KOxohADl>~+ld3R_U zyaJ{yhyls542NQK@A)QG69CGuw_&-Cnid5-Cp@O%xhwN~)?o|#{;v*%Ru)JR%BlCc z_F1hRq^ zkzeV@^7Qt6Kog+CN;1czK#-Yk^?M+CM8>O}nC+8K==3|y=(@M%kjq&fMNK$i(cv2+ zY!+q@=@s}RpP&+!GJpVM$36JS{rBUB^V@NgboG$`bI<2rYp{{3+R7y0aW6S0d8!Nw z%IA5=s$=-_m^Th?9q+yTBMBYqf^-!Sd=_xJ-$GNcaALwgQ#`*3r?w)fm%7bU)zhmV zGF$haq<9TB$xi)*!Q5O#?X!1%H_ElD@5cT8Z(fgZ*lIusKlltToi#0HcBJv>VVVX!p(6c(B(Wgw}Z$|dwrw~ETj z4|H@62i0CWnX(jMks-7)wQcyRm%Dl|Omk5lz2B3&YA;G3S&w+XCCa0C|B?1%lpd3d zRetRb7qEeeEH`<@R+-~Q%4nIzI$Q@cN>_H7)kC?NhAo<>)r=?TgZQRc`L5Sv*&vMz z=q9u`wBfkQGf{Q!I*w?1{TIf6O0%C4YM?q2b zij-Ecm`iS#*WJ2#rtTIy&6l4E#upQK;&Yxl=1 z%4?0EOKvtgD7qBY z2)(3Vj26jeak0x{g{5Xk2_-e4Ea2(f#irBKL}`2KY>-Juo~j^eY|l^Ux2u>w|E^pw z_kH3GYCHuHf1nlrOE@ zm`>S{D#;Xn*t|naZ`eXp)7=d$R<X5=P=@?!9P ztw;|np5-pRD{L&i9ikwO8ZwZmv4xa6WrWrl=Kk6`fMNHS4sD=%h7$l>mYV-#i~N7x zu2(Q{Hh21$k4C1Nh4Qi*8eca%ypRDLQ4o7fy#sCyj+DegS`wM9#fVZP+5vfb2y!f$ zdYtY-(wSA7Z8rA{>L8}funa}^Q$BacJUgVGMp4$Jhvyyss@s{*oyVHM>#rLQKfPyi zP(p0XWle;<@k;V#Lxi2NH_bK=HrN%Ba+?BMVEA6NuNvO_z^J+Rf*C{!!h`w}g!XP? zn6WemB@o`Yy(|Y_7#^+t2jd81_XROX8h{F@W)wkF*=~dYFU&n++cE+eBbXvs+KV=5 zZK+E($*d-oBUxa}LyclL5rL{e4(v742~`bu-QY+~^m7B*;6N^P`aEjhiV}&-}+G>10Y5Oz-`Q37EZuJkYZZ1$)g-?mwP0wwG@qWPO3l-J)qP-0~VJxqmxNjsID! z+oO`MwzT*rrd0cKqJ1Ar&09lgwQ~_g&)mT_PDYTqg=uw0m}!|+bcDs4rhenVVzb$E z1`T;T3oP;K$Vw6`;UNPr3YaHRW2tFeDZ%`@)QE{AOD;=N#FFSO3)RUK;IUa4x6`7r zkt$<1PWphGtP=@U6^a+Vk8){YBdrOxAgOMs+^c0iQCMKqwCzwcH*htJtqfKCm?zGo z(&eD+wz~O(!fBW(9aATFm{dbwd$>L+75?j$w&JeqFfX&&qxhl7oI+hqdU(DJy-`k* zViptifJAn<8eL9vG0`(5`Y1qxszJp}N28m`FxO!fM;d!iYoP`e6r$}z-YTgAe;4BV zbtvJqm4v&w;)HqB!_c9H-r#gWjoN8$$dukdi-%UQc*-VXd9D>%p37rVRQC(7c7VRN zfj{JurG@J7z|y$&jDvF-VfG>m3Dfj9z)zW>AvRa*FHm5BadTz9LAF zP8DOj8@)#r-X`1;Mz4%!mo){H*Wr?q@Ezi>heOYrC{c-p98Ji`=lDB?c)|VclLC0j zo}WP>&blQ9OskzCOS@5k)}rFu9D292oJ{=4to>FXgzzsu%Hh#pcsi!;z)ejc4S)_% z3HZZ$0CRNI->FlVw;?>-H*YzFB8J&LO-T26sY?}S`M`reaDzlkvzy$&M9o}ZZP`(8 zS-1Al9N==Yv2qUU3?{q^g6O$#ku;J>Yv+_#5DMm-WU{W%>Zt--19LDB_MonnC*IB& zASE*e*`}#qj8cA=K!|L2&fyE}>lgL(ggc+qc)R^)vb1|jyc`97*a-iz-^Tr0vh+W< zZ`_>>okIqE+Qj+aoi~?QZE4Uaq~P%->7_I+<@0%X=o)xnfF0de_y7g8 z%b%T+>Z@_Kq^lA)>YDm!1U+WH37j>8Xf6b ztO|o9k(&@olxQ*j7TrRVJ!s@Iw4Z~B8npP#zNw|#d;#HMb3RRB-e_n`vSyb{?+@4@ z$j_I5Cj;t$t_tHk!-NXCenS2geh_^C5GH{HOz~%K!o2J8$jao;!4bKm8nug$8{*I6kX?$#Yw& zdf!rfS4u($scMw+xbFZw)SI7e#C^Af@7>j?-Hn;{P0gCCW|tHLw8t>IN3DPvTl)&R z$cCel1Px*o0Z9`@E22@+D618e_J-dGfS8IDr7CuJx-TAS7EphFU_9HFikQwSgteZw z^{K(2?6WjIMQ-^;P#BLE!g%{|$;5 zR~sexZ~Y`CA!$D$^;=R~6R5l+_$XR9Ln0dK=z4s0LaG>$o zS{UobyarJz8_2l8|Zu!HT`B-Jf{LgCsrJv=xra3Jg*rTwxHJob? zOecr8K8EvSV4{LeUUHz#P&k9H;NXu9Cdapw0(4q6bQIu(jhG6y-ixWqfX#QGnsc|_ zI%C|a@CM})2QE|BHkTU43CI}e^s;6bE$%W^mAMB9`WGazzXa@Rh}n{TTb)Yz#E?4o z?EQI1mxFO{#c(lJv*mO0$-rhp(iik04hTA}$I2GtN-f|ij&dW(;ZsbbjYGv056QMh zr(LU>qNmZJ(Fp`dt0!-RJBpSu#(;q#8ItB)0#yI<`GiqU&g)7b59XMgOJLSnuN-3W zE#2Uc6kl7E62((TnI8PndbPxz<})P!RtHl7B87B^aHUTpu&n8>lKgZG`-OLLhw!Yb z(G}-CNAFr+1!i_S$H#dW<;hLxlQTIeo(|V5-eUnS-=Q$9)T+nmP{kI$<~}GqjCu?w zYc7-MRhdca=kVLX49`iu#(Zl3fv8avAX?V3kp!A4xBBF zX5+k)vF&sT+V_nLIY#3bI?yUFWwf>-lkfY@58pynwr4t9?iM1lo!XRGv`NliXoYxk z&qTX#f(9_Y$|xrpwBE$*c{9Dx%*Z<7QybtRJMpdO1in>swe&I3VJGP2P+ zn1{P%M0`?JHx1_kLi76R-(Bf-A2DYpMHfF=K{@c%`_APAhh`u4dJ<3XPhOyWFOs=CG zLiM|rpwM?U!N4^|ZGR!_bbf-P+8-L6zuI2G1tttEWf6Z9Ik?#T|HxMSd!g}fPd~$^ z`e7?UWC8`3;6~o0fDiTPA|zqJ9Ax3G{!>>%;i9&5AEaw_Nn$doJObHg#Xg=@l-{JC z7khoD^Hd%)r!T)A9}z!69c#*%7=R7e3`9XBh|z2+Y!#Lo(--vwTsn$-Y0Mms5=Ois zGKN*uPAo98g_=6!xdibZ*9Jo3ZxOhq7+q)$wECIPj+#R}@IKg!_Y034mXsdA*)jnz zgtB=I)i1;Zo1uUahrP%juz=x(qdreR8iUMiUX@9ZEwrO3$acG(DosBwx(xEn^M(goU-MSq9V= z07DKGS=-8a5aqYb1AuR=d1SjW&_E2?fSP#3iGf{K;KCbUBp_taDbki4yN_WbHrKl= z%wnl(|E#YHo7BW9te`kZSR}3hK!zEYpBprJON#!}0!0Ox+$E6;m(Zj%8n0m$HtfiC z{{X##`KR>mI2Nbq-U}QsLInYoa7-7WWc-bEw24tn5rIVCo-0@$9;wcl#U{w4mTbh? zUOmQAUn}Ls|Cwqki08h8*DJ!?;|vRO_rd637ZppKPl0+zS>qY;kFzWeso!A>h7L<` zPVnEGC-lGN1OMEs`wLA9RV|#rL_wczt4mKjYiuWMCwj5u#f2c{Jw`ENKT=$48GyfW zJ^qM-SIMn}pD)CB2qi*g2lT-vmCO8+>jCBg3FxwkkgP?HYA?@L{f0H)`zJgr8%CPUpIH$!1 z4lU$7lf?#f6fWgfkUv)jlRc^#;&Z?>fUk%^ocJ@ARMFSy2Z9yH<|e~YPF{c$fQLQz zN|CSwJ>U-MnY2Pgt@nr^bRJ{VY7w zM_Xxt&6&Mbld>=sPsg^M&z;U4jV*>~GNFjPES%hJ`$$CQBv%O4<+)qLzyXVhc5HH*4q zCe%bk#*BwoR6pdbe*9oApQz`qg-#?aV9+Fj29pJW1#QfU2XyRJe!zfOF9e(JNC025 zV;oO7ojR{64S$8v9gUhSbg0TxQYEWX73rJF*i6(DZ|*Aw?%~2q9M?`S z|M)Nh^QLlO?!JVHK8c_{i0wo_u!gnss}I|Wp75(u(rrH5iBC8m!)y1aW7UXXB#x2g zaue~~7I=ftA)Rc_w3UYa0*52GfTB9P7>R1b9o6(V9$Gy1m%e}8SQz9AY&9_Yy#6Cs z{{P{|I@|qaF#B)pv5`j=1d9bhRn@};!{-qz?EdKNy3ET{Q2g<;^erH$Cwl%kAY^`Z zuT^})SClv>Za^H>H^HF_MsQcAeaF;we>c_4jQJ1mbLh_)iSKIR&`>DJ8S^(=fVDtO zoFumPnQwUps-QIB{HnyJ9HChJLC-2FN^Ub|Xbov=pcM*8@_OS7JdT(%?~nEvo5?#0b`~ZNL_7o*L2nbYVcC$OLA^T8{EfOpG4d_jT^kC zF+PaYkyTQp!BRAAthr0?+zeb>!T~z1yl)th zVxz7&r)8?AdMB@Q#UQuY!7HLkt8Q{`wc|_y%ui1>_^d>)nV-(H$lf5kndTGisYR@5C=%~5hp7oZ$>GeIb!n@1qREHUaGuVfuw2XOsD*G-x`z*LW` z)`P{bAfxZ;8~td7$p}jRH=E@ZC))W3tf`T;l*K4BJjZJEW{zN4qWD)1HDHaveyx%f z)p+e}E5ninOONCrja*=C=H~&?B}5R4(#b=&g5$iSkL?H(Ks==J1<*0S2Hvx&FQi&x zu%tH}c&9Czo(^{#;+(-tCKKb9X3KZ*>7r)4(KE2v-TiQ>U;^tfSm@?sSd-boxN`=v z@^jFdGQf08pAiibo0+!0D9|aQhsxHc?=<{PVv3&zV;l)-am@F3*%Swyhnrh&1!I$v z7e1K5TX3ETg{TDxW>}n2Z`@_Lp|3FiIM{cys|-G1D2n_?C?ft1iiGX{C*kog`0-Jc zmGAz9I8pw9$+2hed4`nik_uY#A_lV53cqF;sO3b7?jxiX!>^7CaE5C;akp z1(fc=Vz)+GcTP}k_vUCp-ax)L>8lO8C^{J;Q-#C?TTiZqLeAj;2?0%>aN}$+pCEvPS-$#2d$DRqjIAIH(c`XOex}7Lg{A}h+af=5D z&fh==2z?G_OaDN?<)nc%QxKGMPdZf8>tWv5>6y$=(4yPV0UPzp3OB*Qm4M0U&~m1? z1KmOOVN1dY5j%7?09_bw9gE!H&#C%LCwqu;czAqM zyCMSlt9e^#WVSK8HTXIgr7S6|sDr$3N**eS9uKY?d-nOa5ySX$>G z-+bo}6BJGl5VV%yaM|Y;HlCq&0`>&$r4I59!fc_{z}{ zh1}j*P|Wi=n$phGUoz?bNYA1-pM^TlD=LoKWdk8GSeTxrmSgvXgx5*caI+RD=dPVP zQC=km9HUW_Bv~buwfBEwZ1dK*4Q4u&m6n3Hu(FvQ04Nkj*21pJ5Vq>MLDOtMDNcE>{{Y zfN9d5o=k4$CTX{r&mb#~zAhc6;F|{|V^Rb73_n^<)_!zJW6)O3Z2o?*%pYB~3R%8w z-qH&MA_oTX<&gKZPEHLs%k#{O$Fl6WOb;GJ6md+}$Pobc2l_0#md!E4m)`h%AcIZo zt??`KT8I8*u@0H@6i_zhv?;7VNkcmMK$__h2$S04B|unh=)J+SgLslHlRYF>7c0Vw zOvHej{sgtFNPLS*XJszi!#>dnPgM()a7VYn9+LF!_D7mS`ry@NzGKro)(~8P`VL-! zQJaLhwwLSB>Al-G!aj~h=YyaAxe_t39I-zg%noXiGBXF~LyN~6aDr;{@tPbTY-b+U+KA$HW=)|!iG&Y!^8@?T7nm~hHavAM@DdA$R1iP8p1mc zt~>`ZCQXY7rN5A0fut%Zve%P61SY(7+9Uy&y|R1WJWgY7^epRl1lgT$4nB*5YJo+- zBxq&fwg6)C#8f3yt<+R%tSXgQ^&-EtsB(>|$sZX}Jk5omAF_CN-%7Umj{syO5{Agk zBkbC@L;`zB{V(%#jZTy+&BHNvjkj2OY^MXe_)U#4HGtetlqT)q- z3=kZrt|(#!QfWUL(r`J7+{f|YLyhPRNz6Z(2>=>~=~!r=%jJQV3=%@>)>5j412B;J z@wsOu6rl3_89Q7q37$GqNP4x z;prmP9!Rb~d<}xCB@^1%-@Fqt<9* zl<8(EoDb4f%RyWtuYYp!aEe}r-hrQSw||_Sv;KCZej}y-shatJtr`5q&-m9V3syS+ zBc=3N7K%<1dN4soM{*=8Bm(uRXyAu&7_9Z`rQobVdJs3O8) zg~13qkr{)jO98f_uA6bks{9-~YgSAAIY?YdoI$6UB*OeVdOO`npXG5q<}&5nk{I2Y zieq!7syIF;sB*?K(0~k7q=P3?y-gCUA@qW3s8RLOb%QyK&%x)5L5002qf}b3N*tNK zI6l#6ivVnSn_|kaNo7`2Ux3nf#)+|q7p+{T_dIR@DD*j%A$BcbAlO^A_3Tr?TY&)v zuX8N9ShN-w=0SJz%8Ja_G0T9>nQwz*c@qg(&|h*KhK7ug`J%n%E_%)sNcb!>iLRN2 z8CX6NvH)0guiVyvxgKU&=4hgsN7KRb=Fae}c-OzKWgiZ~Kgg@v8PcD_eI6R27*Z56fj6eBDjQ`#BUD?FwKWdmZFMggTBZfqK<60Qu*k+#AZk^kDD zb)F$fcFHAeRb~eTT2m?KX(<6TDV1BC-ld#<=?A3jxM%;*iQXTbhe4r{e$QXTvv>j zZ7MItIaZ&=NQ$f9K&pNxdI3okvxL$6wCJKEZfcgasww$c?O1r7w{|CV8ufA^%45vZ0A?P2<@z>lJF1j|JQu+`GSlqEc;|uz+jX-Ji(}JAaoCd$G zow+=U($;TySY$|%3Dlj5rIM}~X;|Lb^=58pr3Cp#*_xc(a`OT2#xGxyg^dK}hA7>B zD6D7eJd;?h-ZoQ!Mru0Z0HdAEKPzNNQuRY6P%p=AQu}|Zs0zv&r{Xi8Eb(p0?srOBObDkj(VwflmF zPLEl`LnnKZz^03n&l-!TvyhdGQz?kt!kBjnvJ7ENfD~=kG|)5P?*p9wBFbf)(@7>T zkn3QbHb{O~9?K3;CdIc1Qir9~DlkvlDSM9_xvJhS=JfO0^=Izo<=Yz=R{V0-`hmGY z$aLfVSF|NU{mG>eFZX;irQ`v5gH4Vw42vK)>g znr{BV56+tabuF73xm{u@e|WXh#qw2@~tJb})wXQkmH7B`fwk6wxT_8yup7}YW$osdx zg8ElbIVo>1>&1BM?});7iDkK=e&!5M`Zm`tr@kL|cc_7w?mHQ7M~yfN+nT6%s>3YE zm&E!J>u8N+#yZ1sq5QIUjC6$r&twt2-<;yZAyKx0Qow!&p+P<9uNjGiCeouQX!yar zp=Pv4i7}+7W+g}ojZxrOI2p-zxQO6TARs0Kil?%*RZ4nnQ47Dfv8J|ca)Cj1R^o~K zyN2hIg4~{6WGR)$YI?D!a(d;uEU}gojVn2`+B?~CWf&P^q(?iOdr%Qz4l-?4bYl&z zDA6~WPgDoe6mnfT+uyqO^%87$e>1ml&bs3jTk);h<%r1CGFJ~uOj^GQT+V)SVs6pSjJ~SwPX(8REHgRTs2fdgTmy_ye+oy`?c=yL zev{UwO-)nI!}^?6c#8=VG}A5>iBL1^NRg%=MQT#l4{Ib@6Q>gA9?WW;Z&6p8G0*kD zWSmY-DQm513Pny_84*~9TMuYRGe-~90<`zdRGAO2<17$(-^E3Ok(7=5qoTBZQ~_l{^qLPF^c z%G!5ioP^7aZfkP<8p1{D^)djyHpzkQDGe_MrsfN%0=!8&d`B~U#0EE@r-v;j^oK@B zd)&gDDg&W>W1kcO;V7#x-KYukV6AOvPo0{^6VC9_Gw_!#8=Ssj2?$jel2(mTMcmyK zdw@dp9*u6aZ@*}{WP~mK?<^aDdfZdkWrxd(61M`AkPV`-z+c>CDm(bi@aG=D{8u}s ze=7a|x8V*)X9p8A7nlDk{oRzexnFE!^!bX4mg8pXYGC;8>>I4G+LV${^@- z1M#|@8!ig;g`nf9DNP2&kmL1YjJHML)ez^HZjG?!cxw!kK;TBak5US?91MW*yb0W# z)3!+)acsdJI(`n*D_(;5m9sitPtw1GJ{Y@^67AUjxG_z`qp;8%?T?>}V$z6hex-5{ zZ&Y2hXG&p*Oi#q~BggEBb;n1Q_-#Yz4Q=o?VHe!hkx z@uC%Ywxa(dXU-#Adyu#@qiR~JuB!fqsArs}Z3kuY$_#IZWNjD12lFr=HrK$teCtxW z5-$5|m>$LIqOVkpY6*0H_(A;#-HTw>+WO1JNP2_^zXmk3)`Fl zx}MMaxb&~}k@Fu%;NL!}61EP;f4ijqf5)MRm8+$=m95!-p7`&X_+N*oCE<30Xi0EY z{@LwAOZbCn>2IUrV_Hd2c0=qM zQ4-QE>fs%SuXY`%*U{^3nCjLa8gsVWRc;7N%&HLBH|0zkIYtM>PH-oA6G@sKbHoyi1Z$uZF@M<%}_ z^T#M>1J~^>xZSVh06!2HZ(_&wp|T|tAyo5t0#E?4_XC9co97&)6L;K&A=b&=nuO(v zu?Zq8T(R$3Zv4eL63{Fe#1GuLD>PR!=nB^Z|xMPJ_`yOp9v3>6D}xe3OO$gQKDk#2=*|! zT9?yi6(Q2q=&T?lmck_@b$$|q;SdBmVMXXUK-w4WkXzIajfrH3=j=S^En}>Fi|31t zr-kwAa89HbDlGUprM6*1lcCPM;WwiK)8gQ!*!GMjrLl}ys~Bcg9JyzWE3(C3*pYFZ z35Dl1Ea$PO2t%ere1{a<(4L;>zISn<`86ERKJU?I*)N60nHA?PWAU~xL2zbG6A63g z0x@Y9P=KtdrTLFt<%1wzS&}@-Z*`;mh@2uDrFHpBCJLfk+pv;+cbl^6+4-NADW=2? zrHkVj1ZL!O(fL$}#t?GumKIou)MEgRf?v%SapkJa4Fkkd;a$wj;guf{uU@geoIKnx z8G@OLsfwj@S$&0iCdWmPFc|}$2EfP|ZZiw7x!+%lqf4Kk8SNDfJ4EYxo-sytQXx}j z4eNCWu81viE?p-EfdJ#)wSiLR)BrsTDhpftU)aB*iGDyvu}xx==l@j@p6V&j{61Ha z<6jHHKQBywBO8A&V6C>7FU|^bUZa_SRgb*w$zbPrA(&Vb-Pmjxs_^x2P#~f)543YZ zL)u!Lv6ixHMyN9y%tJ7QcR7V!&_e+$7%f*vSW78gX=%!07gf(~SE+{#PjgpN>580>?lDh=Jvx0p!3l&;TM}I%ohHa1Ato1lSH5KmmLP{U8L!gMN?# z#exFRUoe6KzP)e+1z@}&27REtFa&*|za;g4L@*ZYIRaJM>fGIsZ&jcQZ`33 zhf^lwD#o%y(gx3Q8-n}Jg4pa+L3-}f4tZex83$s3(wbD5XIutG@82VIVCK2^Pwm5^ zs18v9@|nBGfr!wj{q>|3Vc}#Mkok#*{IblxdcokpV6d|R2hxg=dZ`A?KXd~ql2WDv zQqby6!_qG>Akru`!32^G*o!C~``IWP&td=u7GIsK_{I7gZP=d10P_ph&~23=7k8B* z$9u#+Fae3-OQ|f+HlnFvy5+2ptfOxryn{|jar+0q1L56veRTZx_)uSx)l@~)i^T8 zG6=cEWM{NB?MOJF22`8*(&>{QT{@P>)Q^F~5n!IW9O}(h=(jRR(wZ@FynKAc<(0Nkm(;sy?7RKC;iZS;>W}Epk=+hqcbjXb& z8s^44HxB=PfBHgh#yjDQSgbIr^8N(1OLp*ISB^%)JUkF`Y06nQrBf_am0hgvcD z!l~m1;W70hrAvL}vQB?=kC}K^UGvKZGwtruGcsn+aKqsc2CFYj4{nFqKU>WYGTfAR zN+oZE39B#q&CC}Baq5K+|2IDb(iad7!tpvmQZ(qx2*o4)H7`jl4c~zX2Mxv&d$Ejk zqOcTTgoA`+MPBbo64a9mx_gSQ|Q%|L+_IVLpncrwZIPXe}K zQ@uc0Om$-iGo~_6NU`mpHBH<8x+e30Z11cguaItZvu>j)Co-;z-%jGNP}Y{&x(_LT z1`uAH7RC1>h0dh!aXgbvk&Zk&JT6hN;8WB<=iD_hC%XVO? zepq}t6d4&G?v)-|3?fz0f|ODE*!lUpA2tHv^7l17#&Cb3v@`;#*t*M0wcN;M4NZG*C+~&E^n$%O$(Tx-GU`0=6ss}^K30R*%7N0 ze1niZ3)5K9&XI)M*-_TD%}=c#2EjE{)A8gULT2VNf_SI<=gahDxVY-E#8ihgb6YC5 z4UC6f6T~K+pVs8{K82$wQ%V($3VhwIjJ32ZcC|ifG1atKa`4tB&}GU!mRncH%*x)a ztrQ_deAnojkxQUqm!{iyiOVIH8?a#^EGHYKA(uO)oBl%AdRFH7oo2FQJZppcsqczoD@1y- z440WaStC@3ps-vwa$&Vz$>8O`3{5)m>0yPUuWLQb+md*KPPQ+1&gh$M$TK&{_=VGX zEGC@U*R^sWY2)c&ogdw(Q?MY}UPsU$TsWx{0I<>48HI0&SL0Wv(G(7izgr4MT6Jbz z<;hg1)v8$({VS~@^R91_mb7dU${-xQtiaWKNZUa&S{}b!V61eXObJL~*y~nrv$zT) z3*w?Zwra#ui0F>R-Vj^pTox_6`%Z~AWGov?Y_R+Mpf!-+ zZbw6IUoV|5`rV9-$9XoS9PhKA)74}SmRDH2C^X4^}*EhS7g1y7#!WtgfT86+Y3;DTNF!7_BI`VK&G1+FH z#z@yplqHG-p@o%Z7YB>*=rmCqZ5pJKUP)&3{Y%mjKYszcIooLVDsb&slKn4(ywNdC zO3eob-UY`(HhCwvXeZLF(m1Sz)um!PtiA_^$u`B``5S~by2F$K=R6@yS*aZpciRg3 z(qNE@4^-nk-Wo$9DM1e?+>;8khTK62)J`j;nlt97=V>oG21r>P2`+>}6-A*iG3F3> zn;N`a=w}{r>K_Xgle$X+WJ=^#21;a34M&p1;=lL>kg#lGD28Ig2c=ESU5K|pPYq1Q z+XU?)6757bZh{Oo;uUoY5|W2DY2?TOBGN@Ng_896alm*{@D zs;NYLl@qIm-6phIoRbbm5)83GW61&AMHQ2Rc-uHIF4U9lUO~9BI>9)tC+NveEAO$y zKx<7!6Zs#!NqKY2zRquYd8^LaLez4?xrf&H_Ex(ERYPb6NOVv-QsQa=IDemrqJFpf z1<+#ZQp@WM=B&QQqgvz7XEcnz!O%GopO0>akoO)WkFhzZ5GOV_Jg47;%ARzS-<{YW|G5U^OG| zj*rj|#dBh-QiKubR5@bx36#!>V#$L8Q>Cw)40Q*PDibf3aj5zbCs8F&iiTc<=ok^B z%&1D5Df1{PyPq>`Lp*GLJIeIBUK>fN$m?;rM@Z}PSOi#lEt7_)r)^rp0`57qFHqC^ z-biSv?v6O!?5k%oFBW|9ggNJAojX$)u~v0`D@tGH0f!#kqJt5HMG=+*=h^dM|Z?|(?&WQoG5w3C`ZmVs@ zoOlUc-owclxJn|nLbvhgIkD+(2ln3ttgIh%*xEl`ba>nsCyHKD0T;XGXK$C5RZwB)e8^!(@wCo^Y z4W6-OJ1w?XXJ%M!z~@zjg-Q?lLh;eg`h|~R0H^irYsPZkoQbH$}>IVo=;T(X+w2%m^Y{cY=pxyWyUDGLNJpG zLHU$ao;KKnxp^w{xqz)F1)0>f`C$p1i+v{@C{wq$J zjNmfwd9MJp?L0t8Z5t|Mx~9C~iobWu=7p_FL)h4oBQ5v^R)$X!To24(LLm0gL+d^v zFO)r$Fe+~F4kL^Yk+`nYp$v)zu6^JT;a$EtW?ie2>Ut|T;>qIHdu`hiP$=|Balvs> z$3kWxJ8xXvyl~W$MC_bA>!*2D15Q75yJ87>!;s>dbm!!b+;;0eSt@+{)aCz4ZK6$Z#X(Q)6)xki2MNf_C* z4Jo6)lOHsQsm>;)I*h_L>5Z02sVM|)&DkvE1Du-2qe-11sR85AMb6x@-s9C<+B9W` z;rYrOO;?<>%Nlo*Feu5uek{&>PpaojTPsfkn-jE}hw_7jXI5V{BJko_X3$q}$v0`C z3bpyQ5UAQGs`Z=t>Wbzywbe-0Z5F$Pa9_xlpne$3LFsc55Xc7HI-)G$Tacn^QG(BV zn?JY?d~tl@!uG{0udbXvHEC0i4UGG*W^xL*vKWKEAmaVhh3=$`l4r~nSlUoQ_S|Q^DUdQf?Su^SSwEkzFlFJ zy~|ta0l%>U`JVl+6p=5dKbl+?H$heheO(8CX?~?S*P060G_ANUc6#azOEMCi`!3~4 z!NoDRC#e5iCREr|=b_%B4kT79A=I?U4$W>wPs(>Td4L;2iI@jd*e@LgOApb@?$@~; zmhzOVo|*bN#>H>vuj7|nJ20eu%Lz}>k*;jHXQ%EPqq;38cZ9F}?(~2Y1zDk`NNLnn zzAMAu_q}js!P?in%$MgghRP;1HU$s3i<>05chghEQ!A|oe=Ph?9P#*b90 z4s!TA=<~<<{aVz9x#kB>+QWxKmoTz~ z#G72GYpPiaCP^pgaq z8-xmudILBLhREbN0!x^fD$1Q;FM2F2rIHKB9v(0stCvH_@nb9mCN;kfW3oLI>Z?8a zw3hJm8|-~iRcc~%Fd7&v3JVS+gB@W_!ki@xk_B|zqI(@TWsIt#yI`70s;VTOrRaxbC&>du6uk=v?YV$wEGRka`nBLoGZ5S{7jiv>*8}<@_uHFVbtkLgJ`(s9QStLKGC=*b1RFt zh4q{P#9iia#jBdFQVZuj2mz&kzOtykqE6~6u`8cY>v4*un_?E&P$Z0EI-|v^qzn-r zj^Xn-0<>Ztdeb8FBB(BJ4J8yB*z*$oBRVrkvx&jjCrxw#?8JYgg)BVrdrmESgplYD`+xOFkf|F4$x1Dd= z+wC$6BiR;e2hDE+kYGngS1)#Ez}M)zGH3K4UlCtXUtwR>ctG2xRr}n^1ge%?k~s?# zh};Bnntg7(yk@8P-kt^iV}*;-ji|wf_SeM5XF6!zr-s%C`^M%)e&`t+-||IhNCZCq zl_@a(weTp3*h-AtIAT8pvxU5~Mv@)C|A-|*E{?8YT#_DPgk%mU^1=vMlB7<;7G&UG zg&u8+%O0mPUtl*aNsm?(-b@dG)!`m}fmjG9R zV*$+Y&7}j#;2PDEdBg|zoJ*9xwM~7c$J=8maR_xY8XpTD#^H4}OCqvRI)G9hp zeXC`JEj&^e6FfcR&LoK48(c-ck(C%Rxm)}CM-|L1tZ@IGa-7j6*a*pcaRX(eh=Cw^ zn-}IG_SFxtg?EfcD%UqvxE7dvsXMhHYSd*vQFHRf+;#4QGi$zXYZeLVhMYnEEQ#%H zPUzV>y`z|s9P512rRg5N{IJB~&=q$!j>AfEUz@%w)<=yw>*45AdJuBpI&de-wB01y zP$xZ&bh})AV8VnIL;rbI9HH-IvZSe&@qqP8CTcqk528!M11FxeVLMR6l3$4K8e&}C zKRrh4E(eFP35_PQM)xaDVvW4dE;=h}#_`k= zV0!H=(_tR>v!2Sfw7))5@P<`e%u{u1TB>R-kvqX(rHLFgylYlrh8TNiG~;ICqsvNl z_(T4QuA2U<{geoj_yrL(p43+1G&BTP<&vo6hn%~~H9IX5mul12f)6BS)%=F!sRt(Vl}R_}T?{B0^b!h@v1Wl{ zaVV~*Bo}WHsyqeyHm-z{|`s6W}~qsj?^!IR>K07o|7Dcaib!$uOQGDQp*CfultEKBIjEMi zS>~X^M8J9Yan7h9SNY7@pNhQMO=ipuxtIL~>!;aV9+saspJV;MFalYt{C%$_M&sR} z_oUX-fYD%TgLR>2D6Y^xD6-ILp-rI%Wa}u@C^`IKiD>5H07Q?_OcZi5k-<RxMmJN6NXb;j%Hjfx%)-Xc*eJ{pK&LN$83=--HH^<_yaQj2(n7VAfPDNK2}9{H z?!ek$0-)l6f~F#nrB^XwtOyid2L|Rv6=bX_n7CKDA>Iod;uI|fm~o&@o428YxSV2N zy6PIjzzD~t2e7^=W5?6s@R;Bezlkctww7*&3$cl_&!p;6t>U)c-LH(%cY<2Mi*{JM zMi>&L{WC4&&*HwZl;$`6)iRwJC6uoM??5-EHL&>?-(ttSkc)~_h<#@j4!FI_=~g2` zXZKI?KLoCwdPBK^TNsaeKigZWHbG86pqr}uEs>6x+F@nzLdXce{4R9R`u51)eTFU#RC z)F^K-cm9x-Adc4nZ?hPoc7z}+R^FH^zx;tIj3r#JA>(E55NQkdTfgULnOZ9w{jMa) z3Z^xURfCAwa0xn$&Z@Ku^n}`!nLRqDx0Mkt6WG0VSp5b20bTa)TMI0({};E3Rn<9^ znZ4?o^aP?6(l>2I6h^_rze<#=tmtFqBGYqAxe9YvK{ zDb^C5kP!dq11dfj)oJ$RCf?)_?jXBX6-N8J<}C4{p_g;;`MF}uZ^(CPdE-bR8U^lS z`F0KyV9MfuV)R^?y_F$Ybmf%CSn4|oUao5*_GC$H+cgKH(Dr>ox6r!+B9tC6<5Z*H zfe?K9Syn~AkmGf;L>`pw4U6TIb?OgZVYhigl6XV{E%~85))<9-NDksgR61lL3 zDUEy7qmfrYx*_L%IXL>)v`q~2dhO9?#gO_}BanYGQ_`}Z;`aY>0I^f~6xu2w{gHRr zvTxVjgh5NVRG(@ytBhhmG7sW4tr!;_FYkwqLUEd8dhhE+94DpCK^IX->hW zLF92ceez>T-Z1Fv`y4(UP=Pjsp8Bi?-=GypYsitrv>}EnV&-Er5|G8!BYcEoCQ_KG zhdoh{P0^-Jw1x1if# z;BB3}0?yod!}|`*L)xj5ZY12jx~#g&L}ne0e&C7p8oJUhF+JAS+5BodtE<_8?tKl| zB|Oe`OC0nOLN#co+<3}o8ea8@9|x@M!wo?cE`OilcDT)Q3+J^uaw)&Lbep?>)F7@x zNl@MK|FycVO4V(`q0_!Z2dT$2&ZFOMcSoo5MRZ1e>epa_rk(w3k1W7+=mrWPcR61S zbMuES339s+FVx7?5eYDjmu-gm>H!W%HMaZgWzs(5C00A{_*JGj3E|nmacl!_9S1H_ zp)CM2Mrt@DgoPI2yXw;8_V2ptMcdeZl|s~*#mb=G98N-SvBZAdvWaFvb@B+%MergE zOm65Hur$UEnHRxlEb?3d3G3U(6>wo63rW=p?l5JTd|`U5O}9G=;jzs}vTq}f0+b~X zQnP}@T;%qd#Hg4>{7qnaLuikT@~ZJvAAl-pRpdo3A+syB`lxbA>k${YHR-jJBjqmy z_F{ z>yhS1k^VsYk?;cTV|ia9CMV1zqWcE(qr?Ck7Sy;JQ7Rm`|5qX)sriPF&F6I&_Frdc z(Ef42{U?Cq?=vk`16mjD?Ar%o*VNT4LWO5h5oN{H&0!SNXfQT%A(xF`1rk_FL?2L_b*@L1%AGu#tYoPz{dCee8GwDyMKX<5BQlA zeTddr`hgdS>VX!>^8N;>^a4eogm@qU1sGTY3eqqgsOa`I zgqR=}V7;K(NK}vu1vJfAQ4oBnE^rdi5}3+KhSW@J_;tv>zt+~XF3=hdb{#Ad{x>K) z%PV%7atH`JuEmfP`!;<+nw_oDw9dv7`vVNr!6{+dfhLko0k{%0Uqow1EW8vurY$DC zFrEZ zkKs^4_GHX6Ga~=M9y$Jq9$8=vQuG~%0ppGU@WQZ5cgU)a13HX9x92n@a0RzA;_~Uc{*Z zbdJuM^%YUn*^R~FMPyG=}eXb!LqS*;uH2115v-J5Aj9OLf9QZWV z^p#jEEWTUSMgU2&ZI|SP+R+wQ7^g)zRuVEy>l!wbcc4EmRpzFv{)+4SkbrB(0*ef} zkmVpZd6B@CV_?4cHAxEM>nd|$sRUM-(fD^M?no~-k)Oj2B^=4!yhh)0jWKIP*pW6- zaIxg3WMx!PQLKKQ4-{+&5H=5wAFEXQ5|fa@Z+@DINitTD{_uz~X3j{NMxdI>W9?Cq zMozYnBDBX=T1tfH5YAVEnk#hVI?KYek_!gR-f^bmdX9rAeeF;11nYxpelDBYl#jm%A)~Q zh!WebpDfR67E`YJQ7a|VhR@A1K`OL_=ED_mY?y@|&uk1yCKglry8^B);rgSb2FFD-y2`b(W}RKXP_HAA6C0-ISa-(;E8H#m zU0zW_*R6mwL+pMp8!_Lxnm@AB~U8!!}igM1Xq2IP0=_zfM61%O+5-IEL2-!ePJkG zJHyVpTFH*K(BS<|rGmc5(1WNMvz#pAW#gfm44pbNVrs-SOeqlfX`4zOsl^#a!aBF!Z@ppTat}5g@Kp1p@Ehm7?HPWUf;&a8(k`M9FNo#i%>X2&bAfp7w)L2Cx0Bf zr;1LpNtoNz!v7uMD$9+nG3w{{Wv(mUU*4*}gm`IHo1wpr_7q)8K=y(=vy5r07-UgTCl5 z&NOvzI_v;fYz01p4=D9mBFzAhYJ5>oWAtx5k1C!~&7}2Y1OZX)8}CkC(N{2IkphcG zzc6QBKR@GgqD|9@Uem}Jr#r{L&hF^5I~RH*vq_rQrB~$N6Q!=Qh1$c_Hi$EKMDYDV z>YKcb(L_d@xpk16*GGl-m_mlghMPB`?aMwhO8Ve?XJ^+(f#9BHjUMPk&ZW)9k!HSg zaB++l-SSE3KlhQlN64)jOj>BKB4te^;w|<#rG2{JR`Vy?U|%uN`w6ZhZ8)h4K7+BcXJG>Nlc1|h5GBU z7t0FyWPNtyvpT3&mSd48dY`zSbqUU1*KThGzStVY9>I?>kLkpz#bHY*3L_ukW0Ld2 znM@Bs`4X^DzG(`NjKQ4xsWQzls_wlEHz+z7MA#r(8+|{>L$}&&JCr4_&Cw+wjW2@5 z>RKIji7;|uY&=3S`(B%Vk;go}ssK6V+n_FER55$*UX!Y1A70q8V*q!%!7`y}PeX#( zJS9m^AXWmM>2m-ESac%ZD{q_+9=KemjXdd{BqKSzkltIIi@bmTnp$?ux^A4|eF`x8 zxkr;Iu){;Fq3ZoO6epW9e{nOhIEk8JI$9}%bt7IWBYO43g34nCKk?E!pUKNjfyiNX zJUFh6k+c(7_{4z(WisPs9$e=dkfXd5y{vJ8A1l2p=M5!7dGKvAbb8S~=jGkxxyLbB zO!l#o9EQ*N`@(zV;K4xG48uMm5TEgtOT#eklvvC#{S-^~DNGFoozgq@R0BB5k#h5q z+$Y!=xEB8(Oc^%?h*t24Dd+zyru@%jwSUEwnf^-?C{BIOOQG>CX>2}}u2g-C&L)h7 z{e%aD(Y{A9P~T8E(W)d#Wyp&DrtVVKeF3@ojpy_#h(R|S=5uqj&BF40|MvTy{orbqVBr9)V~7Pj5=(eur@67pk|>G8sL9ZYC!oXZ`!dHE~Ems7A8$>?5_+KpsLfFW8{p4tq@%^DC;FdbKN>T4|*oxl3JSn}M{_6-6~i-)E~dahY?6 z*xD4e+7MXHSxV#^SpJx5qg2JVTk;xEzyeUc-DO&9XVYZavD*@_nXb zCq~4)dp<@Z>#iY?57ZtV$P3aA)uGmZ557&lF9wPL+hNcj4UUTpL;+#=Hczq-0>Xd_ zEC>t->(=e30n0&~XV^~(1OW*F1%P!+_csJ0gA{{~g5{tCtwG{pJZ1ZBz`E7@FTuL? z`*Fa!mHM;6y0!Xsz`E7?x4^pf`gy>*mq68kG2j;mU(qdtM?MAt!|qf_I+BB&d% z5=9dvpC%|3s18^{@%^Tsw(s>43uX|KFQW;uXZ8X<#DL{L1B3(wFurFW2*7=qy$}r1 zfI-1g8hxcVOTfCoOk-J=SqIxA?Z<}B*0MIY0+*@Yw?L@_T19_nRfD+JSzpla3 zBt5~>iM|A{oWj0_1j&jb*_m9pUPv_BNy85lL$RV#I#^*N0I+s3A%WSsMl~yHUB(R=w&OnF(`UY@Wr6r#c4iwk>W&%w0Zz zw{#TX`s|C>LQxb4tL9cN;ceq9|1|0y?4xbeu{Nodml-SFRv0)vRwE-6XVt`se>Q3h zhKyBIkljR!7@Im0th|AQTWg#;h@00oxV4Zv*E&0p3`@fzwK~YwS3Nxd)XKJJtP75f zWNo(dO?(^zjls!vu_zB&=x0;#!&q?NaG#JMCpTjBC+Dl->n01&E50juL>i5elt6_a znfWas4$B@H-NZF*b8~%0QtFu4^<5RXp-&79GOjesuuE^7WiZfYl=4BAlJ&jrR{^7W1H6W-!|akw`ynR z`LR4X(Icu_?PONh}sr4D1-^pHG%reBdK~S;8eRrsp?bIlEz{Pg#m{ zO~<)H%vmC3U0blpcD~ia?1(dk2G^c^Aw* zmuF^uVOHZ{yaI1^3qFe?{;Q*ge|PiK{U6QodeTJlKtM6`$4<{jW2mmou;rjcNL`?jL|cU1aX^aQ?@8^@;~XY9>n z_DUf3jX{3;N8hAZ=tDB}%uNxzR1UET&n4_@|nWR3Ek? zgo#SAw5PyaV%@E{PD6KpqCwT%=fAG_B5Tm73A0hb72jhflr6L}=bwdsW6NGS!MM=E zYvxY`2Y21k5l|l6D-Q^1Y*098jZt(I`>TrRo%a+X31b;`<%y^jDx@1LQwA|nqeK5O zNdYLaai73%2phf5Yss`0f&8A3Na_9X#VRLBJTJP=7Nxw)2RIh|S{mB=fsohr~Ni+W%fbE;IJJRWv| z8DA#uPVR?MwG_%WN`QstV_r|9I<3?6fSlfC-lRH#AXvdSg%Li0j#9WOZTx`gqdF5<}5gh@nmE0u0}}CD`QMa2aRz5-)6R?Gvfkw6NTXpJ}EK zD6vUi35sIL0^uPl(yw~X920+}>ax+hylIV@(e}02=@(1H<(<9#j3~B8T;*nNbrKeF zrBZ8Hixd-kQUd-|h*iW4i<2m^VMkMw>^4lA+J{>*_J~Fo4;;UKO9HLWwPb8{54EZT z&U4CC<~82+8Xf&<#j>Mg@3~9QWm@tG$hKR)%wB$1$I(Bb)DP*>O>7+nki>yq=jNUrNK?<{Q`mB0yq?^1)%#O+dC#$v&}ZfGrwr%wmuUWuSJqD> z!{RKrGpl?*ea2>*fHMD#oGH%@9y%7Dd($!xJ@VR_ z1t(?&3A`0vgG>YyWv;T3gdeoO?9#riWlllmN?~0z7>6J@3d4Uv69oDAmR558Zeif9 zE(l%)BoU;Aq_q9Q!g;QB#R=^Ln|o74_tjYm_sMH}P=&BT?TZu=`G?^KZ}MkoovUCN+#=#xziQFU~xOW=}p<) zpsbT&VS8&w<``wik4r}C`P(WP^ts`)(v05;?(Upa0?h{O0De~QnB59QOJ}vIjk92U z*QRz-puG7gtNAsqH>)`vi;c=OcI?K5KJ{8jMpG*-h~9M0YKEJOW-ZfYhE}>U{Zhu( z&eVw+m3fbvY(KZTek~Ku*}hlo#`yFoTC-4mo0PpIQ+91BwC3D&F}BMI?lxWh@{*;A zl*|$J20Nrg3+v{3Y3x#mw9L_`QwIFa%FoX)b`MyY9C4cw+6rh#Pt#~!{;%OXPhdG` zi5gSxoznK1LD}22(CdeZGtSlBqWaVf>DlJ{r>M}HH;Edv{Xt{SC8I5>>1ozW%4XV0 zaHJ}2qQ8SQEt)+0wAc-EzHt^Yg!e4C z5E2(|nBX1*W?MYlcS-s%VswSpo%ptw7KpA)txkk+bnLfnO&l>4+PWT9zCR>4O4+JFCUH$PQc21K;Wdl}C35*d$Na!9m(?dX35L%+M77h%09*BYP_)Z6zmRB;qsU@`^mt{NHg*s-Z6eCIle**v z76gpB{;HKiN5#-#Kh8%VjigwW`Z8aNV>~ud*x{Kcx*zG!@_hDwKfXuNuj$MO+blSeVB3=;iCBQbpUV@*)q!Od*|FIF=!lI$_{zsE&RAlC_LcjW zUqfLK&2sT~gYY8(*ZVI0M5!U$uW^0UGV0}}i!7bbt=yW>yEBaEoj zzM>gS-cBR134P8bKNcH6t04sEz3NMRzSVz?h{=l!AVg`y?l6$OeDvCc}& z9*b=NK;}&8J90KZ=bYjcdA&+Bay+SsRm2*;SJLfe(qH5Mm2uwLt|(Gqsu&_E1|PI3 zOYQx-l9>fE15fIL!AI&A?=E#gyfEfoy`+16R$c~>_wfV#>m?ccXU(kye`g6axN?GeBR{uH3xBMNo{z|mjX90LF zv=Dzb8Xer8YySYe1f{Y8K>&|PNLePT15|7tn#Prm-|o$zMV%UT8@tx0d2PGu9v!=DBb~QTPm;bK4x;ss@_hdQmfN08ulv)PY>wvPbOE2A zSYI+6XcT`O{U+rpt zk|qt*e|f0>0Zi_x{z6pylQ#KL^s}$({g(Pe_2fhOSD%_vK~%A-QfX9Y)&IlTJ4RR5 zuWR0^*r?dHor<+$+qP}nwry5y+jhmalZraeKBxPAJ>O@YtH|?uj^N) z0*IzMQ>qt3m9F4WM=dQYP)9vio-2$vSGKRD>Mn}dP_pl!>Mn@jQnpW}>aK|BP_~by z@~Mi*rRXmSTR@r14_iRpRT?A%I8hli5H(Zm!2+_%!x~U_6@*iXHY08*`YXe1070lo z^1@0;i~uXu!Cw)p`C-`+9I8milzUzPt-LT8)LSrsR#6x|)t&)Bs~}7g)jpkwPIVA9 zqE!J&m2%Glpj8-ViE3Xv@JIn7~v*{gh0KA1mLa+8$qd8 zMT(_1$R)~=8}tF-$O&s9eMDf$4ZcOZ1QYp`hv`!8fdIV9!gQ(k*dt!5iF``KwyF0J zB3=rKK8wQmsP~8gUR7Z|6nnK1FTzN^ii6vzw+MiDMWkG+J>7_x7NXC>uo;wF-68OZ zXiB!4)gXfg)IN(RO;8^tq$-YsIs*QW?7i~Jt6;|;1KoQF(9ZblVQ`#Tg09&r#VhXi9@?^j2{jz<6= z(mSsI1|*r89ZiQ|;QoseT#TtI@;i=y1^EptR@P`KV%4G^m~&l%w#*GS9M)Pb(n~#7 z7127?69{O6=9CZM1|h)LqAtXYydHs7P13<~tp)J~3M*@Jf|VnVpqAt9Bd(aXR&1`neh4@ASjt$w6Ik&J6jmlbo zo763O$1tD=@g3D)6>0J3A`%PYBr%6bAoD6NR@RWJ`Hq3F&`u=FNx^6PDq(;sQrHn$ zMkYN;9+hsDf2bK=!hKv;=r>r?!@Uc9>w*-{;~SP>bw~=D$htcGXR&vt)bhk+#RAb_ z1&Leo2B!}ObbC<`;*B1dPe(}hov?2MD@(R8d!{>pf^bI~JZ~Trh z+}6`Ms3#;Ifm_@TVSp$k9;sWDo}XUa4rag#luuBvsrkvXt=SFW2_|5h@J29TpQSa8 z8cVCcW`6cnm+S?vj|&tZ@g2{<5_AaE5Na5ShS1K;4zoisaKc;%u-4)r}2HvqM z3*{qnLkYYBX@WSptjFG=8pyTUF;wU8tpMxorNNjr^{ED{uOkPGq@w{VUopIjkH~?T zgp{QOyYcE4@V$-#AMO>*jy8~eK?^z!IgD6E7t&GYfu4MIBzRnFhtQ`2%qQSSAcjb~ zDq{!V2LM$?))KPA?Rx}eNAe2aVGG!RazWk{xIqozg1RDfi_@d;+XsDv%pq8nWe~ne zn$|tMK7H>1t-Xii{Nn9Hfb^O4t-Q~bJ1qt6*}*@3PgVMh+6MuOf4`0Np0k4h>PzyH zIL$}6+k^BT_@#0DMY8*a=q1wlMFiPP2)2KHE=VARa_16|doGxtAV``l0RYA7Q9&Ov zLuV-r!ggj1(JhXwe>VxxbdvA4T>?byWtIB z=re3j6AP5xLpbm6Jw!@;jdikT;ZsIed`WLl8+JV@2*@ptJOyPIGa}=YMNfX8X-^xP zSst>wS{lOXmO*cN7t~(7hu3Dgp+7Ugi#;{q{Ys}pyP-cZAoNv2k9-wsR|}vfPML3= zcrkD12zfr~@1vg`a=?043)p`l)5eh{B1g;`m0cNv?p8#nE?*z@bgqw(sDxUM11FD9 zU%sPlHzU-5xhAqZFGy~+Cd*SffAOP}Ig|-NaGsN)Wt)w|e&K3g6|?p%KimH&Gx@Vy z?&9d0<>`HV+UWj@r2;7jE)osW6Spd$o8H%+?{Q{4z!_Cy&y5U8H&U?QGdtOTbG9aP zmxt(>j%_}TwKw}*PYvQc+!LHI_ny!9C^t#=%q^Z^`x-hG(IH;D+TCI>X6|cG_rNp` zY=1s$Zk9$0y?beQj^-{7wS+(Y?3;M6Y=438kwkC2AFO6w6I@*K!y)UN?cAWaJLA)6twWWUv73!~{d@TrILdt@G;+KEg z_6)z$6i>Q<`O@Z5b|g%9b1^A?bujCGb3ilnK2ws{74_3s$o4LL}*mbmfBBJHf|@IJ2IYg5FNigP7k#Wz^xsnCf%CBg>5z5tz$<7e7`g@ zT03-S?=Pz8#13tzm#v>KgQ=WvH@|QW-smzN>HOjP+xM5x>c;+-KQ?iM2{A;vax#}& zKiu*l@I2H};bs)lLBb!m3rIn8-sBsK1%r98;%`#`Rz7PCPLHW9?uU1tEGDY4xhI!2 z`vn|*m!&%1$guYcH z=wWfDuKP8jJGaIv5QBOo& zOdGlLDx~y08@WFpI&aByc|?(TQgFo^CXqBn*PK{FQ@E=*d38ez)r!4LCVLRO3KQNt z7bZC}WnZ&|4praVy}F|F5=XG@L%j*P6A!O$9tZpHoT|9tSTF8gz2~ysMaK6%GVjh8 zm^8faOwiiM3+0p1q_7g>HLnt2bu`!jmoYNO#(1oQI$Fu9lr2>?&mjRw_!(xV#7oF? zf$Ovr+gA+3$zLa%#Id+7>^@zch$QnWr1lhcPKe&6!jk9iVpoE+D*RDGnOXrg4NCvC=ojXnz)uyA zcL8lsEFV%IFpoF>JySjc88YT65%+#a95>U6BX@oi-^gYgRW78DQZ6RJL5rQvKAD#e zK9hD>kVO_cWnaEFub)ZEDh1dm3Rz|)tSoLD=!%DYilKBj2zz=D*y7~_hvMu$G`<;i z!3sQ8R!8r4Z&U5SNq=o)dK{QI@Xc1^tpE={hIaIz?em-1cbsqy-dr{-Jkq+O$mPHL zGhnJ@{OgWkr3E(sf=oRmBN1^M5$3fkG6f-54p%X`lIzd;eLCfFF?j8-a`NsW{XBnmZJwDOJV&MZ zRS%)zw#{QGWZ@^8>xEOV`|5ddD!PHk4v0hll$_v&!I=zncSt;E7=ag zF9Ry3$(SK#?giQV_opN5R+}1Vg>Q>XEE-BpY`n3S33FO50@^amJ^m!XkmMh|Pq8{; z)>*v!(gqf^KP{u4udg&)_l9NBusgL^9hggMo33)cwkRz*w|~@#4X#|?hqO#qUq4)h z+4R|*VcvLL$S$rJ+Dq|h6ytrA{lRy#6>Qe8va zTa0FgQ$*@a*iv>^WYmrvoLMz=pwBKCb=+QxCI|OExGM*`rip2MiDO4poBmi-^=Sm)<=A?&mBhOui1`1{P;Omm7|HQibscOySMyTz<#j?af7Yjwb;jyPiy zw}t1kwaB#-PK#GFLX@SUqb9yKdc(iYrE>)y(z7Gmm zt%i4FV1H)tCdgIJ$TBG|6rc>pnH_N-3KoOTmTeFTXSt-QrJJQZQv1)(H(MCqpGjoN zM#3ztM2JY~4>VS^SJ9W%p{-SswPjRkbc$E^MI4}w@F?g%#xmyHZ4D<+Nk^-hT}Lzr zd(M(iS=aq2G%PCM*?|Ul1j=u!D3Wwd7ik)n0@L7~xxb7ZVJ1{0B?Yb-%QvfE51HZQ z!)fIZl|OAO=UBp{{y8&!c73A_t2)n>ll1Lu_PCP8$X zH?bm%7VA3%^MwU7NzjOpAVz@v!1$=-_QrEW)X_6MP(@_eqs=nLmJbU>4BNftcvZ`o zP@b%72VZpg=`=;1-pMla2HF-JM2$Kq&uZ&@C`j3sGy~^UUSZ7Rt3(XE&2sk^)q|UW zTlg^gz#}%e3FUlSSQzRV1V$e5&fy`5;}_&`;I06;6II)BY%hO1X?c6LLq90+?pJ$R ze@iH<#JZKm)QdUaRm0KRETuvmieKNUs)HusnxYmXl&1ed<|f}kXyO^uGzIm-kPdCekA3jvHtN!t^N^9VpB9m+(Dw5tFaL6h1N}v*enQn9#AIA*BUXWCg1RWo@xB+KhFV z5FjDTfdRu>E$H~%PE;w~vF^c>+#1}ZQ#)ZuW-Sbpu%PrLQb^cX1AF2uA3^r$6emYe z1w$S4tye(wMX^c$w!5%g%7m!p&Ejc=$)AlVgsRkQq%l+#LoL z73r(pYnbH57qIQ_t_fj^%2pwC@kp1ka3Pw;O;&*;Kfd$58;@T_33qviLur1|06&X$ zyLz(x8GP`iLRvj~ta1i;Fpl*~$6w#2&k^hs@*$9$zt{V4E1whpiIeSnr_+u;cv~GA9^w5pT?LhXHxgmAUWy zgaS_mlZNANL$bz~3`^grPqE`i?L~@jHOx~ncV{ugl#Z*gt;oQ6YEmhXiO7ahVF>ZN zI=AbbgNELVj5t1Pzo*?5mbeW~6d_--PSbu)ek*oVzVsp@S=KS=fD>?N0AUoZywoTI zE>t>s-b9x*NQtQvnPdbXf&0lvYi`xE25e8hS`qtNGf=EFA0B`4*tv!_MTnzIq2B67 zw%b=92dykr#HY%#f2hh|YbmCKLHW5L!@V})Sk@%Rl@4Y)bn)NmM@gjT<-cO2=V@>i zqrW}ZZT%+=>|)dr0y(e~iE!t}>mwGf=E^+&!eBmTsZ>iXPjOj!Xs`dAvR&RV#w#rC z0;V6W+5=QEJ)h!id4;YlT=8TMpWkOoy82VT>aH>ZY@VzOm3&vx-8TDvaeGX}e>u@u z2SwmeI-xl&h*`uT0oD)fc|4sE-rVBxcA*M>mYbP|!Xp zlprVa$fgKSmS(n-&YYKPb=sYBw|Le^Zj!h|jd!sF?*3#-C>q_fQ~iFnQ!O_#eR7Hy z?xYqlXrLB61>TCBJB1GadBLbo3v)shSsv%5qeL`jiJr0yaVj3<#7M7FQ#a!kOhcw{ zS4TC1W$_2?DPNlEm|m;7wPPbmyh)B&{5C)gvVoRp_pY_j+G!=??`ci3aG(QbIAYPc znm&seXC@XL8Ivj!+#Ht~%`EC>Jz-ksFG$$=)oEBkz(XGe8Ts$6!&vtXIpRy4PEm5S zjLY5+=~{ziluoKC*X(f#-QLNO*?#xehtB9}6J8&jz1~DSw19NkkZqrrIq%0eUO59Y{+i9a6r^`a zO`MW)ji;JQ?i@8Vztzp0U~-XvkPk*xn=q|U;ZT(Nzup(IcGnW>GTgAL#RgJ(m_P0F zM6ZLICKT^6}|KC5!LzB!^gf?! zxa&&#fr{cYq93W_L&fmPQ?=v|hv74V(C1+K=(CD$+UKZkK+kt?`Qm1f9Vc!ku5i0KhIeg{M9G$??`5T5agy4V$8g_|-ZTP7{ z!c;PAs=%eV$(}6}+E^`32#gD@qD;;v2`_+BWQ{P*Xca-AO+H2&t5_smpM4x+HEAR? zZ$*7&ri9m1IJn53od3rjv>QV9KC+pxWbP=&@)#vs=I{}@Kk(4%j<#0nh&B3^Nv$eZ zX0EJKPHi(ih95il>H}fx{Mt;Gy$U0!s=m2mmbB0SFND7~L*kK5ZOcqOeM2F5a)<*W z@Z0V=M83IS)1|-p+m4TreObwtl6gyd2-T%al2ylQ_iXoIH{=Fvz40V{_%**R>pxY$Zd+~Al1!dBSSvfS*D zhz;yLt15Ydjtgv~Rv}k`_>Zz5%|~mX&X6_63lvNddy~ru*YTo#^l(#h&jZBvZre0GSfRRkud>x&$-)djDBrt5$QvYgP+YKC z8s74QFN0OsK&>w*)uwd($)3o@%^zA^mQA){fr-I->shDK9P2ACB`uLBmckZ1aL_B8 zQCBKj3$GBP)+l}AP+7I;4bvB}PN5HwTH;G|?RIU{vg~v# zCTs8lCg#++vS^n+tPF8{bg)BRXSHO-98u%iio$cm(jZd?!pHn88S{V}HOVYmq!wey z)*%;mSkgdnb>Xy+;F$oh?do^eO6Z&|_uJSHV60LiCIOdbimQmc{-4tRba3izbu zGoMAgl5&NUADiZ<3*Vn`dl%qXO~YCMCX@FPqLv^b=bsrFJUvl_KGf+mtj6=x>bD(z zocG1{Zxca8m+L8}d-WG*{~y|`=N;iXFw1*xjzCtndK1p}Zs;8jW$;7t9H5snr+MEs z{AzZ}=?TbM!X78+h|7Ol9#IeM+GN}~O*5Xv^NYQ>2k^b4f!qLj@xlADM9fA`K21FJ zdApG1got3Dha&9Va0&HAKj3WE!{{(H2HHUk9}Aw|ZQyWTr4y}PfQC!LZt0k#jHrL- zT*8nefvrb^>VZ>|f$e_-8`yosLq4A6qL>i~D5luZ$prIbZ$*l@ZCaZ0-8 z1danjS;9F5p`@IBzPw;A*b}Cc-(w!`d{V9$Zk(LLSAtPE792b6TH7ARF@@`DXdWu@ zLoLq!?GBc05*O?U3DXW4$>+dnnpG}ps4Q31nEeYmUZM;OHD?c_Ipj1$twDWV4VYtlu01s7O#T}S;;Ln8Q1Ed>zi9t^ zL7s5SovFIhsYC?3<^xDGdZHWR2n+Rq)UL-V3i<)XDUm~yyH^FaujzL!EyYP1+3UhA zDQAkO?BaX zngK@Oh1Ktn2dKrVvZ#jS#+~BEhlMF+a^|lNVtY2#G}C*V(y6rBnEp^c_CNv0H21VvBY1PH26Uc)-#45q+vV1 z7*n>bhRzxo6BeKrm_SQ8EiKMrGePzfE%cn;A@>hL;fE;dVj~A!k^lGxPKHe<{IS)$ z=$cd4{(db@urZwGjS6~6dsZBV>!r1 z-P#0YLqqe-;(5T(-pv{P8)j*ZU}|z+1?uS@yKPMxSF4YoCVnGEOKReJ89 zGfLN|1i_cOCk|u_U-#7IJkyum(Hjoz%83gWctM2HJ2Rx>+N><4_qkBgi1z& zZj|Ye3sLu$Hy6gsW)Idf!eUmZe z?^0{U&6&+?1;na&2Y>k=5agd#gBcUWHMNYA>g?Jsz%JLvshV3W2FFQGr>okP4PYBm zWm6KPt5g(vY1$2}tG`op)0$ny8KrbKIXv1LFh}rp15>b78`znF>(J!XsWVF8F061J zbao*U)*SOMiVNG1fG#SpLa!HR4}o*edvPA!juCMr-Ov8|xOL-qy94blxm*Hqt7NN9 zFrxTUDvXxn`H5Cu-oy72FS!^4`FH?5l?cu$MWy?-IUp?xVd^{|*D;kDx^RRi?>iSD zb9{u_(-fDG>q5)rDxqgAIx$V3P*4sE#jJYNJu z+Pih(>-ap~N_7aLHfKhfb5oI)+*zyGVpsh+QZ|&cDvufH+hU;ZSOEq#;>kSg{H6e-|U_ zZo(gGKKFj*^IwY$e9aAO!QbmVvoinhD?R@|QilJr$>;cQ-;#f5d;BP$XzL|V0(lV{ zs7hsO{9!wAFnh&*EzNlZGPzkSbcoW4G8r5@wKuviWgKFd=}5VH!}y18Y}Fc}@Uv1q z4W3V2rZdLJ@9&S7Tz*KMQ3jk5qVn9yH){RJP?%8aP`{uWejbr&2H6V;fTE+00;4=o$OYG+c0tDwQs(LQ`b7vxRkVf^{>bpF zq_WwMi0DW|v=bfjj;eGc(1R|dGO?2$64EjaBf;6;rxUXM&8NubB)I}R^1F36J*GN+ zsNnh(EYcFQ;Ub!uh5QX2^KU{r`(G~Y1}9JTOxjMuC$}#jR3TGhqF=3>{M%`3iIY_+ zFgYrAII@ylPYG=o9qo`dGOiPwaAIQ~31KXisb?Qko;Bw55Q`6UWy;Z~X3Ql+BVS4; zee~*HNp~9D1*rqj?KAdH{dH0s4`k)c$V~u>%g!U%Tktgmh$GlbMKAGZTKca3=oaH1 zH50RmG`5-7Az)YZ!C=^W5nPLEK%AphPM)``wgCdMzf0-&vKSVT7Wmo=x{Dbl0efbxIWp^ZHC#0ZK%wjrE}!A6IN_ew za+=ZFV`!lgoIYG_w51rm6_8wBlDfoWXuR%>>v2l!jH#$x(cylT(Qe#bn<7z8S*g16 z90qMCrufCAwf!_nv~q<$o>PhaC(0JWi)x@PFk*7nSugyw3*RkA?1atSn~QOHw*ESM zzR03bU2v(`R|*y(LuZP`)J89irNwL|0&y_58e$J~9-{{LaxPxtZlQMvq0y9m)}1sr zlC@si=}>W3+iNu>AvTm95!tRYz>OERU<)BgA)cc;CQ+T2bZ8YlYjtpq*6CRBj?Te9 zo95zx&td*`KRhZZUoT#s5OyIv z*j#KDY-eC{3f(8{R@H{T5<(1UYFvq*nxZa|3;kx-)F@(aU4Y~%KmM2MBr>B!!u z%PHPh(O;L#Ug~3+(Tn}O+$uBTo@X1SO8i^X1^*uvcVz!pP>lbl@qG&|CG@{{8vk2p=d2+A50vh+xk=kYOMsFp5AwvH z7LmR)J}C_&8A7of{4uz+%TO}K>N%r}2h}(EC2tU7pn-3kV}*Jcn)hOQy5)Xql07}Q zr~3=2E?7FoIOYwf62}q;;g<%^ISw|fl-{}!)Cy0M{eTddB3?z>En{p*EMW8T7N~(2 zc=Ks)kT%4j;W0+PY3&!rH!9ycq)9#t?TF$+WQ57(a@jaczwH&_ z*`ek#GVa#MoTd?SU)b_a*`VL&NVv%OWl+O@9=Aw1do{d?X`IKT>?#UxC5J3DLBG8) zYIEXme50)c)_~x#AyylM#833@-%M_VR{8XKQt!skYa7$|e)%H4u^~g4ogX9(_PpSG z>@{YI`9$C#-u*jY)e*nVlH1?YnWQw zV?HuorVkHyhc;S?!049^KV@09b97>vm{Kk5;>Bd-*D59%-!aN&euo6w zYKiBk>^$K2FAeoC@UMr6S<>I6n{WT(*X#6M2#$Ye$J2kz4%~ms4n^~CdYZAqx0#=@ z!+$%UILr6R^z)-+H8mDD@&7=obl8`}BEF@$4e(%4MwY*}UK5heFQE<3C6y1H8$`G% zjIzy)7!I1a^0YmYHZkSv`NIo@lUHb-obKPa8E3%*R8e!iYOY}X$(=Z2zNzCOsC7&A-60U(5+*l5!SfBd{mNj*m&ICD^ zd?g2euBb_H+yP&K@BGa^>rr&~;1lBPXGxAO-gYawcF3@<86RBdsNt;WEe+I*ImJUX z!LE)9!>F7BN|Wv@vwlS1J3Ha)6Es3nj;$1#t`#?6k;t6`-x0(t(VJgD)T~#P@x{@Vfdh(qlo1ws}b&9zU7tf z)Yl&av0OlvY6ldKo%?CKX@_b1cE65(p+!*SZHEN&VrQQ;QR3P$L@1zz;7I9h_OXOu zR*lai4G0eLAs_cg23L44?a)9^tp<;Y@%6)6ORjI}B zs!|Q3b1kfC*GkRM&*llel?0{GxtK{OusN$?bl774#sb>%F}ni#C*eQ~5PBXa*rS#* zfJTIx&QZrDTe?w!L(Yj3=2}?ijKG8$QU>ntTy=HhwD(-a^}kxv^ivE^2@cx%Wv|2o zn>`Z0a*CXaQfH-m((?m#f`f@fTUiXxYNpHeVP&4LA~@5g|b4Wkj%|>UV%C1sv6`_TntbDA{qXoe=W)Xj(ORCti6Bd zK>uSl^nWDq`i_p@L-T((=5Gp*1PCJvPfKn3>4wHu*ogsJb!3dTW)P8+vX`8FZhA>V zf{6qf@tNvc@=pJE5e`l^LI(J6CT2O#bd7!d{d@ss7|f4s4~z-)hR}w9P)6BZln)d= zdHTCR>S^F_bjH{UmCDV89%NAa#cwAXk0OyKDQMit)i@&F--J}psm(z6nkCmBahx@q z0X15<3=sW}B9K1f2WWNKeQ%!`yRXL8=;Md;+fm}XC#hEl7!GM2Jf&SWqY zXrG2LMZ_$woYcfa&XSur3#yNk+57FTcSyg>j9Lk(@Ji`f!e1v$x-5kPs{{n?kg$gC9n#< z$?8sKf^Px(9;S_yT`@AK16I^57JTQgRKHTX$%^Ko6*p3D6ItRkn3TxFons1UpcZw~ zaw$43nWV-zmOdXxnlW<#;;N%+;Md8JZy-qLRoEFygG}PeL>?Ok zs?J6wm->qv%Jod89`3A0#c&7C#!aYn*iIb~!rGt`uP2sQ? zIwru_A|Z`;-HYj*Cvl^)<+j220F|hmsXERO_Z*h^Q={Q?s&m$INsgtQ9HE8`)hy1^ zFeaP$5xdMz=toK3)z@VAK-5i);2eePgTBMzW!t2ujaYhy`xD?pNv$_voxc$Zw?C&_ z-WjfDIi=CkN}ZwDMR6<#pkmYbNCT7$2pkd&TkQ;j)1+IyIkt6QL&m6et_I@-IJm76d=N^qhmpQTig z3z{?sAzrD*%vAUGw@FTBV!vw zca?AHjJd7N|E{dyZ;a=+dPW;!T}Cs6t4x`$fVi0;3UI3s79K!CRxC`N_Sc1zXIZF( z*_m$eFO1I*-CpV&Ib{5(ZnM2aU87CY2(!2y?kaE1HwTv|udml{jwg2t%jUeD=0HIR z7uJ;k? zQ3O4nqF?-Ecgd6WQAyHPReP0k*e6YhgLo z;~8t}z1PsP7O+Cu?5V-zZM56YY>cnD037DdStui+G=xf@9eZjunC!4zQw%Okd6u_a zJ5zjEnD3jdK3oYHT;j84s*L_}s1XZmDA_67gHw@2r$0`f51CHH!Ifzw=UD1pPH$(1 z5b?{<3D-vjDTxqwlPMBq?9X37}O8}bx9 zk2&z(ZxgM1&!3?uN+nOnx;ZG0ild>3zATQEnkhK+Xl#eLEPUWISdB7ZS6R1LwU~O? zsIVeZRFq`YyQX(?{OVINW(%gk!cYi4A#M9R!`-nldAr4uUi2FXpA)WR+eMr=a z@0}Qz&}!g>{GSOjbpS!~kH5V5f1IpL{}#mmctmS%^nXq%yuYIg%80?EZG~y6{`}T1 zWrWUnD@6h+yO)59@Q1UIZEN!T^@Zq7r5h36U;{pBw?1;fiID8f>1%iGl)Ja52Y6i! zSd4fKWsG?YW(?B^&PXRj2rokR$z!iPg&yP(Fd2hfFO38&Zt@xCfWl!OVT~{%j5b0{ ze@N7($YaYO!`+seqsr-Dk;g3kMu*R>9z!Xb;F5r=DGC*hHrkMPlegJ6OFEK)!J4_$ ziX4sIq`8rPviTa)_%JY`FaP;F2J4XdoCpT87FFi*=!5QYu~f6BJ?x*#4rUKJ4ToV# zK2xR`FRvOQeX&Ra(i)@&&Tng7@09CGTuVuPE@>RpL@l{z6rP)$AU!|rsbc9cO==To z!SWgDUiKk)2hP60bo1SA095 z`zN-#v_Fp^Jymt$JcFLYt~w^e<=5|n@($Efd^+?(7!*5F@Mj&rLY<=+LgQWk39G32 z(xB^iSoQv6SmD3JDkUxikWmEa3fSozni+3tXgY2TqxwA8t~!(`nuC{QvzTKahu0s3 zR1294x7m;-hLYA1w1C%*(h)>_Jo2P=`4<_v{#B7KVHLkvYdF5kzRntgxoztK&`)Il zyml_JS$W*O;di&6tXLB@csm1y; z-sRyBb6tDwqY*iDCehiYu1$NL%3j&z=ITh!TU85V@9OR8>iD=z>!UrZs5cGZloGM?}S?Wv#H*|sRB{u zaaqYQTo+vjg;&#@U9AiA){`*1%uZ4{d`h57c}%by_;opIBl|@r4U5;zZ92)YvVB*L z-cS~WTH3eHqXRo=&2=1F4hrpTm*9ZoB5LWY#IfL-T+ubGv<_%=E@0qV{`&x_1fNh{B z^0_`;3BidapkzjlJ5fHvY4iZpTy4rH+?y-Whtt$5VIMMcw|#GowIpk8#!pyMUICKM&)=@Oo(hOKgSRq)}1r8L@CkY;1ayd8_UIx0?8$8XHBZ#i}WbD)`1rM>G- zA@gJ1$c;lIAxAOrdFUpwfDSC5T+n;%qf6rhS8K66rq`y`Cs@8+Mf(N(J8r9?6$PJ# zrN&GB<`{eTaRC^~Wu}mF9K}>~dd`FmgAXC3u za){|U7@5DlCzo>Y$2OHzv_p0$_lCYKPYX~K7rT=4X?Bprw8ef`{fq{{b*Jz38tOmd zxW%?crIGLW28w~_P(bREc$16-Te#>VCajPz&9 zoy}ce?Jy}&jPP7?b&@3srB2G$8KNWW?>kB?h|E&r1N~tAYv&_FM%T4iXDyaWlfuZm zQdc7ir5QS>NM)4;d>q6UInyS10oZ;~Vs$&L%nFcja|$VHI8kN%Tp77UzH?G};>tEr z(i@iV^8Uy#L`Aj@daPM;t+HZy1QfPgs3E(tYYHg! z)Y~ARlAJ$?&cM;x`6&f7RDKhoIzX_OEgvGnv$`s&2})GNe?qzNsqmUm$6FqX&@*AA z3k#T7H)7juIyfmEnvbO1%rA__2Dz29l#l2IovI6Q_seV5PRvEYC$uC#Aq(b8Ur@-= z9X>P@UxdmDUNSw}EkoHRZtR+F7jvS(c*d_VE3u=$|YQ-w;Hd=09{MExBM~9Y1&elxG zBpYy&QI*8wF~tai{H6!@!q9U4BtY{lX6^-z*T`!3+6pV2l2;l z`%P8Wv-K#TS|Imie=X%WxI|K_Zi)C|;=0=F{vjFmW_5C3qg}cg#1i>x;R28R3mIW)_ z%M0Bx-fdgA#e&O`T@|%@s7#Gr-uLI-%7vqj2x_bv+rA<2!dXT`dQDxEKX-PH0@p$} z=LP-h=ephkr7qHsPrrC!71@ax^dJ!X(DO>#vKbQFimYve*c!+t8@d$MzkE-E@Nv+F zoSeq9nkoV|q@Pj*$Jo!2N_9jn&BJY}Mhx5{<{}5O@0i^50ODtxMG(9uc}+TCz4|<_ zJy6lI7o1*sX}zIr+or@UreDX1F{tlzKNZ0krG`r4F(?$xM7&FaM2HX5=-vI5?VWp3 z0N254@pOpI(A}_o=+!Xub+T0$(B0{gT*hT(cOk&X0DwIzS)>tRut=$k89>`}G!JP+*F^J}x zi|9-HJ|LSFyJ3(TzaFEv2tTuNloY%Vn;3AHAu-(uoInnxuCgLK{^>Wpl;{Iz!Xm|OR(wtRLodDt zJqXF>jl7s7Fk*CWlrOH-X6pRH3vn+Vcj(T8%MYbZp4FGoCXITd)LlPRo@~71v6eLP z3~pMbgl`Ou1bG6qrcN-cLVp_^RFnTrc-CWA+W#RX-&ga(^)76(2z03{xSIqSy15%f^uV;h2YLisxwzfJ z3dA~FSPBU3>MMXNmN6O@Cj>(;q+v{Wf$AhZ4Py0{<8zfkc1tshRsM`Bta-jJ2dZ~& zhuRSg`u2EKc{n~xWSLXH%@o56>oU8I$$)W9JHC9zOtGl3yj-1|doJQ&p)E~<1fgZR z#9iD!aic;UHKq#NbWFIIF^0@AeYG6N85lX#OL8iv2Wm%$wVt0H9iOwTASSB>Prp#X z@J;e(155(PHV00_s8w2WbbStvHfc#L7aoE`+L`s;>AmklhIXi?1GErC2k6m!c?>zd{zaDE~q}15#22|W5>=9N;Sr2Va=0MiW>x8 zp)z8{*osMl96xrf!HVieuUe-~&zBBAz8@&yzj%y`D^zDoUa&1p+d9Z(LzNlWP&Ba`*~I>n|WGXhY@^m(7P6&`_|hw}UEBQyA-_b0*~>FsO4;`lVHh8%d1`Dm6W zoEff04nJbq@vnwPhPJj0oO?QTA~QSQH9n8B)vA2Ze06JT{I1NpfA|cZmPwM&1yL)r zkx63;J1St>odHSHrAJ^9c|zCgO}-s|yqyDAb$hgFb#-Xge0qE=Jf3wU@@a2(eIPYz zz1Mm5bbh4^o^0LuICmYbK%nUiU+%%Q-68ElMMl`n>J5E=w?#Y+`(pZZ3uYOz`d4c>yVv-WVo=Kf--3!^9b=V zoh&xY^mL2cY4)sL88Hus$7W3TKLWymrysDp2n9vQt?luUu4(&I{ugUk0uN>P{in?? zqGU;nRI+cak}X@=my%Kq1|w!PmMm!#N~M)VDw5JFT4<3JEhuTdRLj>Sh{-Pbq8=`f@+_iMNH)$kWRbR`!qaJ{vl!CYo~XYY-m6z;JB~~-+aU8(?pc_(S6;HMi2Cjel47nePsm;Fb8bTBV{G&vEnnLJXYw_~~3SX3ex+JH$+{d-`XI0jmBP6$B7zxOQ!DGeASsp1bw1cp3nQPFyw>k`01ZFpS8Wy(3Dg6sP=TB zacQd7v;q4qYhLH1dL1i#;%ge(*Unt_#xbX5YjJD3gVRXj=R2?mZVW|v9gbmN7UDSk37a*IqIn^{YXc0*G+Tw$sp?xIf*G} zE=r}1I`&6)mF^{__p_|VJxf=dVY@9|Rphsobi`OUo#`G$fd$+AKlr4~yjO9oPH0id zy`8u7PpXZwANk;R-1t=U#drL7?ito^tjV1Ca%D05^>^~WzI}1sY-&XxwatFA*MCo6 zU$SAMncL)*$LGv(nRDv`pZP@%7W?IvHwMr53Nu}4Ys2e~YrH=G`6WB!#`LN&m!7u_ zRag4Gc<>#j@!orUIVHw&)7NB~ZJGMh^>8MquTXzQy{J|`_V^()-hSM%(0fpmkM_&M z&qno^Ogr@DSB_1%y81DzAF)jZhKc)(n!czH2(G=GId$()aY4hc`-T_jUAqwyGHHE& z^}v-io4zV+PY$0=3+O8~aiGS*CZ@qSQ6-yOoTrA{M|r$Cz23)bMBI`**JlUoZwNix z%x67Jeb*|EZl0OQZu-%rs%5`+EVQ#0m?r<_)rB`j7Zwk@l`$&zMD4G@Zd({Eyrau-J`+Xgodo&^Wp!n)tKB8Adh8=wpFCJhhxK}mkD!cI~)3Unu zbN&49L;tiTI8UCej`0_x(H5wZ?K`98I^C0KiaPCk9N4|v*bbOuDQo=UMW(-1N5(sx zE{dshci5+O&{kA=q~NC9CziRB%XFX2?e|AWLTKICZ$8zVw5yobx^CCKzNAhGtbr?- zYif=kf8OVa$D^yOH<^yuv~1efuR#)$0`v1;76lCvd2m=M->$@JWlG+NMFY?DReiZ& z<<(H}m9Hj7UA1O0N;IFiFh{J_P;f9_y6?x=RaSoMU%GlQHobJ-7kPJ+|6n+jW2h2=07E7l4;E0TH*aqa`vccXw`ePx|x3OJ&Z7D9Z`Y zUOm}2qtVY-_i|a2zxLf&JC@^+F=pe!5-t7rSy#(3k9#`QZ+u&;d(uRW1&=@HZ@X?j zbMl|4%JV?-_sg2wrYP;G8}wb* zBhMsvv+^v~gs1F1mlp3%GrqVYK15aDvYEB-u3On($pX2kwMh(p{ znTh;$Mc)0KithFKUDx;Coj=*rh8;TihY!ov%6^y3+Ls)EvSHo8=DO6zWfyN=&^K7Vyj5tV@$*Y={7)}M^4AU=KDo}V z>{Z-)SyzV7q{8fq!eLU$2N&Ho;Xgb)v7#YB?!mODm(;|5WEa`JnoDQgtY~Qdx+Qd+ zv07P5ohe7r@m#rGL-TRz`zL{PjtL0KK`;f zlwjYeweHc*f-?Q`fr^*ik8F6V7B9K;%la9Tcam=|)14VDC6YQ<;qlkWas!37xhCvf zn^(0WNK3adN5S@uVS?V<;(&wCCBsIFnWlYOX)bbjMdL>)*NbJ_(-udBY`sCxU#<8%qLQ(oCPD~p5Ol6xA9lsjqFP$Q$Ad}9GG`$K=J_{ zz9WNXS2VXh0BS)WY+u0BHqfndNB1CWMqnV_n_EuPSBInMx}#m%Kk}}t zX@Gt9=@&1~R-35^WU2eQ<)vhpY`CImm08>x_2TiZis%7Dm#-W#rpjefTxxOtE}b7w zBc_bl!~Q}Wn9W#p+3;DRm!QmcE6u@a+@u!4lJv?|fgL)xPhxU!i#O zj@+|y<2S8$$(yvb`IX=ZrojvSor*=`fS^+6L@p!=fK7v zd*BHL&#yMcN8)zHosSzZR41vTPGYp|+tpJic5PFSk1Xf_=48OWmFYs9t3a|1Q+`y3^cCJ~`Q?jV>kbArl-AedJs&J#cLO z;QEFAS)oNI4~M;9bUNz1e8jyoqPK!R`b`hEPG6)LW;*%alz?jG&rjX5)W58mYc19I zEN(~E0`uMXu~IAQ-E@xu>} z-rAmRG&}xazL5LtZ9fy|cuqLxJ!HnArz-1)l#Xe75jN5EdCqq+}EvbsT+TaI$VrVg8?! zhlH>0b)J%&q5maQem}oa?c;F|wuM}@{k&+~E#0t{A(PhiPpb@CpVr_wF!^J@1uvNM zoX1Xjc4g=tj|1)w~8B{g5@g>zf9-zh5`n z=eLR=wcKQ(C&;jZn*^|i{d30i;V{y z8mS<#+Ux=?eWUlrby{E7gwrmtOJ^o-PdvH)>j~X0NuhOpmMOhDnY`IzUFP+&&-Ii3 zG&Z*t8E=r6pEvcx)YEq?M#?8!`Q*rGs>->T+%y~@$9G6c=3v@2J3n1Llh#7(*mM1I z4=;N^&_=p({IG{lUuJ%q5}%z=Ki@IlOtM(bJV{WjkN zn6!Gm$FEuql`3_Na_rS3qwTaR^Tclq4U^b(-Yj|ciJ4BTSPg6Ww;mm;xaXar+V#xz z7sJzr9J#XBb(m|^ps38Z_GSUC{Qh^Y$^LY&T{Lr9gwWC8$)<-Igj%MI&9$1os=Up2 z9eMnPj|qP35^0oyUa(5)yQZ{vBDT$kZQh!ldNOP8J7fOt`)b9_ zA7#rS=-in)17e3{RwGFY&n&k^_gMcbp9!a7nnq?Dx& z$tY@VHtqXJH7tLa(6t)P_!GlsO1oR!9ePvdZLQ7vg@M@X;3M+kb?}YR@H+UZJy&8# zUI(wwtby`IDHGrCrSG2$s`ocxjhWi=~l>ry^W(W&`X zzVznnvg6O67xYWIGE3A^cDHY6=F9vTrl^kWTiwCBWs5`8=%dz2)hphP`8-(qbEZ#( zLGw7sM*JbWevr!s)MJGMg zG7g50ZH}kwdVH#LV@-W}G->f0`8ah?jbv#vZP7CK*Y^jFOSw9C=&NIv=f^*_D|vi4 z!6f%%SX%z;{3kgQ1Lm*V9%?6PWHjXVM7CaK!ppuB{0dH3sD{m3op!+T=8RM^lh`qC zCuE&V6&~89rrkOAA;oC>uj7lZ>^*wq!t%vxaw1v58bW^d+l4MfHy3f+A@nvMzh-tfQv zV{qjPQ~z?q)~r?$#kEiEW=H3{9Ez{ z1QQj7`SmRhFMcWr>^~^8?avmy;)<}{hkadNP8ao&E1zhxp;djt)%uxNm=)$)7KtLC z9&cIs>)~>@3#-YJ%EF526L+|LvK;o}zSh>1*mu*4K1th%8<_b9 z{uX!JlQv@SD}klep4Q78vafA%8oW}$GTc!1o#YH&q)q3Q)(b?nV zsiO^SjjaO58cT%t+xbNoO=McCteulvD{VG0xAdc5*7Sr#DWf#8idbd+ZT*#XgBIP~ z`MsrV=gZ%}B7aC0s!!wB7T6Q-v%A-3 zh&m@kDJ9NKo+I)m>MQ+&<`hYh(%5xVceKn8Jlt9sJhr~Eu5ZjEL*?7o`O_-)gi3Ea z+sdBwt16E^&c0~hEcl1_MV^h zLGw_!@vPychwY!-8+0&`@!GKI_~kO6;XC)uH*LJ9C!(@2x1x!@ReD8HtaM{*X~gh3 zYu?_N6)dq%RZnW_==)Fh^S1;S$Yq?^kdbD&!zW2(z~f*&{p96?oK+XaZ#F&Tr8(f- z!*b2YD}iP4SyJ``ZhZgrV)&bFN533g78NM` zpV!+~X521G7g){zwK8+jySZQYzRL>Du8x%4k+b#l%$)HpE|`}r&ldseqjYodf%7y8uzOv{o=)p^;X8(tQ4QO=GmoyZ%R@MH`nYgc1%&x*BkRu zwAD8DNB26;_1PCyCFI7HJZxIc5PiA!@}L8ZT;-vA4ozQE`XO-X zwNq!=<|?{-7HcjF{*ZR{hQYV>!@v7REuJKMAzfx;RYmxt>}w=7niU2 zBe*x}+0@w|LaXVemruvai`mK@q+d55Vnmk`&i&{y+-PNli)i+$Q?x%bqW3BR@%1(x3*WOC-GM#eok#{92}Ix}z2 zl}!~1yy`e+W_|7aL#wi9xp2cIlTkFDo_TT$qC#Nl{d#fu(D{urK;=eR)M zJbG;Q?awWS4NX!F*_^17syU&rjm_TjwL14HG0B&zv^!BXWFr%u0}c81B0*>3V^PC7?|Ps)FX zTJ%xA?{msyOg^4JxaaBhQCTVr-p%u?dOJR{cAl5_;d67JhrbBEqv)x6=je1%>)5F$ zmwEA>-0d~&on4^yleFkE`iE<`m$0%wg-hQ}S$e+W**m8*YEu4c^n>2->Q$yrnXCLI zS?y5PsdIgA{+c*PacHb_?5U(#Cs#=~EwmDRBt5rr?wzW{&Ec|-?=N-AI&3(BWf0*v zU2)!|1@r>{sf}W8YAX6Bi=+E2)P5_k~dHN>T_(y5U#p=(*o)xmRJfh9TjF%>? zws-ho=kjLf`!z?ZG8TRbIWS0C^p^03(fba>u6q8if2G~EnxAhnY7}lA_j-N6!$9(< z>gPM|RH-KNEOgv9hQDe;BDBK9Xr_3u94Jj8x+lZ#>bx`TI&eJ`$^ zIy%Ew;e-D&#mV>I*Yx#u3pf(FYmoE)IU1R*778k_N7TOA^ELZ?$QO-+dM*cl3nnPe zt-LzUL6S4ddGq*Wi{mZ*KBXA{V!sa2va`5oxST)kyzrF#s7u;!-W?B^xXR6QtDEK+ z1KZ(^$@|PxCfUWt+&j1>I%@a&CAY4hSNxC{zW4Iaq@>NJ3T69-eLq&|CHSo-&R~>e z`43Tv#l?{#1%(kBT6M7_BR&`oRUdWBCc$!Z)kei%=J^ti$0fCjFrq|GNIG zetqqLFtOxT36qOL-!8r|FD!2#v`s7cjQrpnuOL6!pq+{gTD0;RtB6*_DxwwJ|GN)8 z5Qelb!=vZ18TSKjL$}9`T%MNaKE7B~kS!rO;k~3#-nUf zfD&E1P`adFfYq8LQ~fK$KF5Tv)*H3E+J5bTGR^IB>(A+*yQ=xf#bB`8AQibY35my- zWlSjA=sAC>j1=E{*CgAoW*jBQALE!w?n$hVqTks^?>R^dIX!SVx`A_CMtN~m@k70H z8ZxU-Yd33rck(gPKc+I;V6geRvXB|+EslofP)9!;$-`oux z=Smldoe(tO&-N>JUs9#nrB;GR3+lyEYPZd$@0z;xYl`C1ofdBAG_xGqYOq6?;0Z*|ycANMiW!wk?B?75na< z0iS-p&8PS2JD4_@CIJ6aoyIbpb*mx?O9(5%cMyEhk3ZWp*pHzO0Wx;lo5={Yn}z<| zZtR?QrvP>#oFJD)e#?M=V{^PU=xw#48qAO|4a<m(u$ZN5XDj^z88B##f2#!10 zbx)l3aE%+qX3v3_wR|+1qGdNjcvgQOrf{1&hGj&DdFS7et>OTY#fM>DD*_U zbQB;ygRf2(9Q?Ht8jjae8#c$E4wv%z(Sw*F3}+7f+5P|zKEWw?ZWW6~3Zcnr2!R~z zfZ`EQhL#HSVK{?j#elV7DUYttIQUmQXb&vd)#&WR_D;obwpLJJWEeDiG~nxY0mEMp zwaBpB!s!|8r7S---IG-FS697>jRB=>gVv)AxzG{Bt#4znW(3ha=|OZ-hEqG)R&zjx zRls6^$dEFTj9~`DiyrJ3_uK{C*d2GEVzmT=d{kV#6_VTIVnF@r^< zv)Q3q)$aj)9ymy|E@=4cj4v6wb=#~mgAwS#VFrMaSTp=PEk&_SrnY4yzC@+h>%W8n z9~~wOJ%n?x|37l#6HSyq@KzYqHVMeELy=vd#wEQWf-owC2^TNmvrPE*e#diwW`i1G zV0HU=2la+!4u=U-4n9XnrAUhz*jt4FNYjmhMmrQt#$gASh%h*gjG!QRvpvw7&B6vW z(HK6HJ5bRLNLGPSPe4eV!+K4EFUO-bFHHY{p2h(Ms;eQ9WEA+$2%kep%IkF4A=Dyw zCs!pu?!N(}Z4(S_2uZqqN@K|g0JyF7_|0CsLh@-kU_FDcN;gazt#$(sRPyEyd>%LV z6X@eMXd5-hw;RcTJv)4>M(317(s+x_{&Ek*4PP3bj*v~>4RE`moN}MhcDV&@fqZVK^!I{gwftz?SFR~35k5uf*ii&!Fc{H!0j^x zGAYqHFxt6DIQs2B*sKH`Y0yg$x_0|09>V8nU*&6f{td`9jNAi*IIvdj6baw8_Ep`q zW;~25e?ji}@AT2$GFh@b{DM6hupfi{!vV`Wc=*A6Io$Kx#I0Ya>OyBpf`yA5-Lyxez0ak}fNVD^&SnNO-I;#O&g=5HWir#f{e1^`_=k{y znSwB0IQ>tI_<}tubV>&u(fWe=P*-uT;GbxqcAZ1eO`4bdoU=iRfshwMxgK5oCoXdi zJGi3|h+BR38QIr!fh!)kOcC`p6#WyIB_xC(^!4c47v!d#t`zR^9>$n5q=nE`h+GL( z%8u15`4E#j_vrUaFvM376WJoN`QPXzQP*_RN!hgm!#5bwO#0$}QZyO^)<5P5dFicx zCg(+3{#t+38nDol(4(m3n7r*JDNkqeNASBkK9>%JCwMFIp02W^8$0|LvEy3z$WPEw zo<_T7&pow`{?JP$XuZXE)Otc9-y(t9=heJBgpdDRb9n<+h@aZMb$&K+hS zLxhzh*n3u9#!_+9%_$vNruUGs4rX<#9oQ*j7n?Q1lSzlwF1}`yhmD=w0<2i5j4r4J zM5(g!w9N_Aq6M+rY;?e@p)s=WKLfl4NOaKM{TP-(41auSvQ`8;27}@(z-^-I)Qgf-iL96&3|1h)+*bH$ zbG$Qvr-M&M7k&v+R0*tSbPgzdAvT|Cxi|qP7+VOf&>+qR?(d>z*woxa$bjKDazI^d z7_fHGH(j4k!>KaaLUMuwNv^Ibrpx5Whmy+A50{#OeJZe{d3bpRQg){2-(0+`nkpaSVg3Y1&uiq2<0yrHaB;_ufg1@T3 zS(6wrbS&%`E{6RAb)O)AKkmp&zPe(GBlukzFdEcgCd2(wB)EY=^q}BC4Ok(T3O>%d|h6uA934f+MI13E~M zHBrYJos6jJfTXMz^l}RfFH}|E7LgKBks_86{x%fAm-GN%=|Ksc%#P+?Tc#fg9gN*T zX@J71BZ2E5IH>Lmu=Xd1i`?xO|L#Mp`Wn=2yFy*-d;+u#(v=KV1yG4H|v`c6B&Sg zv&WcL+(!xDgzo1Dt6A7s!~Tiy_^xcrd;Az?ur!!!&~3uc2YW+;kKX5d*cfGqWmZFq z16|RL&7p*D8p3Q_iz{~iF$1=*@wqCJ;|7g`?kEL5l_;GMBUor)bX89~pRT}hQ(1|&Pc527UM z#l0ZG#s{&Yj?b(NPy|J72J=GaY_V(9N$|DwX=cEsD-huA8OB{BvgtNCc4wNH(3V_g zh%mCp(r9n!+!N3-xL+9Gjki0b4rnrOS8Bu=3Nt>90m?@ef39C&>foKbv-rxfbv3m% zfN}j+l6zeH4WL9q>QN<5t;qZaF3$zTK+~3j@OA|S{lL~ZcJ6|!2A@0PTjM_N8Mb{b z4;F-O#Jh`8qVa4W%AMgbzN&7A2S0cK!a!`z)gJ>P)yzW}Y>TL)&pTwoC#mF|*O?0) zei}v_x)(WfFgXd9LJlH!d&ZW+1rZRGts27({9B3wF!A(eDxtmRDyRgzZ2(O`By_KE;sVm zi&a*jbAR!4BIx#t5UhBzQ!PZ@yDeDs9xNtlKYO4rFzf}oHh%98s#DFt_K^F6>e05> z8}$Oc<60DM$MdpjF%xThYmc^XG3f=mE`Hnl1-d9tfOek;Ux4QK#zBcDp2G?3sKi0^ z-tOBvy8)ky|JkLarI4NQgwPkAf3KQTr^3e;NUM5`4KP@1aQV&TAl}%s=^Q4O&4bt> zhz{3I5JV|HeIEKaf}UQ&T!!WjA3>&%m#%t{Si*BS_|lYV|C%2K(xk!{)l@<_4 zz2<=7-9vSAIP?H0YQ~pvRe#NdC|Mfq0oXH|vKk9@q`i|6pW&XF>wZ;`&mH7LCs5&C zJQ$cEVI+0zOLo~`h6#aI3swdrrQ2uo9^Sy6BU3%JXTmDp+^&xcAiMUM8b+t{K*ka$ z?JH=Kmj52MCFB99WIQYo(KN}E3?BGFVF3(Jh8GEVL}KJbUr-4aVWY8^SQY^|mQONa z!&46MS8#c?D9^~Nz zm5Lq5c-vD;M5dbP+u*(#1Y?0<6A;Qrux*WNs+eHy!gM9KF`0-cQc@%a@fI6D_R&s_@rfo5l_3dqPn|75|92}j<#3{+(fGN93=!WBxeoo*N39c6r7cm5_o zC&BoG__f=|x|jz%X}D+iY`+)-(YkDc6Ks{LD_)Bkzm;c$Hr48cN!`BEQ{(h4; zaZffLE8}XxDqztE9u)pJpW*CAlC0b@tK^y}Na*-SYcHm9eAunCh8K6)LM6i_OZsY2kJ1|(XscYi8l#&FBlG;I^^N4BeSBISefEU zP#&r{2N*BBIuNBIl}W51boV?U+5j@4BaqcfNCZbRVL<%#Chhh@?wy0S*dICymUtJw zX!b>`Pd_4oI(a%uaEa~x!j$Bfh0yg2V8EdjFw^?-LT?+_5FxNRROHBg^uVqKn6UGp zM##wW>d%X*9p8!(%Jg7+!`j$~>Cve)qBXi$!{c!afX)%R2#u>N`TheP{vb-ZdFuHc zAi4oA%N~&}Re%~wk&J5Y}O@Blr5^YdeSEbj71 z@GqT`;B&B|ri#OE2dw~l9_5IW<-yV6jjVWb3|rGEL9B>Sd(Uz*{ zP6D@W_`$uq9*c>R2+3k8J8r;)p$U-=ItdN|11HlPE+4+)oSc^?m4h&n&_QU{bH-S5 zlJ2tLV?S~~e#H=Q8{vyi68_`Jv2EDA(Z?rWIggz{-UnTd&Z)xV3DNzrL=mHFdD(GY zLtFlcs2{90sLQ$m(P-#;bViJ7s2Wdj(@;34W6N=1 zcr!u+P*D_-{~u`+zT_-u^#u7~-AFS;nvkLSUz&h#6lDfh*-+C%iw8C|b`Q~}#)c;- zsepo86f|6baxYM#naugR|3Zl`SWeZF%u~>lKcFX3Gm)LhlLa2ECKaLI`h9#Gpl=Ss z7tK+tPNe|eX7u>YzO{Ih`CL%WC{PaS03`HyBHOS@TiwU!W?&Yy7^?rdc95@5dxMX!opmn~ zH(~W(69FCFvQu*E4LY%vC0N=x7lL(Rg*E6D%{Qkv41Z0HXo~(hFg58JY=V^y=U(b) zxDXP-DRQ{u;%|`2y}#43+uC^?w*6o6HMw%joP%_b@c@`Ly5?BV;6;y5i_i1aZ%^I~ z=&grP4rPptS|{&+;{kUB<)<8iWO~Rxd+jWc>odrOjvoQIa*n{$b*QynU*vlzQm2K7 zeE=^s8uCeK#pWOuFMNM(xDuo7!XZ1hf7kASwKqXA?cInSmtKI*ux)rWn)VK&z(tMZ z*va(PG9Z`*3Nt}E=m;ba$rT0y0SUJ`7zZ=`;075Y57e68=M@wr(m(_*!uL!68zF&9 z(!PKE;~gNJ*Mo3k_gdP#A^ouUenz6~2+D=M1MA?N{|! zvI2sGAl1U46P&Q*`xUi#5lBS`W@AK$_fGeU6>+G<)vXo3#Mc}I4c>rN6)NTa{D0?z zS%TCsMf22j>_EyIm~2tQynLaztQ5wMG=dCX64IRGOFHU$TqXmL| zfB*?a54uSK-CtV`1~3!50aP9S;NBC8iriEA0aW!0!g)2sAo|^*fF8j1SBK-g0odLE zH?*tx`X?}L(t;t}K?`*(?(>3%YAlWRM`TE=Ef{pcUnx+&5#h&eULzkczOtOKU)X9$^9|7$GaZ0VMAlFL@8TXbOy3bR%G}$UoAxtCj;< zChU|s!_zp}%Zn&D-)qrSu>+{DgM~XH7UCoJAL%=qVL@kk!T~3uAgOY>^Ob8bwAPq$ z%fE8S|Fr@5>ffhl*3TZ?m)<~bmw8q9ztQ9KRX<%3b43aYK_S>cG$atiC&q3WTdCZF$Ggh1#8tBPA_)ml{W9vZX+eex^qu=662#ZxsLa82< z@3fZy+6D)~d`4KNS6DDm^$j$c1(6qe!eP4?FAhosK5<@QbHMak>L>(Ae4V=#*3_|;G-HKjv;uFj*t*Oa|sIgjwo1hG?i6XG?4nzE_JhA~K zedt~AE-(^hAW`k6Gq|@fkrx3B9<_EZb&sAGW;y))uy8kcoqkT-$ai-20y#9W$5H%6 z>h(L!pv^*H*l5P1^e`!M#{uEatO{n~9c9kASAJbCD3HttonRG_hKO#$PEg|M*xkcw zT(GNvJ8E4BirD8yjts!^0}ElYL__wyIaJtqNwfI4nvXMJXG1iGjt|X~y~8(Q2ZX_K zGj9%@p(DSlQeEMAe{qoMFnrPI?7I<@%5I1AeBkm;#+_nh`?&TPzniw-cn4&@9s1; z$8!vj!UmLuE@nkVdQ0KV;)Ew|z@w6L!smd5aSAkAIE310xcODIm-P7Z`Ff6P zyb6;;p$s=cm?m!m6YQmmPIJa5DD+xZ_5u`$l}gP<+(Ft@3IslIRVXiMG%UDnVkDL#ve0yAE2i`1}#!%zVWOq^!xj3Ws z2c@b(Vf}iL40rB534xQF@^SoKY&y_@We(~d7_ds_H4|}3g5M>gMUL@Lp{U^=SQgqP zflS^MU13P4zVKP*d@Npj5wxWU=7r8{r-G=kw9l>NbEg{1DHl6XgdGIaLdNOC5Z)YZ zF;zPaz6e_->yF+4Sa*O&Ypj`}yy4*u5$3(1)!0e>Y_MV2=;`)J4Wr0p zJ)@&P+U`fk7IOGPZ5wf9tPs#lhJHb#Yx4+-RMd#JtX?`BTP!GmTqx1m$X*a(l333m zOBLTebU*tT5PgI%njE;ZoRA2PCV0b}I~q>y|HRjo#v*&`O`t3686;GMCo2e%;j%~U zO2ofK6CDR`Neh08{P&)&hYo3lVTlgOJF9sTnPBq`6&CvFMeCMBZ(#exD2v)!o-A#} zl2lkmZXWXoieuUi*`v#yL2*1;+6N028nu?xU}X>_wTFKx+sKo~G1#37hyAI@9dKtU zZ71-cbFk}Xo*YyNe2?3If_q$OM?nZQH*k9kRRRY(%e%9jjA-hrd*Zet1_Xg)mRtu7 z7H;V)E+EtUb!>1 z)^S80a^c_#@WEe*2Rt-^P`wJWcuL@4x;{74@WD-d7#@oQgZyEur+I1xZz7XE^)eZ|`kjf}Rr3uonk1z2i!N)wAJthQBG101_@u;nA zgp=o7k!;(rgjyWzsmWlvpQDir=`PCHXZ&x~zy{C47xo;wed_NKVRvPNNiv+rT9&1p z0=QrBMH?OdfHyFCUh|Fk2w!Z#zW{}d>DFY(%RD5&hIgT`!U8(cXwMk(<#`S00FDK4 zHMi*rK4eEhAO+7y4|ZJ!_qEfGz!&v=dR6$??K#)>h?;07ihi?q_CP>PgD+}nyPxw$ z#kVx==L^QX2CpFlwE_x=VreglF)=5KkGEdBzA+Zs=K;eS#nXL{k7vOS3?jxedomy& zPC(P{fe#vmv`@GeAFrc(@SB%;)22cLn)d^~;}AI0&!li<^IS#CHRd&lLd+ooRYO3p zf1v@t`hdQutbsGuy;YfEoV)bUHekLYnYV zp=JxN{~(z=O9mMXEd@w<*kv4z=wLw~KHP@7SX+@z^LIB0?i&fcgkA53hIipY_<-R4 zpjZUEC9AV^x4nFX{7(PKcf?K^Q0KABhB8+zED4`CgH_+pn4@-POAK?>Bq*%A~$ z$$EK*uIEYY>0niWhi#E=pPIp>&^Bzc7BBFQNtXeSQ42{xG?LpWg%68u6m^ESklY}h zL32y%Dog4xCL;6|(lb3><(CWday&+}@v&`K@Z zFzCU}&~kKoKB!I%ZPGQ3I>MoM0*4~Fw93~Wx)D3Af+mwjY7=8y1O)|{_%UIY=22ml zCsVb&1Z)FKhZuIV5ja$+(;N71gl!LK<9o+@r#8M^1A@Tb3s((l{|WzvO*<5y?^>;i zDt5dh890p*>6#|}7d{;VKFiuIk6M9e7jT*(eAlP+h7ax+>1G`;?~pu38`dvM_&20x zE5RwD%LqNaUU1?oknI~@RR~rc4PicN+1ss&!J+v^!9hM)jOao9AW!&}$8MpZsQJ)2 zs3*yGBF63-i;D0k@l6y_>rdKl0(9)$H#)t?%%*~luMd5C-dU`$bpXV5Xqy+iP(dX> zwd3ikuIB^ot$~naBw{|(=kw8g{ot-m4I>*z%l4X0MeI)#xWk)Z&AmGM`h7PTXa?Z) zq!6_F1wGO5Ftd={y*3Yxc zpBTTG7*zKe2Pe_B`;=Lnuw^%@Hg+UwW>hv3@ z6UqeYaj^?g875&K*afd`JqoEE(n)*D@mu%5gMr9F%!{gc8`Mg-o9$m$1OA5W+*PU@e(EVbghrnmNTWf8aktO*$iLv&Tb9^a(!ZvSuUWtC z8_EsjFz zCMdQGf&~j=hApgT+ERA}E=0dyjg2D&k7bUqjO}U){3Qx6<#x8F4XXXf3x11^XNXH< z7g9!oCqcc)fm66J-43%TLiz=3swoScOnYPTNq)SNTrmVp8#_^s?uzi|Q6;eg58I6! zpGa`6;lm=3h7LrEh*F}@@*=`VJaIAlLKe)j$}r2KYTkLC8!?zgc;WJkr<1D8p;2W3 zC5<#}-Q~Y9!JI%ghpg!T+4MdE!@+zO!{9*8N9t-P3by{GFfPP3>l4QV%vu;@l1PJA zly*W{fInap9b=+k@=`V=ZtX|f2w`^ruL>tMeiCXWHCH+)v;d-%N(bQ`MRI>>&s z+p$fspC1nxeLgy~4q&1IW-QX2kqf1$lw!|98TnbJv4!7$stX4d_(|`Z=)pm}ncY)uX=l=Q3VaU%X>f-%;gxq^?tnSesx^X1J z9vR&E^TmuXcThfL1_Ran=^iAveGfVi9fl2s20(+-nQkBbP#iQ8B;U9-Z{dbknlSi_ zk==0Mv6zTXq^0c^(b+Z7hA!SCul1>e>BgHT?8f%>f*F`d8f2r(4#5oo(rf;hmt z3BG6x#MgBqm@a|sA%Y;P{~ZyBZ20Xh_>O{a*Qe<3@6F(yFL!uStW6uio$Fstn%cTIeVm>u6**TB#B!WSL-q3gLJv=se4EG-oS37+w; z;NRMk2r$>+i?+smLk}qY)?6?#_!SC2$2Pc7h>;t)Aqb3Q=`E=?h^svyp7emw!EeQY zUrMc-U?)&=!e#u4@tv)(VlTDhFxl{!Z5Z|%1=E8WM9}B<9`#P}5KG&=BIw$C@Rm-r zw$hun8!`qsh+8j(EaAf|8Z%`~(n z!;W(S;RBS8_*xSM0LS2qdT)blE&u}8@Gx>>!aDf%xgH4pPjDd+7){3DBPZhF*O-e# z+i;5;fxuj@K3y}04ZmIsUvwnz$?4XHP=d>R;(zAZ1;bC*!xvrGS?6~C+!3DGhPQ0O ziC?nuuQ{sa0b>xmy-OUBx;_FY|Dv${3AQ1=Io|$o7Jl*?z6xDGg}<`S{QY$R!{cuw zuoV!U1*s$ajvZ=$`imvl=r#dvkC2K!g2=v{UA63(eB*Z_zAl7wAdz{Cm=m`C-7KAA8J=Se-FdH<3SY-a|R3E zKIdS)W5rxdpksw=vo7ugtS0!P)AIal_?U1-48D3ZScf=RDAxocq7k{_bwVhtW$okH zE10F|$MnLMBpe1qOA-DDpGSGkwtPNN0k)S+wg1^{s=Ca+AQA>m)l_5|KOm2+davApD0c0Jt5{31; z!xPq)MO5cqpmp>?F2Kt55I*}Z4y+|yQ2}oY5ccvwzgLrC$wj*WL6$lqe9k>mxHenw zDEuRsMr*z-ce)LjScU2s1Y7wTPi%bq{*xzo8ura-%`mZQBak*#q>xyg<uu%uQQ$l3j`Gp7& z+sJEQnQIZu!J1FSo+yAdd-g!R)<6MuGQRL5ea{+0;UTRBVlA|-hQBGGPQgc=RrNT; z3)+h9grcadz5+NS1MBTB4QLZge+m)mCD`tqH?(yeqE}@>-lznjN${e%FE>K(V<{pX z1oZbno}k#s4Pv+714cBW*zcqg-vR-4A>~Mf;Db0B0lsG64Omv50%%8Je4>^=T#^J0 zAFS%*&RZb=Jywg`>!wRl0G$a@ z7U>I$)9me*o~H-ci;wDnjuVjmfe`(8bB2aTwS7c zj_rn^&mvwtZgAX4dx1?kpK>q-xP3{Ixqxnvv0w3J=E@@Yn;j?5eGcLoH z$I_s$kqFCNJscKni<8bF45;MJ#%iQPdzXPNqsJYb;Rc5u5yW4#Zk9{nD>n!FdtHV0 z#X~z4k#^df@IuCye64*(A8ZE$E1*O}RNon7h<}3}d|0|mWMAy=+}8k$Wg!F6q#X{VRpC;v%^?K;%akzpl(_um>jG>s+3HFhDm|%Mkj}#*1RyWxq(%Yf z3X=z7M+TgnCn7W5|9k|tSO263nU?dvlM%#2!zNqRM?yYfBy=`9b{5T{N``NenWd4| z*s0}D;7!nCl}YZz$oN=`w_R@92G)pWbH*T6Y3zxE1xH*VIEMl*lGKvQs$t@Z(7Z&@ zGCE?E7&wsNd&`Y|#*z?G57f1!H$`0Dd!wfX;i$?B2i=jDw-M)2faFd|3}`Z4MxY$M4D0QN}gcHQC01g_>49 z2Nx@dz_k8s$X#v+KpG&kE@JjF!T3O^Nhrd@ih=F_hhT!+EB{;>O9y5E13E}cLx>sh z#Xh_ElNNRY_a2Cih7Q4@I9Oa6oo?>2%^MTh!OfHReX60wjliUb=%i0X?^#HE{zRE+ z6RSP_aBN@I8xDVBG1BvoNqK?@`rut=AQbzeC{T25#&xp;@@cZ{u(krW)u#c`3L2?| z#qegtH{nS~^2TCkPZoj{sBwv|Bge$Dhh0tQ8L_O_`6+-^pTL6+T)FP_^}QkTV*7bo zF}w(SUU2jUn`GeG+rzrvZ0rqBAms6gEI97CJ!wL#BrYRP44DBA9XDqG#MI9&N=}Z^TO1u+;||?~I3SogQn2PT=;9 zVrw>w?#cAPM?P81-@pV_V-NM9c}&UeB*?JK0^Y$4p4NhjLPhwKw+s(8`W0etT@M5% zRED>3s*F$ugb81UQ))L3GkF z8Xvo)lYmB~uR4_I;~uU&U`=5bslKRrYW zOzt5AzpPz-31Ejnd(r$$^O@ET0Bf&C}jM)(R~XSzhpO-1wH) z=S%|!dx*6XQqIT7|5DbyK9SF_8a=)Sfc1M;G(MqxnHEX*a_LL>4Vp1en#A>9Y=ry?t$XU7^wf| zO@puOCHiuW!(pa9sLs7bp#Fy^CO%|Pe8wCtJ~%E7wIXP6x<^E?=bE)EtiiOw)9cTe zKd2c?{B7*vJ=G;Cq2o9D)R@c*_>qm+5K6FZ-m_Bzd=7m>9X3BG#sKt<#(SPplsIhQ z1^|5Mmow9!^21Cu7UFeuB$f=L0*#Nlpq9P%4;b6=9z8i)i6<&g|0HS|!NfM~>U#9# zN_9%;_>KN~V4@EGJh)Sn671ieB%TMi)clcL2u;T>8?o%>Mqt}cixLNBC;;F2aA=z! z$9AJQj6gW!N)++Ti_MyVWm5{k=c0$JFYEjREx!KFE}tK;7f5EqM2K4H>q(SIh^_Sb z=yHofX#3P2^Tl&LD$oQC7QC?E8#-e?GQ2U%>V`!*0Bxb=ecKaN+ z;0f7gKg8G*gye2MfT21ShARATw~w_WPi%lCh@CDZ#yw|=z*#X^TBEQL3#q^oEE^`P zt-k&qV84SYpsTd=&s0b4T3cE6l3M@gX{#J>Uyd2QzNH7P*ZkmO% zy;xqb_{O#Q^JbFRX|?a}bN}u+zb)UwCzs7c9PUU9-Bt76r6#U|jl$ zUl?0Kj@b5YZ&%RYEpt!MgQ-IbuX%zUn*$x10pDHOD=Gr~nXj%-yNwNG5CvVmxZmv+ zKK6(;%abU0*{bkuk|BuD7nY<}h{lrd_ll;gqNYoWKSbTccJ2LW0 zi{f(%Uk3tD7_3~-oNvp^Uh()7mqF++^7xERdi>B!Xkg2J(<_c3I-F}GP}rrPpQ=lN z1bch{B@wRaJqdwd^gr@&Zwe5&g0|2pT<<*<5`4p&OP{QR6`{eq)?6tO)sIwAv0D%D zk#|jqEu0EU(FJsLlVi(gs>q}j^G7}m+MN#sqR@1%DYK3WLEEfJ5QZf99vR^U*w`kD zIpR``zEQz$KReOOMc7BRq%`cdYq-#Xt14(%eXmLJJEgeagx!)L7nUVPhxUmd)G17O zR&~U!Ju{2}x(fi8jFz6M{-n;+RfL2uP0|cW&+#A$cF+S|Q2uD9!U7eVM8`coPER_p zP7IcQV7joT?DmOhp#Y8F-svx5={umkH=w#)2&8hA1@89!8*rJ}mRU{9t z{S^p2alnJl7A|7{#KVHSn~9v?pWNED`W>BM(oIj$@q_+}j90R9{zfsWt3ap-Lj|?1 zw-UV}w4#THnK1kaE}C+@<km?iv%xA-!P1H-x5Ht9Fb45gx!7R@L1;8pC*j z!<{bJ@(&-FRam$q7t#w0AjE}0x7+7{JOyCe00L+J!RY5bIEF-9Ez3R9a+FAMvHgTj zyTxyEvd-o$a7!?)5K@6?F^`rODfDc3{Gsc3AwD?UAS6==l#JbBf`VU~L>-)9m>r3p zo^uy~)4>qY8Chux6>$6}8~rfm8zu>X1nhC5p2tCt0xCY}OUru^*dx={gSh9)%Qh51 zJ0>!G>?Os9b4Cfml6?fXj?Xz!z$Ukj(@Q_ZVrAgibA{+6KV<<0a62{=-reBfH(asi zQXzI{iV3*M8Qq2oRKF}D#cmHvI?urTO|h~VOFL(Lf3lA#%20J-4#J-7$v*9{YI>Egr=~b%7M*+n=pg`B<7h-xr5k~aX(e^_9aO~~Ep7_((Q^&`y zK*U#moZRi7YJd;tcDO1)i#AcmpTQ1>N<@5y5BS zqs16$AeapVXg$KyBnvSv5D??+ar>+P9E@~K7*_I57VgQ zI}kk#b;0Jl7G|J5)$f!^9p2FgF091w-}4p4C+flF!(l$I5pe$ybpp5ulmLI){B;Yl zlmNU@!{yJEk5k0QZ}{90HI0=3Yz}KU)NbNV@B)VDOOc0XU@ltE7YXo_0Uq`7%##%1 z3B7`Ln7sBXz&7rIZG46rHnB(W8PI%jc?UMSb3fuNHEc3(a9H+^YZ##GL(9==wfQ_X zbP`XnF`U_FHGt~@IJ(HZeUTbCp%=LFCg6TJK=H@2?8Rd@b1YS1Jjf92C3HX8sO?$zY7@Ptxo;KZX|y8p@ti(z$CJB~ZF zj=V_$8-K*BK7Jd~3=M|+Ubq^p|A+*%Gg2fiwA%aa=bF9{#Z(BvFo?(*hX{CFhhijl~bR0TEqPzL-iJU&Ko@_m4w12Ybqh>otI42_9F@&MOP zvDQbm5UK>FaL;HL-&5vjJ3)mnLs4bSATuz*1jtvQ>Jb_)OspPrIPd`(1yYOo`oNZn zU2=q-C54gG?Q?hx33NwACP5he@xVm0>#!PSKq3q^ZQF6YLGg7kJ>8~s6f|@Ve9=8V zzbQO0+YJyOR%EWo6l@Qn8NTQm<@Hn&Se`|L0*%RUv6=$S9?cD$MhO?cxikJ(+L^#r zU37gM<#AU`v(#__y{?F;<(5mKK!SjXh+By)S3u-KEGk)M=C0}EF1h4_JGnq=>7$AJ zzJR-yTP~@okGq!o{?1*vbMBq_U!Kp)^Zcui#dpq}IdkUBnXP6_Rvxgl7oA(emmP|P zRi~GH{KDta>mLna3EX$g?Wl#;-sE_1TvDf5AX2)G>$la%8dS>PY1-|r$}d9>g83(W z5|72|cGluz2bFows_C4^)@bcdYXrj~Iu|=IplEn``}Mdq`04TrPEI{B(dJfMHn2!s zId-2udnX;q`yF2$p$}titgi+Z`-0>=&pYwaeNpu;&@dM>3RwuHKL$^;(cBKS8br>f z34Ag43vc(=#;VQsQsY;v8h}|6;_@D`DMEvZx8O||Rv0hTmTRH3E~?leXw%nDt8F4R z(Acda=k@Ja?$9jo?&0Ir?-8ZVQ&)dX!=*E~fkz1(-tz6EHJOUZ)BHV8i_wL5^u``< zdH)z~o@UE;zL3Af10qfbnpge)Ky9qL>WzMg($o1nS}))&Z-~=krqOenW@!}z*x}6) zXR0D|H5^GGo?*IzT`@}gH(i{gsfdHUb0!*K{=Z;2=j6~~8f0@;(sUiZ+`8f>p6(g} z_0GfGo#(FJ`?9EH2Q$>k$E$|ZV{DUA1kYVv9PvLCp`4{2obmR;Xz*v-@#lV1biR6~ z+JDQ8{QhP>KfT;8R}CB(@+DRyK)^@^b#~?tW}pCU1vZ6Stx*yd*>$< zy#VE`HFsOd%Tz$N9cCH1#4stVG{Me~Q=PPHgZi`nRCIhusf7S!@o14xWp@FsF!oxp(k9ws;ur&n~b_ zy16VZB3RR}edwBVrz;e*7mDHY2$vNWeqv-=oL4Zt(iI=Ag9-Yl;xGDGTpy)y!$;bj zS#$)0TO2%mHT>QBvv!pXDzEy?f1hqV0l&HdAE)H4W|VGpB3mCLr?i|lx$<}r*5c!v zSO2}no`MmEYG@?lpXJDESI0kD4%{Vt)$v(>60)U8Ip)BTzGc(#yHv*-RtEm5_m2$I zgPv4@{-acjW_g>6cOQ-4TZyj@BUN2%A>olEy0hHN+mI0+IXDP6xDV8;oG@57`~Y?5$VodLAr;H3x0)>2 zPtSDPBg^h_gjB5Y*LrmAHoc!R%1+9O`y3$^JCRGxy`Mlcku!F>eE*;$!>GE z;-J$Gh%M!MT)m(|70Tt>>v6+52Sml4x8tVlC`-gxJ3YR>=z!Q-E@O1l>D^z0NKY&B zmg#!g0a0;T_V)YR3d+J5@uH6nao#ltM8!qf<9y1k_!dML_%!|m!?PPoqP)7j#$OHS zj?VW8D8Ayg=e81MbmNZw_~7zK<@~%tTH3 zJUH!dCfg$-AH9Fetb6F?{9k+p2kp5Hacu*8cBNd2MT-tgrDSBwkQO%N|ECBM(kRH-#(6ycePco zZ8^9DGLC{{xuUjoS)cU+l<{_zTlmJ7)GcZd;!vefAAPcZ^irTs;B&)g{kiH?-kico zF?Td`;2)BbqdrAtYPFx4=)7ex~s4obi92=xj;ZR@a_=2W!Bg(qQ& z4=?>{8OOtJeJb_?i|(Xu$aqiQvhdOY@$$(qL?*^ZhezU_s=`~!R59zH_u4s~12rA0 zbYs-zm9?nyM!C4A+80Mba{`TLCy!KNw4n5q#Pk$e{Ibqa6!B`e&{`RC=KXgaG8K-l z@!?yZ!=T4@7&G~Ns#0}_%+!p;L_Kn!(MItVQZGCf9M~0f+~jT3mG8K!N|5W+BYfw= zanL6%A@CkE(oI2qATw-EHx@E-pll~A1fsq(y<5W}Q?q@SIeL$!K0+*szV++9#4@8IRvF}VFMbC+`Juaa1 zhTv3YQY`ndFhccFNl6OdQ6@Mh|4aPdWPIrC!cSfk%kTRRQ*Ddtx2RU1Mfjb3e7r%1 zds==w2=6`_@bGR@YCPSLDQ}Qx*QT`Z2S@@wu8NqZ7LX&xwqgr*O~UUl!S^;k>yNXS zh2fo?926O^*hcC0YS)$|{MOg_c#9q@1mOG__)y|wX`rk8JrXqIXEyWIzi4jI zVo1Y?IV4_r!{UcuWY;8JPqJ&W_|_Jbaro87jCgNSr+FJ5d%*-?;c z20q^JbA0V_VaPt}6*ld>BK%-42-X&Y@vvcppGdN{oX*WC81sR~);GWWMd#EM zt=28guy8k&L;DoHSw)%!DcM%JaeU9}cDosjlm29^ALtaVdZYT$lPyr(N_=%$anaqx z;w;ZHTT4GG;^X~(CH@^M@&gf0P@BqKVz0`xdTs}W?!k6v)m|4SQf#}LBLDQoF7Hz`iqU+(Im7)Y zT*Ub+E8RUkJ~}}awfCrdxx)-dx*7IXhv8#;i+Id|(@d9u>tjsym)E|Z;ndf(d5i8L z;2kiapUAUZuvK`P2VGiqzVca%?~BvJYKtIa2PWgGPerC?p8(%zvGD`rjr~zBezTPI zYipzV-4G(<|H4mBM0uy+ZejkNeQkYo-lW`~;~^Su{&aE^e#6PBM6@U#jiRYKM~Y-Im6m3O@6C|?H?$5 z|4iMbX|(t0KFVmu>aj0DEW;Gzo1nT61b8%sQ!2B~x_HoqW)5edckWx9ad#5UKuqJJ zsgp5X$s0bc<%8;jA+8UgJY%;bSp*`WN*X*w9|FIZDhR7}YlYv}H~r?oruM*~Dlkoq zODXRZ2#X5t;TvHJZES}Bf)X(!{tKySr!3D>^%){c#g!Ei<5>gX-UTBP_e^4%eqcyeUrfEsq@2QMRw;Tdq<*Z$f{%8J5>hmPnaq{T(WMw zh%D5B0bvPZ9y~eXqo-?Y^oD5V1v1rYMu(Pz+AEVLxlPA{uHw=;w{=Y z^A^H$uGjRbB1^MBDY8oL)z@Tu!(IGyvE|Y(dwF{o1!5~8u{$GH&J>BF zCn2>eS}AX3?$f_f@fGE5D_>@wh|{dRVpVX>=#S@6n0FH&A2|jr7TM~E2ZNuo_Z=HK zLLUevEQft@MU-AD(rxWaA?({r7CxYpg;$UYa+1`5zhCnA|fD{E8rA<-5<;c~j+KYTT=b<}jA|!dbcPPxh_;)gVwFLn@vUIQNrC z>48T!@hqVn@{dPgZ<;)90ElltAHP|I_{HPqtU_%K#6vux*o`dlVnvD= z&}S%J$Jqx%qy9!$8iAY?PmcK<5ur3?#C3-0iUSSLo3y<*ABd6Y*t}Pc%@;9dJaFcj)Qs+S<{tZ-2>D;lT`?|qDd$vx$E=(_!&(7P*tfkT7AH7lx!gWiO1OTHe$yKL zhJ)U{C4#KsU8)?^Zq~7zTc7~iX;6=~@QXVl$UK~RBWfQ=`xlIF49bzK>W-;>3`jcp z#{-*^_aw+dUxx_H-18{kC1-p7Q0pe`Nu}j7KD%xHKx7LFEacmz&=if-z@zVy6DWf= z1iLepTz)8)VLQSrwiKNY^Es9Vb0RN<-=2zWdA_#T~}S zo!qr&VsWDMs>F%z;sHYqSlJ1996s|5d10M-22kGEHmNbK^6Nl)-GJf)ZdWV*5ye3E z)H-^kCG`vqRJQ$|KOfAc%mxj`d?S9)YZ0lb$brafs8rYW;>xw>QQi0LI+YI9wEJHw~c@8qj5NV2+R1zMmU3zp&sNf|& z-tDtXieOXY8iy5mKXRTFw1b9u+Wf$MaO2V<$Rwfia+S%)I;~y`OhZ)EjaBqT84-j1 z39MD*LftJss^&R#I#+o0cNpz5IC@u+Wwd{*5mUawxRRo9VN*grY(5t{TMsl(XI!f(Vws4x{s2LPDMz62 zVY5HD`)w2op}xXZdAYV&h%wrfw?^We+Ahho#HH$3t9Nd8o&oj*$j)am#4wd6mV=XG3pdIrBgNT68zqi{c%dl3 zpBYA*FfF*Xe_M);i%hjFFI!`8Rp-0>>_WMwC~_0(#GA8uxL9nDs5E4SsVA$prUflS z-Uk&}44NCOz^dLN35LO1RNi8}dlzi+!4N*BqU{=OOka^k6K;l67~3i5{LI}j*^=dK zN2BMFB2RGea>2gq5j%7MJbFDi8Xl#Th!sI3m_n9aXKuxlB|e0v+A~e9OcW7z>&4c# zF8o(1`oyFm$`D~nyMEbyP*P2l#D#sDB$i|h2j%q*IofkUE)XYy;8Di(&qajj{K)9z z3)kkAm+H^$eW~Rvpef+viL^zjBAT_YB&vi(nIi~Vol}f5>yq|wK8wO=*K)f*KiLvcwt?Pr)L1_c=aPq?-CBQU%JLY3;YZsWuu0inr&SIx_k83>Qg`lu;N> zXz0S*InR~u1>OS1^Brs(M~Y>MonB$fpS8ODRUM!T@VPUw&x{gLLch`(m)GRhHeNSA z;&Vt<0h-`*p)R9EoWbEuLb{nAeCIE5G z_na?6L{(|5ZdGh|C7d>}Y=$-0W;Lw5P{b8_9xc^hA5x-o2go%5737(eW{X6aG-Q`o zclNXFrPWxTz;It77K>&#O_b%Dn9%L-Nw^W)X=zq%b4Fs1~z z98&vf;I{1;UmIYxm2cDhEmwrlWLr)saiG{-FVyB!)P{G7-?oYbxaMIQf`69Nh8y;7 z&ju|AG#;V#-7eBBk&T>ECFL{Ex8RC>(N(#T1m=kp*5wtmpTm8oUcUp024Gg<$2!Ey zeIiTKNQ@JBbLBIvZb9X;s&@+5oe%jl5%qA#T1t0MWxzy}m$1j5F-Ds=fG z$~c}DhNp5?&jb2iuc3xsP%EFxhn*BzLd-7*&2>H7nl}EsKrrqX=A9Qorfp+5&!bN< zn6_dEhxp2ogN8MW8H76Sh1%R%Gwry*kiya{xj{v%uU|E7k`JV#Y$dnqj7tn>+}LK# z)60dMl^u2X2yCbVl*jWZ<*wM%^hq^HeOu zy&Sam{6^5vg3jkVd9OseIE2{FcNEQe=x5iNluM(Fo4GeQ@kT_O_GHK_->7pyWO0c0 z92`DpTH@>?toMlVcA=M2Zp>E*{B5uKspZjhaWHOv5~ET{u`px?x*3lFDHd$6|5fM0 z6mZ;N0(@B0mlZkYPHoy~DzE?S@vE}p(L8?80j~8FemYhZQMSz^Or6bsG6u;+^}hIq z%R6qa4;_;$C(aSu3=nt_bike3s45(0+-PgR8`Sv6sv)aX zrR?hA5cLg2^DKSYGQnQ>T@g0suW)(twG%qoD{frnm~73r?|9aIW&Jvs#t7w;RA}cA?PH#v;@- zrIHIacz&MeHV8%=qPTBf<0&#^oIuH(x zPm9B>Q&0beN(oQbHN5#1N@@Icz7g;HO$;tOckjXIR zj8@;oouj=8bgez_+r526hNd+t@?t02{=76Bs$77fj;ph=Uy<3u3|n4<$t}+Qcnzia zV|;RFjn+PZmtqsX$Z@)X=Y}Uh88koVN;L$FIMd>}obk*jcrBincS=C~%jeZKyNe8p zFy(|AGapo0gl3^_4vko)#`U!&6m~wWvIWJb8dqZ#EmF1Rgc;tz(!_=bsv|xXvD&Sz zfd5+J$}hAn`i7frj8~(-1GroXUY>Wpr;X-q;4QdqR!IX*|Gc}M4ubU*toqED9?ak@VM`N?2^lnHCcU6Sha+vle1MNfb1t(*W{-Fb2l`N? z$&`C>+IqcMz8IL5cD6o!j))S+SMk7R*@+*dSPATnv3m78JYu;-b>ArU`6Tu7FevbW`~e<&w%0UysmG+Hc{V%(Th=#qqO;EIlT z?JPvTGQ9ts>?9LtZ&PRtTing=*v^!Np`(aAvGr^{{YpiCT)5 zYI<^}fpwmFnR%90lI`G7Qi4XJiIs_gg^7WYg_(hYfr;(#5cHe86cqIOaqR@v-=CrQ zd&>XE2VwoaYv*W2Z(wg=WNt!l;p#yz;p#!cNM~ei;N+C7ysn5MfW!+%f}M&60HN0w z24DSV+7=lUi5w8HOC&7it<_&k6S8T-EP?rf;@1ZinflWrIhOBPA%#~$9^P=MDkJA4 zXJLWY$Mo$jc18~bepdtCO<*IXKq|r)jc_BmKyHY1LkVW9vH*`)@tBHkKL*tRA@Ro?L!biXFzckD-~i4r z3r|4)E+ZuCByj;@hyeoJk9njKAB36WbuuD&Rvj*(Dm-1Pagz{J-mDdNr;>O!+=&&` z?hAE=5L>2>Zj-c}^tJtv>kcv_Kn<+Cv`b5 z*Js0J~CDWCLJS%IwcevU!Nv>!iF zNH-Nel91m+Y-r!J+}aP(iYM_E%syO?r)JpFU$qOvunMM{39eTdK;XcyDN0H4)=~%? z)WdyQi<032`CJ0saV-do&3i3L$=l)biq& zkDnkdd?{G+)!unHN-PJyZ!ns0t=&zx2r@EFfe;%MJEkCN`~5GM-DqS-PYf-U z_(A@OesiiJ*=Eph-}E5`5hM`^01dPVIBFRYsLiU3+djMN!Ro+Un$r4Fki{nDI^3igyA?62a^4y;XD$kk_F<(LkSe| zgWeMEq;)@$;YjPmk&6@5X~Ibp)TzRmB$A{I(@9dK3Jmeki6;e(4C;Rv)rAgmL`G_O zhYTIkboUz4M^>t9Pwi={3B9|$CzrgUmsv{BY~d#q!06*X2^qv&gg~{U9Gk?VTctkiLqz{9e|F1tB>iH z+ECU@_g$6XK!DV((%OM95>8!vbZ-Gq*=R8^`MCh9@<%-u{zD#OA1KXWDQqn?M%}YD zh~*6c^ZO1q4Pq_F>%9R7Mf}WiRXf z@nx$5lQL`KR}HPf7lr9v&i#$5#*gv#oEsG zm$ZkPz190KGO}8~YH7Gq7?`LZK43TayDZHL8ddbH+iDFA^wml}htRkSvC61iJgQsb@iSdk>EJL&6oH2|RYZW3@u1QT`m;4Ra7op!+2J4RP?h!d4ivnvOti+(#V zYzq$B z@S2@HUdG6DEn2P5`Ax?xU*kx|bWjf$Uc4r7a3>g*+44s} zYz|t6KesG&EIXSED^H-}?_K-_D67XGZNpk`!*Z++$*z>(5OW13oRVLaH8it~RN&T2K(JH!3V;BphGKy95TvRx9@Cz)|GeWEiYTVQ5vH{9Gs}lS$P<9KN8mh$^y? zu5=z4c1le&dP=Ze@6zQR{ZoEateabMRZwT^LSz0$5=|jm+H_M7SctN|e@m`iH5Tg% zU+rr|N*f6_k3>EUOI5UBPL>~aEiL$A7+1a3z?cxTP-Vg#(o3{HJMDa z2lrOAZK7yIacv=kl6UQM6HRc%u!HN6+wJ=zsq>Dk15cl|uONZRPOX^7WD~C`h8QSc zrbEz>3B@A%n_mMhW!kGt_WItQil$VFiP9bLFDQrRYzz~$A~htkKdbY2__*^{s~MSqVpa~Na0w-<#R-rE)^Y05eXKsq-Ln6Cs7ph%n5YZ7U{HXOm;Ee z1MQUN4o0RQuGr;bv5drtwn`0f=r&X?vzr|kYrb5Z46=T>xPgJ|&CxMfkQKE*hQ58I z+>XF`Qte-1EdIGZg0+3=@vHn|!fSd(a38i*+o!Co8bVR+fR)p1O}!3Vs_8dU{X*ce zda$}~U6OQ<(fsZQp1=H;a9M5rdjY4kg(+X!P`e#)@5Q@>2`?#+Y=Ye>f+^&2ZMte? z*SeD^wi{XB*y7=S(Ps4TeZp8@@H5A_%`oT&X(x#7lzh7$!S2g-Lw3rvh&oDnCbO@w zgLN_1X9)QNcG3Wmtl1fE7$5lZUOtjT3x7)<%24yR$1&i>F3S-?#H1)9x`i?tvbQpY{Z_&Vi{iq(}IcyM{BPQ#oZ46?Bt}i zkfL}Ft$YsVe#9P!mvt>=d{og5KDtQ5$|@z^R1DrqV!ws8Qk)qnFqh@bddhMBeyBEv zR@MOKfg;81MVoO^uAt%SC-=nbTi>pGuwyd(ysRObFrjwA4~PQr;{M1(Ly*%!qGu6G zSrn;Rs=op`sZfqaNL`tLN_7W$Z}zYN>n)6%9WY}{K8=g)(i+S0f^>6jF&Bj~#MVhP z-CMaQ-6?wE{n?l-zP&7bN=-J0Cl-2zjw)AB8S9x8|Adq0ioE_-lBf~ER3*U8V>w1ii8sr_0m9}+a<6wr@p>d2-x$@47V+L~Bl^`R4l6An zDMO-@KgNmX0mi}mn&BAQjSX)&uD7YIAk@Ir=t5KtOvJm~s{D*W6nyV^^J^InVL9u2 z(M-9-bot5lF;_tH?*jMoGmu{gFAfxM+n&Cv1$2ETE zl?NR#o0i^-8S5IDEICm*+geISw0A4yg=MY^3IS;y1FH@ zoYme?BPh7Ye~&nff26=*-27swLev#uz4%ndvn3^SEavutg?sI~BddKuT9(dF`en^| zM}QSmu#(9iZu1d3TZ{ivbn|DhuCC|9fTlo&&E+|)sp_8FfnVE)ujXJXvhWbeo6oK~ z^?6RP4NW~(#+1cHvN4FhfrpTC-Hkd-68%#-R7@aQS17pgk$i(xCwZ(ESQ z-B2A3^#%2hPR8#6{jmr8?Hd*5f78h%|8JdaWant|Ppc?oXX|X@?)-1fT&$+$gtG+r z98O@pHE&EHo#z+fRGSKl;A(oAXeG5+%Ylv%Q~=)ghbns z*v+;r9vh}D34Ts8s3Ivbv`n!0Hm)cF3NIn_z)s$qmw8PmS~NzfuCm*yMDEG0{q3If z)aNO1`vb36_g!>^-Pv1dq{Z1=YsAgDvuvLfm;UUzI_B!+xjP2m+8_7<3I%?N%0a?F z!$4&}-^+E*JnJ6_iDj}JHUOH-P>a_BbKM|;o^j0xHpOH$+yy!Uaf#Bp-3z%vXCKu3%w)D83qHzM9Cc)AnW#rlHm!I%Wy|eB+D6~M9~{8mG+k4 z>w%vbgmWJq{H>q)%w;OmNd#BZ&wL}4?mp zl~re2<-A2@yg)9StI+I5K*h&-K|w9#a8%!Vet3K0FjH?vu_C>=J>Byw=087$R6|M6=Z>LP9wthVSHlH7j=A8=$-qOMh>$E|$ukh*@>kjBY{wA@ zKK^2DrQ3tUi0V(ZEFufr*mynC!-y_S)&}&NR&G(ot<0FMc2Arp$?n_#nr1G}r(Y9-ea6e+i zPrs)ZzqYAi_qy|y)10iqby+Yk(=Z8#EXOe=OaL03Qk(Uv0+{7E)e&#Tion{B0LV=` z?x@(kWqb4k;emGezEN=1sK^%D%Cg@=+Hd^}!pniUX-qHB?jNbi>+($UVr#l@h}9f& zqZn1PGC$S6JnLyFZaj?3Kck`3)Ohf)kWrTo<%HlMCSq|DDHBk#soSa2eF(GKx$Qxm?EmNJR9m>9mEw&e&{;)WxK`W#va}t91-QXS5 zY$$QV@MFH79=EbmdEk*+&mEyl?M*E}KxcRNPc$v12)zqtfFKnbW~jg~+hujCj)f@U zkO~Guy^*E1io8N9ilujJisW!w*^f51R>g|VOU>ZLgAkKMdo^WnTcER{{)CY@_rNOG ziP3xfMHqkm?;9~T($CT90sPf_`%cJ=d&xnuXBP}Fj_Cd715h^7?Y>tD8xXBOb@6v4 zV-VKaVrQh)R`%hzi;#~fPX)LwhSZ30sa2|@kDzLU9XHgqq)I&^Z#-)z8?Bv#9%rD< z!4tT5$8mK|c-c(NQ-xK3gcI_?+?F{Vpt&7>P(n7~yX`}NZAbb>8TjjvVz`MTC1 zpSnbf%OI-NewZ{mI0gDXsM-$**9V-@4Wva1dnLI?;T|EiGfq~m;)Dc=VOGjLlOdxy z>xteSTqARB?Y8>o7)VNZ$kdCrrrR*t2ar4w0^eSEL-4YSQu!-A+ZN;qW|2Lc!c+FMF?6mbMlYFdLy$bm7j=O#^uD!Rm&me9S z)t2wPqNT$=x=Z0!6Qizj7SXzFfLb}nU4PiirL(Ohz57u&Uxs28Ma zePm)2j{fYuiOu5GV#R_JT9Yk&By}dmsMD&5(nCA<#*Ic{$}W@z)%hfaey%RQ>&T#g8C_|%X`wu5Og$3Z8$3-mjD6|0yG!@DSFpSk|!YSX?h8dka$^# zFGNICKv$&QK95knp%JVyP2yjGT`Wn|VTVUnkr54x+1^{R#(Yhx9<#=j)gd=HHa3u zS#pKUpd$zu*^`8aG+(XX7wsXe+`N|$#)k@vbUlm?W)t~Ug=NP8(-nP)D5bftLLgNJyxEF_hY!a#gLqO+_oE-oo9A#S)961b1Wgk0~g zIY5cCsJ|B%vcOTW3qQwE z0VI=G0r4$53k})jd}H6SQsU7N?as2yGO2lDt2*XK%^A#YUd=}oT-h^@#gSA+P{K11 z-9*i*5Xt2tlUj~!`#yZhP@e5u5+DZEmXXbB=zgnzeW`iROb$UsziX>{x}WfQxode$ zVacvo@Q-+N@u}xThXtHuG$)HYp?4I(;iT%0Ff)seW4liNvPkyPOL}MQzgfEl%317| z;4sfX-7ar=1e96$$7#s>RK;B>=FYtQnuzA^Z*qdz9efkfRktuGE)fkNWxJm)K$xnK z>Ddi`Q#POzSkPTy>cZ>x^KsR@7TM|WSh?oeRSy-WqtCK!3$1nTZ5)i_k4K$Oi=V7B zwyM%}g(;>r8Sa^ls0C_kY`Ri4vTZ5oWx3AD~k2jMnx*M<@dYFnGp6{*)W z@*PRTW`!m4F^fX0JKYyEXWJxR#+=}r(Cc^FZsAU$qBI?9+GbGAV%DbbGqZ8z7!XiK zR3a6lP2tF_)sAHz*zlaNtt6|OqU%^nsq>+pI=;p)%z+bKslDC?&ho|f;+BTaI?)?m zB%l9i+tO&_wLI{4W%sN(3avG%Ms1@yac%66l>#udb&JP(@9JS5uMR|w|laLQ~r1h*1{wlKNWx9n(SpR9gRCwz;Srsr-JcsLo-`U_Vf#M4laI_cB!OcQG>zHV6Z?2(j*~n5y4;S;=9*HQpFFU!GL(qCW{Oly-6D)leTR&PhYvj> zrOw@5b176-ckPgK6&c)-em}U(nx(DR-%j0PxDm@B*Apzb?NHr4V*3gL^9nG!N0FVK z;gIXu0sV-Oy2sus-oC;60DeD~(=O?`27E-vp5QM#7ib^eeErMRn$v0G`}TLF-w5~L z822CB>;GYD{jYZAe`DN#^Xim3jJMWd{8uc^39r;lDl#brOn4$kIL(+VophWNCmCVL zAWV!Au^|EdZ9^e}R5eW~cuJx6Azdww3z!DNnvSK%+m!Y(743%J%guJ%!37w zC>P6Sxq>rLx%BcMU~MRdnKp>*E5TG+`+yM7*ih(uoIoE?BM}RajZXxL31&l19~wa6hVbR#cf2hr>a7E<-u` zTNcvuCjw~?QrfcRF%BRy6{JM37T*C^2} zyVN7USsZs;MU}33qf7Q6qBlSnuF;}P_Rp}gNo(+cn9miW%qGrkzD0A^m{WjmNsnym z8mAjKtSJJB$n@HIj--4Gl&QscPz zuX;H-vS{I73GxS3wq()|s&;FIS4(FFp1(?MC#rcxIacH3wo;^p4SA(=Qb@m(5YM%n zE*FvS!{n6xV6M}+!WLUM4cjV~jwJUyz31cYTrZ0}-_*m1A&+X`a{v7^X}jZ;EwfP7 z$gYSVIhNIpH7%t%)19*Kv}$WkIPJs+%WkI~cxg*)kg=+)-!4#;X|iDJi<*(65tg%H zdB>JHzi^--?tMBM)OIKn@4Mf;tYnX4crQjsf@i54?4+bVmW6ct^oWtN5<#T>Y z!mU`ev2_DPb;3X|z=-hJVTtlkMc0lcZY=oblYkzjkp7_biB5K}_lYihb(|5tD%R$1 z^*SnTtk+>Y=-!@AMGY%0?MRIwE9smte))w@#jTVSAe#LShs{bUnQw^3riYC(CfzC zDpat2$RhfaHKCRF$6hJN`ygFU2Qx<0F#fo}VDZslWD!CiwkuSr;iD|Bki%*`FFfoHYwoowF3`}a~uyF0h1UM^r?K*V#_ z@?PYjcFt-r!CE9^xkRks9E^7#1AQ%d)@XMVoD}Ekg4w1qWWUaqjUa2f^D{E)BdBfn z&=$cDs9@spVlL+f$?P}wAsCOWwAM7_*L(1Q8|D+G zzPp2r@C;I(Y^5{G&K@}n7J@7hP;!sc6IEwd(z@5(62XjZCh@#iFVe$+dv2djiiEm1 zFXA?IuB#8)N;?=_XN_Zd_2}TqTpGmZe_uxLnGb6k-tV-ucx25kKXF-me7fPHid}m+ z_K#PM=WX`>x{?@#dQEd_+1fU|l--@ed~2??t0b=LvKCh(4J~5Y(!sn!L3t=^W!ESt zr+(Y}DA``oak_I)G!(;&<)2u{MTIDxG$8mNE!gsrGYyuVh^9j!~0oS zbs}TqI*_pgbM8HJtdE83c&vuJd;G=hm91s${gUS=TchM;rX{NS)}3;tEfw-(lQq6U zONj=Rk{D`ZfzAr?cd49!rmLE&T+O`RE@16}&yzxTNw;14pDjMROxOt<)&-5o*m9cm z-<4vz3wYt_$z^5q)0Czc*vH$n^k9lG$R}%)E}YDkQIa~c2e5HX{En=0)E{6S^OR>h z3Vuup2bM!-W(OvfsK(WLCd4ESiqQs4l#?tbK}nm$N|7>_uh3kH4lgJH9^)%{#ynnZ zRT2#wXcuSEhA=4xgx}yyNA+giS%f~rhG!_Hy--GeK7aSkoRHfqonjACnfduyaStU5 z#SFry4*1DvGtgta5;!}V?!o5=t9EPm{fp@R%q4s1>j>qBLsBdcfBK|86OJX*(>Hx? zKNvyeo#yF9C^OW775}?oxz3B?x=gvANfEkfk))5W(y$y5l!;L3kDe2@^aSeLdvNNk z%%h&!3i}PD&AT?AZ6MGLn4Zv3 z$GNcbs9Kl(C_eZ?Knh4wUKXaW3u^Z)5*?x?-Uy@it^<4^v(b+?=ssk*F^0V$u-2cV zS(ADKHWcbyQC&>hsIV!?P)h1f&`LZeIrl5=%)ur4>0(B(%(|rQkP)+t&s!M0vKqZF zey#q0GA{TLmqY?><9^j(>t3yjK?d}OQWO2dwM6a?JCSW zgjM$&IkV%H#_k*4qvqtHxF`5VlybOnUq?z`Lbf7}*5#gZN1zdYAsLF*r8zmQ1Dub@ zuNF!UdQ3<#j7g-tKf^_GcD=89ttVByaDoDdKU^Vs9#Pi>A1V$RS#U&LD0WoAD4mP! z$B>#s0*3lOK;XDy3aFA44lBfWiR2t$F-mGppb=&=CD*ulB61^T+~f@));WWQtR>9k z%Xgot=#~zp4YDUr4clBOgE)qSY|lWF?wny$U7>9`&#_nCKpTF9u5gO2K#6ho&J=*o z7*OSmNb|`hyMx@_5OIH6X^-|y3-E`C;*5wQTEKOOXS;`k`2{TOuH$@O?GSon#O|2j z^iw~BcaLE8%&htP*X)H(z7Xepz=Rg_V$T*}tc;jwYlRe-*z;%!W$HA9#@AkhfOT(^3o+cM~ zHkEa^mTf60UpaI0E+6IOsqw0|IK7k-d>z9;R<1IW0DzXCI%7S!@7#So6KNx-s))VZ zTu}=gQMNIX4HNyuy0-<=JWiBG$;Bp{0&B#36Y!T|{-S^=89h3Waw5K^t}4oJ#! z60ntl#q~oTIrBz4hFSeF`-+RzU{aQgI5`cM zU-txP5^YrMeG<*bOraDgCzNZ)0g4hbmxre02sv4_iVb2{7NR+B$(=76Mn_f$42LoE z>*_<#RKLe^9fsaH(eFr&!1ORRU*?eyxsnF$>{)#yt!}tC{C~G)boY(?d1}qJ6L8z5 zt&Z{A5qd!E4t7hwHG794fBEv-Naw8jfi?_VufWS$2Fz-VXIYabEmGoixsm=ZQ%uoz z!go6>kRcE|FYTFSx{$~cs$1}K&!jRJYXQZPlff z=Z@WbqkwcOy#34#hpcHrjkt(;@*ztpL z>>8wNf$(ZhUD1irre7UkHpQ-sahNVn8y=|CFZQWjV5KOwV$7}{ux`)!@CCCx2~-zl zClY5jpxum8ZHLHqrrtiX9e8l&wk_ngXWtC>I-%nce%i5U{~j7{hj{;u+gp`R_ackk zFf=|{(G1G3i*<)jQJ8VMGof~v}*M|+LZL}JK)W9!Y6fWhw9$T{GM$6 ziEZN^k>{T7&n$laE#;(d=wccsh;nNZaqIgX$BYScZr{6I!kvaSn9>$2zwOqm-9IBm zizy+CaKvxlWQqP;gZrQSIB7dGIeTXdJ6orJx3xlGuN4{r zr8(6)T~Rzs2u@V1vV83UEG$r)E-RUfVjIuqtF!?MXP+Z=f*Q9PaK!uEu4=TP7>k+n zn`oBJ!)#7OK?_YFbDD_l&J2v({TEA}$Ialw=CSfmPB=2fJcG9GPW6o#V|R;K9@WNe zHY;Pck5*cFOvp%knwiZs1@hZ9U3b6-E~WzY?4&kiVL47i>eJrv;3#DLkGcL^zs+1R}vvV#lUKdV*yFQKJOm9ra7ZhccXu z?-7VE@@y&qFIn(rKiy*XH)oi(wRQZyVDiE|uu<5^Y@}Dyni==~^GANx0QsO`S`lgP zxyAR=BLO_@3P*3?-kViAEJc~M)AYowds!V)4)XvhaEMq=SqLSnDE3PJA!e#%XXs@NDdz*Z% zdNH^LbNEuO@dQEzEjx@j;>GbYQi)dOnR@Rs1oMxI^8Qx&5k4GUCdP(Z{7!6EJYl)Y z2Eb`7qbQtAzncz=6(QxC^dlOXdC&M0S*}~5FOE=p$Z*)u^j7%+<@Cspi6wN~Q6O&gA?cftJV(yAfEtoQx`N{zF>@_sWYYfBTr>mJRLViC%I z-@Zi@il(?7@07#?)7k#7HQ%PreBHXganSo;vytvU#DdYkcHaM&-SThy<==^ztfHfY zt@6V+1-w2eIPP#jaSfSzEiPzyxkhzOQ&Or}1`M_lBC=j0rIvKc&~;(Y+^n;%kU1tM zhO1!uei)s;brut3WSYY>kol8h7->ich>v>SuvPl^t+ zz;5ro(^7mxzgAQFN+fSgOsVaug;&|~QrljCfNtJEwWl)|zamtPOc|Lz=C)BUQ#J_H z08B$%wAF;Br$Q`t%XWC_Q~;hF_o;m!8+v0SuP#Hq=+^Fb*-Vd#X#^*4&RmsqpL+9D ze++Hnn$tI!!msrY>yO9#5VNmBY~9O*MI5*j>JSsHQqsmDK^%B5V+A?-6HHKA@X1C~ zY||Xg1xjMO6*H21TVU541 zb2aT=-xE#i;+Dk_3=T47b!B7pq$K0qab(tR>vLwr<5<}n!AWT?#WP&%(m2OqW3IC&-)>8qlRqCPG~Lnx5Lw8h|0J*?QxdZlgApG$Ds7 za*hzn6|#bEA3}y?Z6kLHPTUf~Ph4`1_0H>K87CSl$>Yr$(*ksVQZJJ#%=PLNU3pA0 zT)jOx3s-+>w?`UvroVS$c9|nR!K)$cr8sPLI%Yiu1-2AGQNj!KLs)*8AHd4v<-3sW zDPXtQAB&yhIFFN=)#Rf8zOJ_d6p{m7MVkTg*u?xon}aBo=J(ZJ%at?(F^3Yr9ebPL z=N0o0doqS7JQ~_fQCRM7aU-nrsrNR25>&8#1au25iD7cq0v|_rAQy98J8=SH9g|ea zJ%;pz{iBcje>^sT_;eNpTF*IEKLvV6rWM3Jo(h+UXB*pDm0P+b{t@@d<}cB-tj%bb zKIO@AD-YGs4P1#7W=Gnf?Z3)nn<*9lblwaCTY{;HdqtmKY`rx3U7^z>_S5g>bYu=t^D4g|Bju_Uc9jMC~&h6V5zkeATRW>x-Cg9(`!NUBvV#e}c2~6l8 z8WXm2v$eJ}F#g~4U$&x-RKGr|uWspdZ1ST)i75fvBpTgu{qgX%t~ovSVvs0ZD!M>5&1C94FW5w}+1#n4KRYfOVoOJCXZnQ0H zk`ancTo5~$JmF^lhR>Zar;O*$bPO>L^-AP|oU^wg=DB!MlbJd#i&jlN!xNg!T2@JU z)7op_z@{eO)DTNL{u8Au7{=G`j5N18CI(%q_LEH}ovT`k@+yqNI;@rmp0E>nuvXL+ z4RjU0e?<^Squ-mK2xIK+AnZ2edLjuKp_gZfJ8rnxtB=IL4Cj^CbSV5A-Mo57l3BcV z9xcz;Je`7e?|7CgP&9X8i~IsikS&0;$oF5+GCDi?c>JwR#9;r2(8`!N8yFin8~pFk zCfUd>$fJ(T1lw$i#xvrf0_5XX(bNUx<>4WR-J1x<$ih2kwVSyr3Q2AHp0mC4hld6+ ze82H0-S|!56;%n0COCP`@P2LcK4l%gzkckK18C>lg{6>rlgv2!negl}g@Nh!?;zQX~LmQkdzE&T^0_P;!(5=E7s>+c(VBtgRZqn`tWZ zot(1gt|9zZn-Cfh-4Op;7FO>;YO&5!9B*aWEV5@IC+1}Q%ebvBHZ30z0yPPnPv;4<6Tk_=5&`nuF=9^Z*v<`4PO#(gU0N zEa>QQDD-BiV_K})b3f-89yS;Dg*L0N2OxQb@;9YJ9~?g|v!#>ulBt8|GdHIc^Z$xu zdIrgqDy5E!s~>x&A&B2qV(lTWNiPE|;dtw>5B7*t+s z0%PPu8WK^F8M3@XS!UO8v{?76@_%>k`)o1D^9_{ULuAJY%!E{ zrw$2ip{im%8+s551p(s@-kd-;Q~i+jv9wD>K2?)sqpYqcljs)?3a^MK8|o&Ao6}j5 zNYEFf1eFi4Rfrfi53=27M?A;HD)Pw6(PIRvXaiJw6yBYn0C!nR&^m#~JkF4-_xj0; zobiI}94Bq1lo4?P^Y%t@+0O??W=OB#W6!Jmd&$18=+iqTL z0(ijT@)06lup7a+&puem|83$bn1r*m{1t2rQ2#A4`2Q>C37eQ2xL7+Yn%LVpSvcD{ zdi;w7WejW$%>HiD{Lj}HvlGT7!32;(zG4!iG*}`hrldv*!}ZT3Sct?2m6XHwx|mO* zaLJg3%*f)7khX;c06fp%_>$CdQNB9}@Ho@T(Bi8+ygt5z>SLd=MX*V-sm_b{PWuxk z3!z7H{vw>Im9NraEVHDZrNhX#uO?M1WOM_ieIYm=&NnF{cR}OYqc^ubg38^zC&zHgr@KVqU z>5~WSwpxtm!0NRs+C2=3GaP7*r<&d55kC|kyC*fj*}YB<4RR@zNcSxspf%MCCB6VGQ*JoFFz1rwnOBsYL+-`hlR*TY%)ekXIc(B($7r%_i3I53 zoS-@D#}6`drG4%Isvj_8UbjC|Buc~w+64fW;&S`!;+fCJA0W${+Q+nG{Y69b-2b#; zUYFUH8h_~x_+O>B>i+?Xf(AxbF82R)$S#gX|5t(gU%hg+%Ie>DQ&D-NEy#iZ;1Ev; zEl8lmwq*`R4zZGL=-n=4GB`LTBk6b$w3UtTu>1p5AjwqtKG%gbcI42KiYb2%>3Dr+ zIDIKY-M&IaHPpEH-t zq=8`;)$)SrGIvCACgo3YGmq-aaj(^GlNAsT!~bA5-e+-So8AeE27R^38lc2Jif?iN z31p+oI7UWXZv`f@MQ}{klCJAz0cL{tBlGq)7UD6KVd1gsKfYH!G7dyr+k}>8QRmRZ zoiU-Q-~eEs!ks&h?qmyiw=var-;Il?;-mHpFsv{~%dS`a4;vnwA7Fh_6NWA*_!3^^y1A&hpJT>$wsNf=&a{7 z9FGr>khOX|A^)o80+WgA2UnynfgO1RDdr{yY3?}J=iw804*xyM zhVob4)Va=(y59zmd$_8|;qYdA4AY)RNQ1wNbqb{uu2@r z*Lcm&Ibq#BT2a|y9UxiR*>o9N|$X5Z}bXSQ9 z^l97uWujgxp!M{|5~K6Gwn{l>OzqF zxY`~0wqwru^P9JGyT3zj6-IMmivPHr>I+Ts?}|>@!v+;TvBT0b@9OcR@=o2uJC*LA zhj1#s730{--QtIlD&4||QYyYN<9aGDdMck8<9cc@*i?Q>hj!)fHpxAOw{9w*Ipf=E zFHlrp@`w24??%af1-Ei4pXuXYCATvwpTXlj$}ehEUmfFL`L})*1wYaLu2rgv(5obs z6|_*jmKml|#VCpdQ&FinG*XFH7*O{)x{RkSLisg>_ps+=ni z(W$%(qIIauqSdJZVk!0%M0iTVd?MVn0df?3 z5+d)4fF8xc8nik+02JyCw#d6GK#p?HLgZZ*(4#iU9O14Cn4#SB5b4Yd!$7-%5_wku z_$dxbMLahUeHMhJqTNIg>8cFsq1|K<>8cH4N4P5i>?rqaM7*m;KO>yiraz{i_4pcV zsbMz6caFf>P+G%#ZNM*o_FjN{0w_plQ+w4xN08GJz8}?9LTd}{ta-Pl>DYuqSc3t% zVR(M-$bfhH|IEilukTseRc?j>$rab<0;ao~>({Iq03PT}kJb%ffW_W?9r0H9Qal zU@t)s>o1v?^^+91e~v`-NchMDYD+9HkT;HKfKEOuy5j-rM|#Z8+H3Ih)(dP%2!o0`yUcvB4?R*FS6yKQv*CoD22R^}g3-7>z zeu?w~e=>q#01d!nNbFI8bKqp_2524qhyYg*AT9JW68E6_JmVt)um^cJ1ZWS*H7JlC zaJ>sY4jo0XLnbeyABD??6o?R150qy^8iy2I{YioSJO?fc_<3-*dI*k3evJo=y{L`n zmDZ;Rib0wDIBnI5r0Yu0xiAGNr(r?zSbn$9^vb60n{ zT)BZ4(9hxEmgn}oZA%a5HsJ5}ph7TFjROeY3Anb#?CZNn=bxZq^{Y*E z+!BF~VDyS~%7JM2e=2`dyC#O+k;K}U%u)NMR%ZiYkHGsG1sCl%FhgX|2ZV#MCbNeF zdO`Du=SBAtgZoPDV1mvdy90aiSwHE4zdng?Sw8jRYhbvyV50p4YshPXy=dUo3&Q0) z8OIYn3&Q-LN-`WL4&Q)4??^I4A>bf^Zy*_@Qx6&9QK?10NS9cixnY=o#t%tjvD;Es z#;hwsq~F4rU%_Oe*O@H4f7FoObx{t5{|9UD6kX}tZ4Xvlv7J<$?ATVtwr$(CU9oN3 zwq3Dp+yDH|cTSJK=`l9Hveocl_8C*y*oGJQxrzprHRbb#gFXWC5}99nsYFq3+4p+tdPbQP)$@5)`Og; zI=W;|D5{zEVvzRp=8YMVR7^v(w%k<`8Symg)+jA>7NhcAQmW$c$Xp-xD~!}zyu2+d zNuS$hX~4~~+0TR7j%TYVEb87B$PfCZn#m4vLrZcS2{;+4C334WZ2aUBiy_#<^x7lr zuG7&4ulqOh6Y_;~uK6yIxyK)!ys(haT>^#(Tz+$X_}QV^5+Up_W2vGf=WZFZk}~-UEN; z+D;$@thSa%^2L@ptC_n=22l--3=lBD9HVOqApc5+%|JD^iJ-QgbIN?h;MNaU`Rd_3Q8 z!QWf6#7c<}^&;^36A5RYgwJYu3F}_wNhF=n4^7dtvKzCJH|q6|vm)H8ac0?SGQPw} zm|&JQvJqVLfI%B6+%po<gmXk_hND1`$;EUK_Rq}Eh~n?a=^VAwfI z=Pbs}YcpjDX(lhb+{HxaBuST&#-agh!;BsdXsCZ8%K(o|Mr)cEE@PImrrCyCX@R+pV%S` zM7fL=oH1U91qn%j*n=j_R3)O8*j^hU9kAzvqfZ2HU~ZAwhGsn^q{tzuASgrGDcDfs zVyGMP!_UUwArbdE4Qv{sshZ9m#g<{!o`E(4H&uI;A*iMBlcn&p4Zl1I7n#-a7kVr& z6f}~l=YuGzImscEk{&@52R6Y_M&|;o7g;hMW+I!WK#&g9QdL_)l-pnzf(07-kkN6? z2Xbexct?)4H0&O8>qVB>W3Nwr;UAv43HOid{&HIep_8aBgg1r1O0Q@Teja=_E{I8- zYbCV~&rZaMTV-yz2IGM|hfYaBh)80{2sI-pSkghi=0!S>%bMFa7$DJcX?QH+=q~fo zO@4fp1mqZH+e!}%1d1FA$GoPi6ZtK3tc&K!UDVeQI{ zg=hDdOoCDmDi+~FwX(a%jfZjX%sK5(wjD#;H{{<;j4XDw9EDA!P)o;ro8 zZn;fkD<&}>FUt1tZ(VeeqTM6-#CBot4=v5RQc`Db@IR<47Kb|uF>iEFtYJk?N)^a} zW83^RkS9JmOd(mTL(=W8o%}2q=VOFUt_nxf!jI@nLk~DvfjBOGp=AeU;RbAD`QSfl z1y}Vv;T0%YE=sM#jCUnOa)dxSGoz{ZV{#Xr+KGw<@6W{Wshz>3-4 zE*%b+#7agHUlpHsZs}c9Q1W)(E*YNW^~KkwQbY6n31Y;$U!_o45s8k2w2&8I^{NIU z$_m1t9CFL(9ja$H_w@P0vu!KRHQPbdhSom803wkYlM}8Nd(rEP4_->Ifh|N=gL{Lg z-ss@z{kZ>A*F#^)S+2f2?R0k=|BN1dhxRIPtMZAPn+vBnR`{1?0TokM2GdI1fPjZn zVfXCYuU5d&$WmL*bSK|C0@H@HF5E{DNfk&u?-Xb~9?AP}A>-t%g@9ycXd=H9JIBGi zFZhL-y^Jr9Z&U?PaYF0~g!^R$Sa7$4llyDBlnn4*_D&}9)~T{nq}(r6mmWB7)d{WW zBqSEO5VOqA{@vWGS5qrOXVM4tVswJxe9}Z2Z zqN2IJey4W1khzj2Jz)g~R8$H)=bTBsIbk&YIa*=?i*KVqHM~A~>;fIMELTKTq_2_C z8c?J~xAJ7nU?91_&)LA;;wP}#sn7eJ%F@uRVPE7&QKGH4Jh}SbR zq^nQvj-*SgQ?*=HGsqtv7!MbB!X01Y3c}aHA+p#DjTa+N0FqK09k1k0^Ts*r^}qx^ zS{4rZyz(+Ow-_i!%IBT+Z3fj-S0%VB>+>sv5j!{B%O7*Rpp;kIyOCc)4*Ey06=NG8 z1I*C&Dmt63lXrB)YRqL>lin|Y06Z% za-D2VRfFA3>YdAVeT9h;)K}{2%B`v0 zRu9Mhw$cXbFl0z38yjdNd>C=6uyQI0L7m1`F4MckPO~tKZtTwEA)tYIELdorZCH62 z>V+u!d9+!w3Ixk44BR~;gL3BH7FqZS1uVJ9MGSp_)PScq^hQv+#=Q`grFoPf9VoVr zP2M->AAEiFHyuV#E9(4!*(7p?6_1a>nSBFfKQ|RddukjQ? zDNz@KNVhJ!po{^P9%E7EO5MqAIz?k+3LZ>v;WxLTn^+4TLr7F2#WuiC)WZFQH3lFz2lUfpv#kS?C(1W6BPV|?8S z@%sm<$HHJTaF96M)I^NS!&XjerGgwoO96#%=Z3#>an^!ZHIfz_b+DjPxJ3{~24WRW zirx4R(=*hkhP=`QGCqQK9y^nev9=ZxQUK$&I@omcnF{D*tneYal4sa3^SpY1-aPxv zc1b0Z2Y_i635%-lNG)YWs-B@e?jAf-u6D(k#Av8z@dz|{|FtGK79}M{*j+W@Heo-h zN&VX(uX6PJjE6s|y;n~S^W#jb=h3C*m(2AhKxRaV0guNYBg1j=Mk$I zBba3_Mi!>Y;l46tc`EEV8Eo+snQ{j20l#aiCWO5*s@Ip^iDQ9LmF~9k9tO?2$-(bIn=*-C}MhQ*j42OU;>`1xtmOH?e7COAj_A1|-f2 z@5i_fgSS06j&2ZZTc0Q=JUml=Q67*uB3;NMOQrcBMS!Qelw z=(j8oX|fxB$1o1v3c$0edK)~~)=6^sc_>MgCysXllmZz0_t9WXN*lmnQ@{f5J+BX8 z(}{{XxkvKn<#TUk`n9@=({VO{Mh)!31T=?^%da|`ZVQx}1b7JA8 zZRV}Q2!;Ji0G)=tR)lwxgx~>&^XrQUsGa06?nY>Arim-Ol~ThCootn(;*a3YH>K18 z`Ewp;eRt~V%96G6A1J-AXQA|xeNppg((%$ABziNn!w~*z?s3M5l51nP{+~1U;fIC$ z7s_1NEOWf>1=B~I;Pn#bR~=HOy$#${yrJc~*3;tkBtdQ>kh1k%#f1f1hmS=pWa~vL z4)|qnZLIX-0!L*V6_Z4tsusk8iH($hv0$8Ot|$S%OqHT2a=p5)QCliTa|XDyY5A%L zBY(Ptl7inyO?fO6mRP;l*?8N$_(r-(Q}t11=TDu)s1xR#O#&N#&q-s#j4Qj4BF7+q z^rFqlsJXyDV<Npa_&qcAkSn@>ve6^NuGLLF)M*FQ{D4n(-Y02}{nYWs4-g<0a;v=Z z%10a!rWNnC8M=H!J_R6*tLLP~PIKh)sut z;%vWgbQYcS3WERxvJ>6me5`6Q$cY_t8rEE1^`R@yo3H=`%uFF zXwxcrb&A=3eZaaqWvDJZbnA_@6A{^6xtR_{b#eQ2vqM?Gbpb z;aLU-6zCM#4x8o3IdtWxu@P>B@r6h4&lPCF=MR65 z5U7pd=Z64-UNNt>rB??>V9|%;LbrH%3m&r$;?hdFTM^S<*K4o_q7k=;3-OH zCKqNZSey-FsIOI)-$!N$$Kz*Nazfk@%@&9YncK?4B6Ke}danNWt(eri-AHBvW6#Er zAiIT$!0JwQqlX?J2+K0%>b4&)CKpW*e+!P=U;FG)1A?Y_it&y1oktKlP+FI(5JFb1 zJ3sK$D@gC9AY0{4TBZ zPv)OOgt?Y%>sq5bxA#N5gr~eOhn5x1#&zDvYTZH4Ut`fe5~QCYp3f&3tQZDa6Z;08}N_e zZ12X5%L9+$&aQ?YZ5S0$x2nu(!D%(mM3Y8iF5}LoYamaCqElGEjUr@kXWt3p_o@D( zxBj&p$6r4%Mf&0|H!X?i7f9FRg8R5MX2TM4GeM5*8Q1X0EzO1XR{0fTf*Z}og&uOW zwjwh#6RV57&wda+7P(-;mtn;SW1^Qbrraw=1>9Jngl?EjK4KOs(&hP{BPFXB=~h60 zxV>(0c}(n7RJOF&bHqo&i3fq*?~)Rr739UF+R~9*6##~5=|49H=L~+S95Sr;MJl_` z@<$&T$2wY~D)TG*w8X+5`2-X+A$WBX9Nlc;-+e$kT-z!2Du z6RoS#Q+rOq)a7&CS!CO+k0&&#FwTh)>=Wb2X@pI=2?HXfhZ8RcMP~k{rUiZH(l)-f zy@jEH^MGNTMCG`OgG>>X&ZbeJkbHT+kI6dK;blrhq)R#^B9TS`=vjl9W&7oi)C4lN zERE6YEWjLG14#v^po%?Vh)Wm29eK9sSN0x$#Z=h2tZ3AUJvgXU0Q)&FtFk$TL9NZn zC1tO;TOXwy`|JAk(EC`*D(-Ucx+M%`Yv*q5qDI6=csBl!6V!G~S4IHn4Hw3+TLdzj z?<6Z|Z{?0Iz4OQ2as*(3?7cU^m*n0zF`+D}Ahccxno^YOO!$=5EC7@A4GHeiX z9I!lYi{b&cey*fhg3!H%5f~*rJ&WLzO)p&8&aYF{TRN^7t4flw1@WD~fx9_tideVX zz{*&_%&xbNPBWRi4UGa^aP7mT(r!P*L!-#DgDIR2^FLTw6O#31use1}T@Eio_{p?a zcf!~@t`r{yXgw8S!0lY8Q?F{V3m$Q^fcL6&@lMC0u%~}zjHO=tdNZTYqrYxnoMBXM z`szQD;*4(clXO5Etay~XJmm&^`KEm#B-O>}vA?1CpW7hlefcr2B-_%zK~?GcgkY~M z*)qO?UmAV+L)1)5`&3V9l4N5V%Q(8wNmA@u9urHr4Ch?dpw<)Gw{H&3W17iy?IVLv zY~?30vM^-9J8SzAEs_c2SVSK?HrKzajpp2D7}V^nq*A^5MLmh2k+}p6>aD232%+`= z89cYH!AQsyVy9Jw55&g^HQfS{Vvw z=|x1#4nsM#M7ar3NAv*_Krj2<(O2OrAb*q5Bz_xF9S?jg7dQ>vYmw`_)N9LwEqtw@ z%ajdW|H8!I%-^%z5RVge)W(S{b{5 z;*u!xU&Gp_R<>xI5wa~TEX(t2C$>q)GE>@i&mr|L8L$Of1l7F?k#8aS+VHHSN09dw zbAL0$GcuRAM|poFtHvG76Qk5M=P6>IIo)$@WbIafn?3e>Mhpov8RMPy3dOwOp5ye# zj8jMb&HriKUYtxI5l}oJ7b8EPrOfYv6_t0&zXI>euQwS+^f84+K0W8e_lof`&ocEo z_8}{uddyP;iFOeL3roC9;RwM})vHhgeHl@2kO4+G743wEPn}+(qpOmEdqicMK4Moi zxboa->kTrb!8xWgGZg(~`s&W!h?}H2tY)vY3;S_|kxH6Fu*mY$N!dX!Ev>nC^jCUN zfx4zv5s_m-4VJkcIhwLi8m_E+cxi zAGlDk?@Avl*A*tZOik3liL}AJo{%liX0}W={x>O=GP~ZYebYZ^i^eHkf2zA;xCw3R zcVV?=LM|D6wgqK_G~YSwpmzP@IXvp^egQY$J7VPA8d-nmcZTT>#T}+KCh*2=PtzG? zxhr3$em8f9=uX<`_ZiaOU8;wFi8|Nl!VFlI;)>atfPO*SjIlZBf2jmG;E;3$RPR3R zcsk#nJ%hZ1eax*gDSb>^QnnGier6UpKtN)Nl=A=nMPHign%j$1i7dkJ^PW85d6T z-SYx)83GQ~PnPBLly5Z3B<_Z{Tm?_Q^iMC@L5*awdmblc8&wOWhZ)lAshw?1t_Q?a zVc4UzabyTlXn7=~aSnvfX~T8x-p?UH2pj_t@wN+}PS7m_Fe$1+G{>F1f_S&Yt_j9oeal?x~ z!0U1RM9diA4Q_o$R_);p0(`+t?d$Y_f0Aepbo+C@@NV;bGIQaxo%5A@1b)SH_k0ch zI{4C~z5A+Tt?lxJcoRRKo=?*_Y6M^=T00eK!HrlabHc`#J4^^@HT2s&?61-{_leg% zxLpoV2xM0moNn8)!5x`Tjz?jF{sdFZ?bD5g4}pe_MhT)5M+p$Q_umTyDGJ-9!>uN{ zSFviB%Z})u8BDsFm--tUW?7`SXua%@>gd+mRXDW{qH%34LD~vEdSn1oJ7$vw=)Pk6 z!($10sY22`3kQiZk_;lK$~q(U?~AcSFJhY)J%SOX==qJ@zI050H#O z;jm(zJJ<9|}#G>M~+Kfe#DPayPP5_io zpvSrQegPo_E$Qw-OQNo~&gBH{q(y+wE_kuZf>PkyjWJvJ^Jej#5MGg%Db?n|@XD+^ z2zGsx)R5CJ9a+EkseRwicD$~?8k7(7v~L94IfluEq5^W`1bdW?=Dw7WK&(sr5w=XX4sYqvTD|I zE;aQe#3D>adA}d|w}5>zqDV_GSqsGD51tDyaqAtW$V8iZ5oGl0()A@oID*M#*zL}A zi{n96$!E*-OB?zX3^&o_785-=pm}dy3{gv$)GN{e<^4Y+uQ0{cgOG4TQJ(mg4ey6MclnDi2rbX*#)Q9W< zB=w>9jR%?Q`!N&Ww)e2+dcwlpg(`!qRW2Z$HnsX{Vbj+3zVmy^32D9$Bn~lmhPmsH}84MN$ zamuA<@vnQDjxx){BHLsL6^$zCVi~!V;Z}@-tC|6%ooFk}Y|mECcji*jO#*CZ?7w~T zbu%7F$<;9}hd3|_)A`ZpM_c+G?eb`3PNuwmwUf*+F^qz!l48#__baUZF71J~LIM$0 zXX@4&%FWEuI&>iHRySoJh8|^&FoT@eMR)X7>E?+%^iv5YK;cN4sSZx1^6&a#4*=L> zpo|ge`QpF{7-A-jBho3g>7`W!7S=&2kIB{WMF*elt1eRv_j20{h;J%3q`g}qdv?f5)8@ACw>5OuPD;QHv=$VO#zmSi95A964aa*}m4Y#~xPe>v!BLOv5tnXj$LPY_HdI?nS$ar>g40euhS`9?U- zyV~o9aAh$^0BJ@3B&uI=g>11oa~A>W$eb+(it10E3U@6tAEn2XmIj>+X|npkPWXo$ zZ)&hb5SDe;MCreMQq;ZCC7WCo)Qk&%_ERNdD1qjZLP47FA6s2FrZdSmV45SZuD~eN z**O-Iy8+c^5I*z)O9T~6sL~y`El8?qU_{c2k9A_2znXhRRISB%{i$0ID4-Zxu@GT7 zMLhXSex_w9{(GwxpjcH#%v2Onj#aaKaVD4yMbHjEio+ArUp(><&*r%&rx3zNEBP}R zPR-Bn*_v&(FD3s9-cma+i4WyNvfr9Vcl16opQit9!pYjiiGw;lN;#CTPeJh$RI?MZ z;dXfEl#O$+W|T``;ehHE<`(A=^B8kx+fKfcOSL)?f(=40*R#c;=8mp7-h9GO5rFs0 zBqSFT&o`_zU9B=To#%{gwWGdIP{|(akTh9pz90EesS?`};#+E6t9K84XJW*w$cB0v zCO0jSt|tpO9lPhA__zv+8;CvhK#sVFQ>c2u>&&@+N#&fBz#;6UdjXoYsjrzm$1}CL zY}ZxqVwNpeI;C;yoD_HWI9Og^z0>jGc*M;<$^1s5RiAyChS~r+#q@QaJkBdI%gg$x zx7NjJkq@#HXJxAx9t$g)u3Mcjdg=m}0Hmw6#I6+A-uI^z8BX+-NGu$Q2RgcK)19nqll_e*-;{nM#*oU~I>%z&kd3kN+ z+|7em(8dq%b56*>46w9{AbB+iRC^Z1(bnzQJH0k8)WZ>QRebaS9hxBH7i3i;nE;7G zbnE!2mTIQny*UV4G2VA2Z=kD@gX>r>5T4;T8^cg~Ok=mYAY4+FTJV)IetYJ2;VQlK z1N9~)@2SfKotleV$tH=c!C&#bMzmw~D@(VUkC~d&LGKwypjCGQJ!@jDbsN( zH*U9BUCP$GDx>Mo#%_#0e4Bk=k*?L_oX^9zG@n5o={}Urkfc|r!=rBlOm5w55|>Eu zBQ47WYXjB${ky2GDjlSFs5axn17!Bu&%!)3R=1EV5$JloTO(q(&GuQI z;xtLqBl8FK_EDbFS=8Nsun!_`EU#c#CzI0Xcdz+>Mc$=kSb(YQdMVjIdhMSab71ppkc_7wk1vl(o zT_h*`jS!1u@7?>fHsQW1g#yMMRAf^hc_qC#md)YT(3|=r?4J9zuUXiPn|rHEsOt6n zP6D%2UIcU~KKGk-$K>t^}F| zFu#2Gu{jCjo8aeY4%Vbr0duDz{nq8~G94gvIs*$V6M6i@A@tLSu})0Eu^!5&S8#Of zwL4lrkU3dkY?P02DV;D1WOv69$fL6T1^@RTYF!bInsg@Z7%9JCN zb8d?U2~H#8Tvq4MZyLSO_Xs{WkbS&+VNrN&Z85ypw??_l(a8lk_R2f8x92KX8$l?~ zU`~rU4k?6eBo0Xs?x*!F(S^tJ)lT~BYv7i=pAC#3a_mb(b1u`j;eNItQ0~S%S>Wzj z2k2Zm+*^bs`v9?C{I$E4jTl$BKs&9qB50a|;H2`EF9JpmTAq68{(}F9W-EdA!}$H% zj1JB2{|B1ww@%#YA8G&pUFh&Hnr)+!+qZHV1H56V0_N`hUbUSDUc{o zZwi9`L=*05THV^jCCe-GJ8@blhSd8HZ@^yqhI}rS@F(5mr}5#WN4JZVw$JD5BS;UM zvQlJuOWAI2xEt!UB2|g199=$s^>(a38B`WUiW1!t7%n<7j2K1|T^`?{Z!k^9e9oY) zb{{INh^?9cD~)&yv7YQObEG)Nyd63w?vt&=F!3Bm5wWC%YvdXS6cQQ<6;bgX`y9;h zd_J_zMVpn9RkMC2c%L05GR(!vY%+}3$@A1r1(|fBjOdDNgk^W*-?0MZ{Wn3zogr}a z{Yzp0S3gkBEKHvF_?54C;v{R1&h4;pqowmXY-{$1C*@J(_0{_^y~HV_a)b+I*Nq$8 z=}gM2=?EiC4*M&Xa+p%FVJH7L$0-k^DZ1xSn`J#_)txRCSP;Sy30|G5b?jH+w=t5$4j5|pNU zTp_t)P4lduhr6;PzJJj!q*fnhti=>9mu`r>g6E0NR1=R*uL{eHHi9}`#{0_c5ZTsk z%yGh)cKc?D3U?Wf+ZhdxKjN3VX(U_M5#qRmf*<>1I3B*1kcfY2|gsuv3$FNhoE z9p5b!FoxipxJHOwT37|YnSKgQC@N=3rYMZ&5_g4naVu8HN$<&(O&Lv-#% z!OIKHEtW7xg7S>%OKH;qshP!vFM6}^8Tc>&TjuLsVY!&FUn*t*XTKc)#|-H(2J8w8 z`)FR`p;iM=^c2*|>~G=@>0$_ClZGs&X1jGJd5vSns3~fUqS@M@O&P~Q+xQy1%s@7*E&`vtBq^OYz@D;Ar6Nn5MQpVv$D2?Nnem8J({_-FP9FPQAyri^_02D*)N^%Fw zm`*e?GV(g|xo#QBTJNm51UpT7-h7*L=&2c(w61z#&X0s@Hl=4crzmR=hBdaw+92B~ zU=6Z9h_Jw+9Y`JIT&X~0xlZC8ra>ymc~0Z4+)ArGP`gL$NPHqzD^ML8ad=RZo4=>9Vy{=XHNl4cH$M%Mo$ zQ%Q_zm+q%S365EtvrSZ8Mo@sz?I95a`U?q3FI1*T>^#f^szMqqok6%6P^`BN&y68Wc?=bFI`nX)1(>uss}gE8?87+Y1$abEZ(~R_eoNRH zx})}pZw~rxD-ex(hhI<%?<^z+21oGGPC2CH7}&n>tT6+7#V$ZwQTOhJ55;ic-(Wa; za=bRO$bxBnm%Nst+F-eL^blUUK|XZ;345$#6(Z)hhKTn+YKVmYW7wtsYfYY@k%NJ~ zneDd{Ov=dWf8=1q|J5sntR3y${?#c971qp=3=jWEred2I1NW9~>+Ivb=IEk$UW2>??6S%4b0HTNlN+a3OsX5Ftv>#d zP5P2vnza<}GzD{Fv5b%O%i5~(2|)x@<@-v2SLtu|aFl_w*lzrWU4mI3q!8%ZbRD-< z-GVjR+C^-J=(7(Y8iy;aIk#Me%>0WyEo3RdD$c)x=nW9DYE&M-7Wy+HS5B|cH$9T% z(3cfwLH}I8_66ed73tM|$beQy88-m?YDWg6IVd~4k#>siX0`>ZPdUa`Tv9p2)9>t6 zwzMTyh@0HTV+3dr5TTp6YV&hVeB~zCy3P_wt#rWi-5DLUao>v^_H^tC$>@?~l`N#z zZZr3Ygv)hUL$lMgU`c1fyl&y%=F?$=NyBzQt~y0SS-J(-Ts*)9X436@mbrT7+6-Qw z`*afpI88+usfHULjSF11!pQ^*M32Xp|5jC!&XZGm0Yn&qwrU&9{IhX>`q&xUD}ukDdd=tVI_znq~cHHjAN9w`)(??4UWSik?5H-xe){qD(k za%p_atN*>oh4cSya{YHkDO))pE1`JP5U((40bpfS%^erQ&D2-q1Np;I&7h;0sF4ba zx~${Y9M>kTmy?(M@jhTDo_pcF`!I(wQ)5xiBlpNMZ)BcjaHcn;Y72G>uMbu{u3C?{ zjmI`wm844|vtLjt4%=1IHodd%t}izB$9J&|J3a zX%>{+dGmPSe}ti&m=t48{g$O?9^Yg(=iJ{U`u8aXaAYU)WNE_n?S+pZb~JG0%&~5` zyh_r)zgOR+uyEg|t$mm_tGYDS)Zb`N*IC9%;{vsQ7rHjNmPBw)?fkc71$Ap*J*eGi zxMZG(8m~fSG)B07Z-@eiP_r_r_a*2S++DR3MbAO3#o|l{troGiBO;=#$ z-btp~WySM|!&Yr!8_P&O;K zsn>Dh(#6zIcE@bzOpYxtyY+a^=PYltpea*#h$Y4ZG)(;?SpCeLWKJ^`w*D}D#j>3; zi6$E*#eVrtqpBu6%FwbjCCo$TeA!#>M2NQyqcv7a^RRNbBiN05rx+FBfXFeW=?c9Y z_HO0fDW^`uIGwSkGJ!rN?bO+ggAVQD{qvvhqNJL)QVuB?=FS1-O@jjV!MK-J?et`z zh(@|o)~&DawW27NTjUNmk!E?xXHzmG26`NL znPB#N)rkez9@-0TZD`QWJ#?o+Ff{F3*9q8pm;L<3%E6oAvCaI?zPMt|GbIiY9d0IE z2Dl7%b`}ojQssr`hxsL6_KIY&LhiQ4eCHXW|+Jg1`d3_)DU1!e^PW6J&uK0JOwttc$YXfRDih8hVM>LR zg#+oXdz>)V{1n#Pvi35_Ui$Mpq|sbG8Zim4=p#doR`ZLpC+54$eMu}9^Ox?vOILY5 z5;$&}6X7ezI?FOsMJPy}X;BojI;(J;!FYCPyn8smbTi;I+#Q$6IVuEooRGN(cVJ=u zxElf%2GUnljYY$X5>%0^$!nb>;!*O*aRx{VY*Vp#&BxWzx=B0;jp|eJ&0{_8rC4sl zQ$BtD_O)->HO09H?7;262sVvXWslR15$bZKpsJ9>Vg<@VC%#5Gr9VhyN#H>2D)M5g zgm@o9%~TVl)1}>!Rkmj?rvohRPJI5sUFT*R_PX!3$n+m=wAubwtx?wA#@Wp9f5x6d z1r3Gy-zc9fm9W&YNqMM3@vzrC)hr50Nx(+&sxg!(?s9~RVC4W z%hKQngaOy3V1-1-`C%%;toJg5Fl(~R^f-Y)x9D#FqCrY1YrITirz;&M_G+Cqi}|eu zW>nUw0-9+a?!cFApWLXk)JAAqa+1ENA^f5nqE4vWKzlx7^w&5S)mA6mw~A!slpu$Y zV_{EoD+=c_=r3!)pD6n*GTS#$<yx$a?qbmM8IhQ$?9p&2f%JH<(WXJq(Qor&asRfWqnU?IwEBG@o9B$5MaW8ZR;h=a{J+lD z%pgG{zOd>NONcJO^m{H+?yspF1vxg)x?VNhz*9f4!)8_E@8J*^z_l(;=zd-q2jEv8a7^ySn$&2ekdkkiy_u` z{}&SfX!ccw{2uXG|05E!{IAkY#op|Db;iFy*h+@)ADw>%q?mBN|F+OIhj&MS-%#{W)sUcp%+7`5 zuL5EP?6-maHkC3jjkYp^9n$CMpw(lp4Qtg{W6C6xj}+Lxdz|n^M$~mxyKe?`M1|%% zWc|ySW3Xlp;V@hDyfT`hdm|EM{sp=vca4+*!VE& zT8V8&;mDaNt1#B*QI@qwzj50KLeQ)Put5+3xM*Q8)Efo~=*K(+h7ZU;70Y~klDzd# zfW8Nse?QEy{>Ko?>e)LO*((@1I+|IVI7r!8|6`o+e|aS&sW~8vAPj#I1)vtx_U_uU z_32rPGzE&l9WoUMu_KMr|sb^6^F_Q;X3q#7n-ft zCR!^m6i+bsDE4?|^KJ^_a1;jpF7Yr7DI1M`ngJSWv;^Cr9i~t;Wy)>~ zRIeSZ0nQTr6n)4%j?(+Y4J9eNw?J5aFoOv05r+6+SlSV5kfqpJ7?QJxl4@4!M#dXv{L>ooVUN5C}_m*=Eu}t)lmcq#mHtmzJ zB5TO3R>OjrGA^r@`YBhZDRqS74tShRg+k2Mj*q#k<7|}j@uTQ2g^Ob@XJ-^q_La$U zZC6*CH`pOQDzARG$q9>0_Oef!8XliRL$dO0c{CbKq3>CI@&?{* zsHWuG2~OoKK#L2Elx+!ftfW z?JfebGmM&#@Qbf2z4}qAWsMLVAScpn;ZM*#i|%$wU^O1~yXYdDs=p`cKCjezR^&+Q zqM9M}7i;`uw}`%DTJE|;(wx--lo`+qo5F4YWZ%K>HIBap_h)1*nTQMVnTo8U#l^bT zR%P^V?#9>9YE!Znfly+>o2{dByd%=yJ@7u?bhM;`Twf6k1|z~pyqFINvM=FBBIm@# z9!A&hsM^q6|F|g_(o|A;Fkalimg&T@Ho9s;=pj%$iFipygAE|^}GS@ zCq6LAV^^}OtViMp%=>TGHS3s_zrO^0RWJrDVD@08o~a)+@ioAYy8p}f$9WFsL-_kH zVdm$5DpltHeX0Jd*<~uJDSYo_{{&}QNvW{^3D`!Y*Z=7U3zc^Ru9(nItK;1K1bmNW|sG9rHaGPz4h4oeL9# z^6GypQDkRv`G3fXx$`dvZ6Yw;o3#Y;iA`CwL@UH)%-Vv8!6IYK(}l%j7134bVL`&O zlC(wcb`YYmb$ko<^5Too-`bf7+Ljz@2)rgic;V4p`UxsP=-tF7? zVbw?e9D8ioJ9gSJ5=D}mK2F&tR}*OMdQNS<(wC=+>%XQD)In}>Z9ka6k~2<}xG4o_^x3TzaEl49BV4dS#q2D+ zFPOwhSZ|h0fPWj^$j`Mi2%UT+dN>Vb!e22%V0Gv^m1v#R!rdlFO)`=rKGEPR#sTE8 z*R5w-aem7%>rOPMqtb3e-cp$s<=_!^Iy9sC;lprFLrK%W$-rYTbf^$~8>euq-SNu8 z8KG{Nj)jONcyqDNB47gM$&KD7l2te+at^EOnyOKBUPG72YgwTq>{_cE6tCBYzb3U7 zebNGV64->{HY}~zxybZ%@B_lj$$n(6N^MF;9hDAi5It0z)EegD*X8eXMyw@-JR_Zo zC*;S0#qIR3$R-)n=if(6N})}&=Mcf8L&)wwr1KF?X2OWmY@yLvsk#U}C@QD*@n&WD z$pj!nFp73b8W=nJV5~SWx`^m_wfcZs%Vj9MNN@~AU)|Rdk>HjHPHdhU-&AJrv38gN zC}Iwb8bT7;#dYDG<)$D0A7kyT-J6TuZ^*ilbKsrWZ?XL7#xo&mFnZDc?Gn{f+=g^9 zLhkaCTsf?#SW{M@UVoH@%VYdE?>=MLyUB3ykJ+XceOVL*Fss~+x^J=nf!gMJIAX2u z`t|#dcY*(TTKRwWCHhwZPbyB@&(k4n2FZrps^C)_wb)wHmON!($QyIaBa!ND_rVZV zRLTt}a>XH~sqFQDT;p&-1XL_br?K@;LgIDQb?|OInW=PtzCT0$5V_{^FGh4EToyXU z!O7CTrZ*v1fyCw@QkoNb_OvaN>T@01vl-ZT?bEjk)iS3S4}+|$jZK{o?qre9x?6UF zw*c9_R^xbxk}NnS6lRs~KF%&A${u47NNt*Hm!{YPQsZWzKVZR)H$&1Gy1#cxG^a7o zJACdK-72);b&#iQ=lAUhwnJ@VFhywUoas)dn9Yw&z(DX^r$0}C^FI6LGZP8wyf1gV@WU|lNMGC}{#Y2ofL8r^4D{*n#Gj_C+RZ)kFFR%$9OT@*KTvlS2$IlZ8Ab5(_d0oiT zfo&DB`*0RsPMs9`Is+So)8n;dZ?iw=W=)E&)`yZrj8>*pkOAK*giCaN~GLg$lgZwO(7M4qWs7!+Vib3{CyR<|!BV9JvMg98R#8$G=h+udh@0Ntbik#CFN# zdi3KBZT$=SpA=gO+!p%!p5(~>Kc(2e|3}CwhF%(k9zm!({!h?vz6gLRfImQCrjRc@ zutA5?bF!Bfd?(a(!mNL`{s!(>DfbUOiPzdWzkFf$?hk4lF08 zEubtQ4cbp1mPH&`d3X(S*#P%!Jn+J-j?8Z{k}xwabvQS=)J3rr$&@O!x{54&Liq)8 zcwNe#8Hn1EG)+xhm;&x#l~>*t@frzP+)26DuRz*{K@?{6!=~|_(+>#hT}jzLUB|#t zwb21Kp$%U3$x~C$ksS_3ZXgsl*$wD`_Mf;Ide_plfB)*^@8{p29H{e8)Olb&!J62ToVU7Q}yBt&^ZN+*Mu zNkWq)tZERqtL|-FZ4Z(e(FU7D3Z+xpJZ{GJN?Lkg?Wq-RheOV-7vrPzY+4^Lpl#&! z@EvoQB}dIgxkWpn;kIa4+$E>=8M#@xOnw+VF$c{wM;tj%oMBL&1X9%D0?#>+_=H2l zjN7CFcEn+nXepxT12f6J1V5SB*dZocICc<{eb|iQBXAng`e0NLov~#hLs(7z-X0Y; zgB((H>puNZ<2n}%G{%+o#J9&Pt46D4qmA{h=`-&zJrYI)>gEgUW)x8OuKA`#m)0|t zjH+oZ8gV2t?i8Oj^yoDTBX~YK6!1HiE66Lq&W%HX(hop?spKFs_4wuTgU7nP^HW6| zS<9f2*V*c27Gd5`MV2Lf;$9v2u?10?{m(anL9go?)>=Q6KtHqgb;(u zt>(7x%Zon@m1#vajo9VN7DUP(Kg*G*= zKLE_P(kB`Y!lBMO(wg&#F%;!r(x`HAK)c#nyHYEZGASI|i{%}_zpVSS?aRu~9usZ} z%x{VKu*gUej<{CQsY}cnaK8tXc)Wn-o$!xPkJ`5RvPP{(5T#1Uk~n@2tU0q!w4Mt z7Sb7VjsL0k^NsczBuGQTouO-&W$bbDu!32%j!_&VZt6OMlr#h=yZSnRF|4PcTREdo zuAScmX~KGW)#5|H^Y)9~$@U;q%Co6x2g{$ zFa_#~I^UEghr#q%2-0K4N^JKm4A$?Y08~5Kq`ejx8y&c_K4+;?Tlnl%FTFeVnxye*&#d^oF>+i%{Qb`SxEK9f^;A2x3D zY`!IFKhl#FzIT$>eI1pJ;J^qk+#bpsmlc66QtR2>l8)Abj}RE#EaJc5^Jf6EwOB4T zS_kvr(~}`T`SJ;8KhC{s@^mH%o{DN5O7R(VlsP6%AwHSDTeh*TW(buCuLJxNT$TtM zi!n9~M0O-FtsmmO~LTF z_TuyCC0i}xX^hzy)_vvkF%?O?xtNfy05x|P{#drlKfm$*=AL@sYv|kfZ7Y@fw{HIR z+y3e4|B+~_S*ZW9105tpDFagK&b4VM3ucy*!Z{(;7@iK6{xUf&eKbu1?AA|_qKcQC zYd!l)d}Ox~m_?Se;Kcth=eJ3Gaknz`h6GyfhQR&wvE%mBk59Iz&qvwozHs&gf}nFp zc)K9|kQk)RZM2TJn|wf2<2I^oA0YAIokD*R>-+eN7BG{sH_djL_;I%;L^3p(8h$hA zvlAo;iyg64Q6YRm!Cvbd5JDX*D3~4`kC-+|_eDM@Ss5}5=R6=_bsxw6!v`c?%=5U{ z_7^NPGEL})AucT=QZqoD4OVrf&jq4Mb<{vBn5LLsSGDFdKBR3)wSIAP2o2WTIND2G z_06rcaL9fYOqQH2hMF4cjgFZ1NGYr{_@AG^rFZaEC3nykSnC^w;dWx+GnTR~IC2U} zI11?8RWiU*?rPqZr=q;E3tfEkDQ{uoLE>aBC1bNGXY8y~0%75(D=UIPQPB@@kY-!9 z89d`CWNUJN(Ofbs7!XU7XTsy_aDyHw8s94?g8 zsv^4DY=DZxY_y-^Zl(H5gGB^vdrMJzLz^!CW1jt;=)(O7C@umU|>&6&V+FZdI?s($HZwvq&3? zNLw*fF@_s*39rMAI~@~iWn}SQ@GT;VUz-9~bCewAs;=&Z7zVWfPR>j)badjRYIm%A zzP}0;4FjnY+o>XilHv!4-E}9!lOL_<7EA%XB*#6>QCp-=?LlnxDuR{Fm;AbV6SZrP zg_I0R#1XRska>h9FB?{)jsd3L8rDNfm}XdEyejys8@yr&>u?>4oy28FESRq5#5T*) z?wq9P^@>xCxa}1i`Un#V)xY*!LOxXLJ4?0cdz-l(NY@uJ{iJez4g!5sIz~N~o;LH! z_(j2ggBBth)Qn>?Arb}E9&dH?6Ynq?^5YRYsqQ__JEso3*zG5tmCc@kXxF)kraG|) z?W+|*AGcOJw;3ItkoF^)vW1eLU*Rt@mU`xA75O*h`qOt(|B8VF7{>*}ul>xV)e>ezF5&SOc(A;<&Z*RRr~CI$aNcwG3Aj zZSAtK=MZyy%6D4c!?^CYc~P1&oeVn|nt^KE{O<=!$bKDr$RnMFo$d9Flufhj2tDxh zDww3z-0ozj=y8*;Nav3XAk+_ICHM7@t-d~gp8@<`LF2FRZ~PB(6VpHU%>P@UNL2nq zIR*0tlw_+3>u;zEt$5zuI4K81tO7<*l#!&SG?XE~FjyiyOgXWw%ly*YPRR@G%Ge_MkZy#mC(&$jTMvm0nN%hj+R5GNjL96C&BgV6ond=M!;YMR*IGrTUs zf=P6skUoHXPZe8LygRe+gDX0{E~RRI$QfwhDIa1fc+jZwq|^wBXa$xI=@JNyYp%$$ zNS`P;v%|;BO~egQHZ#cXpx!hspI8jPtj7*^OW-+5BjX=T!#ng<1|EDA!8C$PhPRc)GMa^`wR8qx3=`J3ngZ2{7(gz z|K@IGs%pQA-lM&|wPuYUk{pctqL^1KWJUq8asY9Mf^3Ou{q9?T>wuloIZ+|zqws=(&3 z)>}lWCgW|o)fi?c>ur{6+0BKH+ibFd1m%Wo4ZNHFrViV|c|P_A#GE%&9{eGrLElI> z-U(Z`JD+kU%^f>xvtG>AjGka1h%$db ztuWDCTe!5bine|VsYAvr|Jue&)Vc9h<=#Np`4a&nrL?V+UIkSxB^fJ|-~c~*B)8(4 z(vuNpKYuZGWvw$wr&ol*tmc8oeNdfVtDaJ8U-imJt@^tg?m^*A7}6VzMFodT@{kqz zMIQG?-b=mPMXqje0*y&VzkxFOTAX<(HCn8nTOJv&v+*zW=}N_>di<7VWlEseyfO9V ztaxTcme^@0HCYKG8)jv{zdo*BRDf9~BbQiy;f(<(M->>>P_BzLL|FxLHhh8EGUC1^ zO*E>ebx67sF6z_2dZ(e1AjP2!9VtZ3*ptwOTpL6Olt}pQYRjvFVOb+>J9r7J&WGHi zb*b_Z7*2j9-a6~T*(R~z+oCciP%Ep6GjD<`9fum{&GD1Z4L|6+`=|Pr@U|kJRLj5^ z1EE5$JYv#I;nd?3oSq9a{Ju$?MtL4MpD)O4?J~iq1vt)zw^^1fJ>3vd{Ad2mqeKrR z%n5J#nQ(u+{bjuGKalB|VclCo>o!AuB`0JY>ukspPgR6yu~zX?@6=GTWlII7L}jb# zh}2A59WYlW$cYA7jZftVXO)6~+=Pf$p*_ICT+jT%X)>32j+$Q?8o+fJiFW8_NISK| zy@xtuu0Ll-5oz0v@%m(I zE}Quj`*Gkw%blX>Xy$li+h!S5^2B>sEf2(h1P93_OU^&4-3q_W4SD}( zwk&62@-Jilw*Zl;G$M!p+Mj3H;4nql;3f~+Loot?L|Z7rg@oh=;e=YE-O@}ZJ^S${ z@b2qKFBBXH;db(JueE*z@IW)>9Uhh(hCEEHzc#Cm4ch~nu*$_WqN)J~E_m@N6^4*= zw0WsO8ImZ)9&a#LN<>W%4$Jp*=`U*wyd5e&6l^BS9(Zjv2WI|b{oH)x);>7gp2tO3 zB`Tj1bz<0`{be$!79DseBrOjq)|>;c9M|ETD&v^1rSMTzSLx*=Po(4pl5d7aL=NNT zjN`k8LOsCR5qT*u zgS3)+X;fT#rESEekA?2lr!m0F$$gd90>J+DXLiW2Hp=Gv>aj0>n;rh$!tehA0Tdif z>;RGU&VN6A;Q)z|QYp`&=Iuc>B% zucNPGsV}W$Lx-6rg$6!ZAdjsWjRMkjjppFsJB2ss--Anxn2q&eA26VC{bK_CQ6wE9 zJ%)u8#ovupQuH=w`kRnxK;XpeK++|{&?Z2IpTAj@L0?sXQYP1!6ow}CVWik3y9X%xO6~`c(3L#Ht;-JzS%0@es<^nIUO14 zL&J?4GEB7C^sbGS%Cyg@0(>bPvQ^i?f<^vAxPh5cA?GK?2U2gc7CfR01^gi|UTLEt zG9LKILv5S8q?$v8AkMR^_uQIu(KD6Ouq%W9Q6bakHjBwp@T=G3=cv(oIU(A58RTat@!#K1@H7kwX z3oI2j&x~OWp{^j-nsguyte)sp5`|jktkQ=PI-8Fn-L#p^+&d@XM4M4~#`dGkevUxU z!JQj5_mK%MUc8b?e}n{C(i2lFviyX?<(UgGZBLNunNomM=Kx=mc%md?3LbX;X~+dc zD!02vV$nmv0)ecYs&R=!Cb0VMtIV1erb7N%k5)_d1rW#TD0^dWxNk|qU5{nL9^9hk zQR<5<=*Qyu=`H}kLwsr4G{hKZ{$Y%8B{&+3K2}Cusz4S%Lx#-my^HgQht66L?Y(=w zWN&}Vp87r7a{NnXlIay}{X=n5)WqJz*4V_>=-*2|W2z&6X=)&_AqRdN6cJJ+>PCal z3k`)<3^M=VpQPX7$d;eZ{hc{JO+woDRc9VRjFimy1liet>58jMV%{}8R^cVT&c<`= zIzKeAzTN@V9N895gXN~muU1pM6&6kbgNnI>IZEeVtpA}akFvNq=R=olWNjJiwYj-) z6Ug*>$X*uYi^A)fp0n8+g)Hv!@Dduu$NXCMlN=C0K1_+-d=GLNH53{kpmd?%#uf?T zY5Y0PxSV9=C1%BjNKlnxWG?u5d(ff6<*Vodfq}0={c+qUcRKo_HY=Pd#>_V3mZsg#OZwA5FNJ@5n~!R@9CW%HVOc{}}L z14tD@K^@w!%6s(E-P_%C$F}5?G~>WPVwPwWzqOVE+n#dGPH4Z>Uth0VpToH66no`s z9m^c+`#lDna$!KTaT72ierwxOzhbMsHQlH3SCB-G(ze6)F2zEL_To z+qOZmalby~RSH3Ht(1BaacR+`jF7G@>VvW(+6-@E?MeSuAE#!CbR!ZoyX1DcDY)Df zyv40b97CB5+Xi;9@Fsl(o}^~Y5^1r*9!dLk?ILQiWlvs0O|XhW83- zbC^ek{h7Ib$&d{JzlJREZ*?l(|JKL;^XmWZ-WRAGDgD7#P0|)l*D85O8ool~4}cbf z7Hz?V@mI8eI?5kY`YsipMoIVx`@lU8|Muk^;%N_wC+j3wje7iZd}`g^LjWtc?an(YZu2j!cl*%nDIkND#%yPCQoz zTh;V28o`-yjRQ!eOgd&Wq(zl?_uB*OtYD4}d~G6*T7C?P@wcqqL-<4!UtRsZ`gtcD z2Dao{o7Hp1?`H>WFT9s9cwU#$>IhQ&I#{N*1G>@s76!y+MC$DpH&SfE`=#cXj5TnJ zIBRoza}5?Y-}cKa_R>GgyCS1jDk*^0!gZHru$#B#huVO`kG2~H_n;UE%R-ZiyV-V? z4Gi#X5C1AxH(%CWgjFp)=qK{6y11%fqrk{VCnkgcVKc%}a>Uc$xwTHf_`9(qC zg&~g3Q9hX4C$i%?*H4#yVd4Hz_F*3O4U?)Uri zOgGzuI0&d8iJxO^XIuB-uJg1`*gorbX)&nlv}N4#Jhs&>)^Q8^`TT;=M?W*>Pp)Ak zqcGC$#|L_`PP6{>S-9LxMCE&b{BXV$Ehicz8a#SG6>K1g35Ew^u~HhZMBB>1r(&{F z7;6oJV-^|ja%eTW1W0bN`1|4l%rw-hL2!tGNla__MNye9Bod~YFYjp7>{zNc-b4tW z!ne?M<%!2t+Dh#!la&~?ms@$P4qI59rl>J8lBo1h*#2lp(A)oHi=!rkt4GQ+TRe=P`=F3wY~&!@9&B*v#_>Q(7Pyjs<`Ut%;@FY_WA z;I_({5+pQSzaV!&-jm@Y5Kpxwzf3_j!YOO;Fr(w<&f%9gO5gF$kb=6^pxj}2EP;FO zChk*HMV=uP656tyP5`MY@TUZw-I4exH4iVB%}3!|3c9rHgM>IE@3GAvv01y$H>yy;d2cv=1X z6-A>Lba>u~yf^=OxHecaV+F7@2-UT}!sD5DWd<){WVZh(Ez?bFF}O-B$_LsfBKYto zuf8roVlIfOo{9F_yx7O~g&@aVvM|UW33|ew4DT|oy|E<~l9=HF~Z2pApzY8pLaU;@DEV#k)mdhnZ z4O`s>t#O_uQ<(}bMp!|IxmdRcJ7!>+coHZ7_tEkj!q7PHKciBZN>>CFRLZ`)cXE91 zD17;H@c_GlR>r~3Pw%C1p(tDsg;$C^F?Jd_2ooLj)%bRn;{(*~k&l_~Jpk%dWJ#@erRlV_EWh&)}!`)WLxy(jI!L_dQ zYyZ!m+Dz1fkpjLskzpu%%g)1bWgm`giXZ+y2K?ZgBKR^JxWYLw!1YrF$AiCp18=NjNU`;b~#AA?ERL^9O!x3;-1B zHxUxsB~z#{2<_I^j`!;qu}}9mFA!fsPT(uh(n%T(%MIGY;9dL`seBZI<62mid9LNs zE#kDe5-kgBz2aKkV^dwo3CGH>F1tN^idfg1CMcUM9qVwZK4RGHCXUna;38b}{W4S* zoDqL?^52-2ll&sUO#dT8MZtuXdhjkDfMs5=GVxb_fL|X z&HdmvHrQtu`9nS^7k`dFA2PU_BE6pc`s?%ex$N&d1db;EIC-Uiy!`udtJ!ETnPPV2 z!IKo)sjQe=WmW~h>2EAsuD3F)v1Oi;)0#hAgY@W~jj7)8AAA0Ma(j%_>a&2l5WO^a z1#O=&ZMEClACm5Lnvwj-Lq-1dJdy1Op#htG(E#3Kl>y51=OAl~%Yw*y5*3p#@{pMz z$pRdq=isAAt4ZB~T{Qtn8i4BD4bu03^P11{m^$4IFv+o)F@P@!l1PK_gS4R^aO}0a zR9CR?7(>xPZJ>J>K>Yhw?7n!Y?nm z!$wv04w+E#X4-&*z$!02uVLJX{mg)nH>C2Rd)L_Vs9=iSd1Q`@A-zH>WnZarQ{@J5 zOJ5{?oyfSlkyL>b$?M=2>eOCpI~zHSK2z}{OxHo6a9*d*fNj;-x3BJS<>qx5P&-6C z_?KIp$zk8OB@@|^CLJofN*K#qir)Bp|D~;=ZtXj+#o4SNZK%7h*v_}%1WM0cd`aiH zpNwS-&s@QoyhVt-2L{=C(Y>e(an#!9nf>kV=i*@BPenRIG&%feV=yr zboM6C!q9bI=S3po$3SMG7csRW6BV(wwA)7}>kTv;VO`PWV5CAae_n*Dq5$bvSk7|X zcv-gdvr4X8Y=jAKpxjxhj$tpQ7Lyd0OD1qevpY$T*MU~0%x&_z{D_z$cxki*QPJ^) z|C8%5Pm?XnVIKFc+4UW1*0dR%OZjsb?f$!V>UaRE}mGnE>os+wv>$>1~0b*eL{}MjPZd&3e5|$Hpwm!sYLmj?uy2ZWpXj93+I-qx z0!QOUU*QV`54oaf_=~!^e=!qZF1F+>wU0zYO2&A`mgg^?!gn)I6y9P#;a&CqtT0aX zq)@EC){%{WD`d_8H>sM%?7#J}5Y4 zxEV)h7HC{gTu)#C@-tDheRNLx=6jKBIKY53ZgCLCCu9NScH|GB_aI*&DhuN_3#AEv zS16bnWX#_O1w=1o!%bv~U;#R{u|yjHk=kkouYs^8*80NV(DyYli;=GB&q_i{$f$?-B=kVjAG62GfUF8f|bB@OMbC%GqQL#z*5F|Z$bNL zNlCfTT$CwDc9>`)70&_ZVo{wEh0_k#(jZxGBU~I^4uBI6>8lk1NB%ChHTQJ6tE|Oc zTE3Xl=JFn)KBiebaeuirsVy!okSqG_;6!_wsoPbiM+BU^)G(^_mxs$_QfA+tDar=x z?n%^9a1N$?;^~YaEH)(UuiNFX_kz;iZtSRRPHQF3#f6Sq)?eEKZEL<{$7dmORX9^1 z6otBE9DJ_3C0ijLL~|D%oPe5?F<{7_+-+E0k3IczBYV!5VYlg=0o~aJJZVr^z6H8L z)3X!j`Ma;uw2?O;WOQr#ZgCqhFliLO?}u~{yiog*IFLd<4$)*o^iEg4t>;H%uMc*b zq@`KO7I$`|f+GVKQvGzqEC}(i3@@`@1U2;2gB5LNy4F=9ibFfLR%2mvG4Xk$yU$l^ zt>VSB+OYM2wnVOW9Mk9Ep*f9n>rV-jQavyaBqILVFU^EK`#tcK%+gG2xN1WHje(d? zN?!%Mxy&AeN^gT@D%k{}i91sZ+89=7e@4Y{wU-44jx}j_t$jd3={vpJA!=dtFsiu) zcrfYgt*RUol4TS&Am^9J z?>^ZO#CSZds`sqOc$ke>$%@!mW3>G0w4I@{T)>hybr`bObhwS|ENgLSLwU6EUu zTiLC8A=*U7`eI&_z;Cr3#)uEaoCiay+Srn@jqdS=% zURw+IZ>@GLc{!nNAi&u$GOP&93|^_m)P{j-5B3iZe$!P>Z1!V)aiM&e+=D3(fB)S!Z4 zrt@{BjlNgT4Su!I=2xLg6?*O^OVp?lC^BAQ=Lvh6YzZHB)ZD<_EVoe|E!~qGk340- zN9oCBQ-zg+@BP?oF`arb7|*dJ8fl|+tA=u$!l5k5H4x&kG#Z}{UGc#bQh**>4+kPR zUrcjvddhaFM0&&;*5Z8eP%dzM7G-h8T$S6#7ju#HsqlWXZf|t*WVE)K0jQRst33mV}vjMl?`^AiT z&^yf*)5Q0uJh`7Re`~r=lrf12OaBwMnVa6-u?UzM4n}#Oa)jJ;KZt1T+q{Ps%sl0` zc88Pk;_i_dO}y6~zkU&oV~|?p0@NZDSk(TO`d5%&qmUmeqVWx3C7haBAg)8yg_HJY zlU(0Gd8kA37X1@6xQz)2M#QJ6UWjRQehIg)_q3@WSKz}EEQ{92RxYYKkCXGU4!YT{ z71r0ySAUJYuUSVNe}q4|b!c6kg6dv=(P8+jcm+ux9Ypim(6IR}T==`<)qevQ9Gxvp zU*W=EFdI(`_ysNoX(Jfm_GrxRqlq?m)ck|N5_S; zT9sAFFt(gF)hLZNLzYH=73JN|IQFZ8Yom{jfFtsqx+JdesW`h|-;a%r_9{x$om}c^ zGnsVCEQ()gerZFnt{@^)!0QdWIT#NjSAZXEz>#}5k)H=+3I3`GNmO=g*$twt8!*Pi z)8pyTak=q%uFt5v0&(upntmrMs&~|>y=1EnnM(1loPWr}mt|W|D~UL9#f9AC134Gh z+hbaA7%F>xpSziC=fS3$JXjNCT8?h~ky;u6n`? zw&2fM5ze>^UiQ}<5aYKQxaj{lWQf?=np&8-I2!!1xQ<-S!pXwW!rH?5U)z}e;*Aw1 z*lH$e=cp=RwwN;w1}KP;h`vii<@YHH@J zJdiW(u{w>rb?`Wip9u>3LZXE$nqu-RFc*DU@PBKZ-%QyT6V5$N zyTtN^)c*rkcs-_0@)k&%!~(?#H)$&hL{N)&sU)-22zqQTFu=ZLAKj+x5#m%|<2VG_ z;zt$OL3h}f!+;vCZ6ioB1)OekYe&ZPNp8~&SiN}mSVyIs>w0`3m#u-P4@tJ}p!=kw z+>WaB%88paZ4{eI_4}*?BqnQoIzQ&_IFp+YMU>GiZv!0Wl8+)yhbL^CKgs6PLZJ%omw=> zYQR|(u4~yBnZZ_so~veMioi}(6tC!u{=vsVTjhbL7V!URm{6~?!%&RUJ z@x!qM^wG(|rWx#p*KMf0nt_goQUg2r@lndXnBw1<<<{^ofd_`+yQouF2*WtVa3vZu z2mIW^-jDFZu2AhIjUv}ms_P_5D9+EhqM)zjY>HF#AND52g4w9`Lx0jy-kvtAWVPX1 zaI6ctb(|9}?V0*=p~IqsjiQ*t_v7(BGPq>4l&mNG0$nF%q(nomBn{aSV{`i8L2KSY zQ70ZKJobw}+ov=N8h3`T_c`#l?Ngrr-JSl+-HfRp zIbqCW{i>kwGV$nY1i+2xO9Y|qBx0hAQ0s5$TT9L^fb`)Y^JTwJFX`5nLp8W~MlZkl~y0-DmePdC1A6$sE77lYIP22NWo`2s;Szoow-% z;JICKnz+x*^YXhtJ;Hfo-CG7B1FH|C4|lHcMh|gggq0}vA^@EPq+CVs;7Nu-n}A~gImO^33=q?pI`pVI4}z{nIc5!(J*N|5 z%y0?(6$WT4gmelp7V5%0#V}Thsefk_0H))P$_T&+@PAX6?Zd(^-FD8B?!nKfG2mgeCyHwUnO1Z8-zNgYiV|x{lhO@{s&m14Pgz52hurBi= zpR=HV>N=lAAYNuUDl4O&jYnDD^xArfrBG*C>gVG36qYO}_W9}xd{kUbqgiGYH;{Pi zOC35{Ji&Fx&!ZU~Q|%I&roBUmXriP^zzM(7*mgxgC-QA;@DL!-OCtKvqDk92F`RML zT$7}D#b^o}^+pHlWlr3qq>mPzPiHk7uJo?1tvLy@c!E<_oP#*rzP?p7$lInK()D${ z_ZHtTvAthZU(T=&wwy7hOvJo+v|aQL)b8h=lYwi8H&bM)!-eEHP~zWBTJuzIO3%sp zfuD$GU%2jXGKju^I&+SrB`Fp!A~4c&x{*V;$=DH>B^x_yB?Or?3jm&cT*Z^oXzRS| zsws0EzExl6tn3t@g3a>^70SLhx$64JuimZkmA2XW9SWTd+Gd%oze+(W`S71qXF5$k z5Q#Lm`WOpGgYjotu}Wlo$&HRQtpf3DqP5Z8x(CIl3o0v2jx`47Rcxl;GsJHu<+m5o zkbH}7KMgvC*HzmH=GSuK=oy~Q1Ha$hx9HD~z{57`5-6@D9m{GgcPmkf#jKT2}uRp!-t7k>S&~hb>X6hS^;!aEC&@mP5M(GZ+B_1iY-_kmqV1JYdl+>h$=)saD-8~GtNZM^MK}e^=_Usx}53SBRo*$^s z&=j(A=%kd$W^JhVon&US7(?g&X%-m*eOYUQoA_yYglpD2I@oa~qhvTgD6gH5wxT!~^NG3j~0tRRWCnV{~Sx^ELRTm6j`C?g7P;pdJ#8K3RbK<37Qej;TJ^VcmcD3~OSJ`K z?J4?EtMElP*abaD%r!hvS7o!ie#4 zuex%7MjG`+ki^_?xch~t#k&E!Toe%>_Oi^joPUVPJ+7*U+Pw4Q(|5qKUEqR3;+DUx+;zX(0!&|(54DI&(_*DXlO^jpmdDH6x@r||dE zFp-uSbD21OA{-w@2|W~@a^GowGo8|S>b;AwB(3?ZO;JsXU0ncc7m$LAKm!{1UtXowD7s92H0sda4!IR0mm6FSnKkHFmUm9`A0Uetmy*q5}DPhBOyQd<|$f7jXgI|j-BooD5EmqR0U^;@ojkEBOIW0YHS^!%Sg0P)hj7O+IR zDIV>_@^9F#p{^l=f~LrMtr=?*NMlQ|`OaiYaT7Jnb?l6`0RpzNe>%x7v+vHXh9bB)Xaqtx;iJV}8T$}>UezYsdVsT*S!jql&> z1a^MY%d?%P6@7qaN*D0D?-P&-jZmSkqgA03IWXZlxBJCWywg4!(RFt^}r+zq=JKWit{uo;a4>lL)5|c6xq8xn=RX#uhRSMZ3sgxWQk|4H(+6X>E zCEnFdb;*LJ@O~=06wT&hV8dI_eXB4qIv#3D42)L*R?dp`bzJ zx7lS|Sx{iZYhtjq1ocI{f@iO^1+Xo>qJ`Cj;gD!XS5u|7coGv*dVQWLS^HB%fh7g# zg@8+Wg|T*rRI1EF?v&CodlG#)N=y2Ri&7-8SPn|$=$DUUDs2Y4PlG8q=lR$hy`oI9 zl5Gqx@mKg6UwA^*tlX`l{czfQ2@xPRqbmw>-QF8}P%t7>Sd+7ST!{pV2nGFFR;gy2 zXw}*p+MAm}-^F|n$?O2(OYF?$^!)RA`u|8ikePn38620-|B{rSZM`aUQxTH~|WHEB3{FLNZfO&i7N zl&Mj(L0puZ2i92);~c9B^bNPoV_g$=|_rkmhl_h!0Q^Kf%1fJizex%wRm!)91eQKWeF16&hxR<5VXwKjq|t8n zQMVu~c@3H21z`zTc0xDbk?X{>__F}rSSYr$$PAllwjoGaw?c0< zMzTP3U<7V*Y#$1&9(9K4K5{Ty`e9V5d8w{Sl4Cl*T_3LJORCLBbLrtp_F5znD)pv; z#Kz_lEs#_cXS7i;AGcI3&{R>eY3>PDy?4Aaj!UM}8VIAkE$yFtS-4Q>U7FJg5KI47 zJbIo+9hNZ})PV1l+F`tp7(6ep@To>72gfMt`5KD$qA0J&3b}{ln97~0blFyex?UHm z2^85SNak$N2Ru|3_h-BISd05!B32;%*q>nyN&-)Md|w-Oa8ez!SgCObGLL3<+D%{9 zU27S-WZ^Oj%9~EpfWCzrqTJN(3g(Irz!!B<8+`P`5hz5&N*mL{7@YL=dp5EwE+*RQS_x}1;OGOSh@>HaMhkS6(hn)NLZwGY`Rh;4hGKs8P~EGQ zME%xE{%*$6w(4VK!SKzmwMNk<$&?1M z7o+$2@_XJ@UIj;;zwWo?-zX1CoXbI%p<9ce`-2}@lT6yEjAZ*;!I5mC?`Sr{KdZy! z=fZFHQFn6_2h+a`K!RjMbf*btYbM_?fiQw(L~^GHr|!qp8OZ-k12F?>3NuR69I1_n z&0MLEj)9oeJ0}34*;eo_1PFtA4hpz+Q0|tr^uAYaFSRPPJuyF8VJXk_KoAMBjc~AW zvPsps5J?_EQP!U2A={(pu`!dF8S&i2CsZ|peCLZTnP5UT*J+a@;(cQ2rw^Tu1 z9>$j9ypquz|9Xf9qqL36zY@osc37#!zbZ-CN#LiR8EF?*Ehs=|t=_V_OSrn77&kL= zMU__J)UCA)bvIEjrbfdpp1LeYCqPm0e3nmzpQ4e$Bu8@_Ao;SN5a(vA^s6JMyF*~E zn^ziPot-{#Jr1qS2nXonguN)T%5lH>l8OmH2pj?RsjWuB3Pjxp8cYHp{bpNQr3!iu+T4}ole(&bB= zY-$4(Mtv0pP)0X-SQMLl74<4U)&qa0`$4=wmzq*&q*~a+8~WY>a#}T;n_l2!NRHV7 zn4C{??-;RCxErPQV{B>k@gxMP2M1yDK2EvO9nPgx3KeoZ3}fu!YLc`~=@v@j11?i9 z)P)f0DpvPV^Y>|vQUZmU)G$$I^tgOpabFvYT-{mzhnn33)AZJ|e5827)5(%9A8MP6 zH;_TZad%|B7d1E=4W^%+*)H_ygh*G&oMHo*<+TCQ2%@lps{u4m|HIQZRd9~b18M2~<4i0*q{kfWt3@w25TF^h{~ zHYNMg;wrge5QWx;tQKNKYrVtU>}`Gmu>Xv3Yl;vS+d={4PkXf|5zC}layo3X+!xS- z5&xcEK2w;iUx8=c#QmZk`Xi%keOJe%aY*RuS&^^otd~5}Q(N{kN)1j{KhaHHC)rOz zW6uQiE9FkA!{z$>))FY+LsZc*;iC_#zCRWH$h~L0YIJ}7S+9Ff?P+xI+P0+q?QUZE z|8d;?he!F3d53?}!Tv)s;2#lKZCM%L1oM{-V1a_Zi2+BMQK=6A(2rP~*r3H!&{*~U0rk27m3Jn;FG|j53b_6hLNJjWq^%>7@qWkcwKjc~ z`8YmA?Du?)^x4Us+(5WHKL{W2KB1a^lMLYqz)3+)v65I#x9K0663T$yBxW74fbJmC zYexDW=OQ@Ni?jptq8iy{f7d=%L@L19*?(VzO(iU zcVdLF+&Y(e5CXn!Af#RH;3ghqr)HffPvk+3u15lF6XwwojUIxKGFE5rcScZYU|CUP z@;e(q_8>DeLr+%%`0LXh7PhhXSoHcvEBLXQ)F@ex|637jM_>6Hc(ee>wyr1(h=#7H zR1}+!${1-ig^UYHw&f^@#cMeDqiV^OV==|1dOHiA!kKeQzf| z0kJJQeGND48*{Mo#PYt*qQGQPRu({sy%5iCT3RB#xlA0*OvDKR>hv1 z;$FygL4yUQQ1(tL0dqz#zW7;4adKe3QKVBpe4(7xDTbew*c3KB!k19>!|Z!vGD~~L zfrXgKQ4{%uOEfRd&mO&o(VnK7;+4DV3dXBT7G(k5ipIAUkw^l2nAEd6g2JE0my;*f ztXk*GOsq*08S65N&svqTP}+<8 zJc4qB5vBG&i)f28x{Pee8%&_Vh16xvJv_1O*))}Ha_z5LhAfX?&)hs!hZq?4>R!0m z9eD}rmm;iHG*wupEKc(MmO+qhtX)#kiVwx$R|l}<3Q{#*Aa%C7top>8@{x`27X;%#laSDmj3LsPLyh?c&DTGg3gJ& zGX)Pf=GF@SsZIN>8nZ}A4#>*f_bE|r;R?v#@hy&LwyKqQHBQx|_>K(Ov3yfzD;ESY zxLc(w05z>;9GI07QtX!%>v~nVR7<)w6|qAVcttiU=6@>qQ^3=pJj32w!WlsY3vH0M z2s_2ZZ(V|OH#}E{RAuWc#4DbVgJiJys=I~QV~_DpS6kgqlce#8!lU_Th z!o<;>m=3uPF9b=Xd|`Qa+SwG=00&IyI&thNKTe`g#D9cB03B=ZdtXk-{=cokko@1D zuK)d1?c%XCQngHCn@P%}7tHJpq!w1g!<1t;%7o|etS9J&B9%c~^ZyOmU`DX@H=p$WhNB5sFcs zQiG*pzS4tf;=ba8n&Q6lgDc{`qJx~`zOsWY;=YoDWTf46Kp#?nN?^RTQSiuoFJwHM zmH7=uuq8I%#I6^VAnS8RUk}@JXrCeWJ92Ojs*n+JdLJBehB5X9`+%co97qW>bGk9a zzQoNL3|H?iF{m8-fRq6?EOOQ!hG#!Qp%;$^WPG|Yetg5O57ei5Sfpb_U*42aI2N|A zH59b34HT8H9i{#w%j(RoG86#<4^HRct~!(hvuAQ2FG3vib3&gX4n9SowMF{yx?=bM zf`Qo$WAGEU@AR%M+hpJ;hbU6#%q}n6q{tjXC6;y!bO#sAfHd2OM_E-{I23hhcYX(E|kY&TO93T&| zmc=utPZwd^!ZD;z5<$T91~b?J=Y4P&8R0J(0p$QRf`D{*`3-BZCbnj`kl9w~&clj(=|@|ZqFM2 zNU9ZHQr43#F69P5xHZ1ZfzP;iNq>kmnQ)^(xHW|Dad$!p_0&-I<`@*xF-N)gQS}VY z=M8dB?wcU#qb9V_#pf*r`OObITtmTnH|C=)0Z_7dAunU9JZeg zRi%OQ=d8){;QT|bh2rm-{n`nEW}7ZOg52bY8W`xVzdRzyvA?qV4Gi6ldymPSlO$z> z_kYUtvtDAr%=ZVjhO#P}qSc5TG3126L!3A8$L5}Sb8l>PQgra3q%9uRv`yN$;3(tjnMl(OI>EuK~BC(r)JS%iqlG*a*<3t6QYCt<1i zkP-Yje;=HeDf%hHOk`~BuditKbR3Vhh=%j%xy6q=O&E24xJ(;c%r+eFgUU7Qv1bFD z1TQ;{DHME8r|_uV2fL#SOyj3~J^thoY)x5+0;ajINwkEi+2rO1==U=STk)HAuY={% zclmkK+p*C&#q_UPfIJayo&xWv>MNP4;and(iJXLp4y=3Mg6pNVuENEK<3&QRLQCm( zr~9DY78p>0nA~FpVh9BlH9MHPGB!7{oK?U1!k%jFSGrEWc-xeY-&KYWX%vwmW{lC| z!&rs;5gX4|Hq&Qr*o)qozI#^R2F3brIq-ec-@JRAnB4g!j_1yksya|bMtWrCRsrQt zJWm&xAsgP6?PvMPHz&9AwFX_8!M#sRs!D5DXQtn=cHBP$xESl}pFT4M#xh)->-8Qs zzL$Nbr~7YaQBP+$z1u-fdPt+~)bi;o@IiT(95e_B*vdPnq3W*7p1R3Cj?EV@^pPBx zpEiMvlwz)<%DGhf8T4@S5s|pnaP;!oFy7r8E{o)X8%yPnTqB3r=Vs#S%6o9#x~wZ< zmnEL!@=Dxh4C;^-VWu)vyXGk?t3}S%E}o}NWZ_mE7!{X}Q?XIr)^9SMl78I|e&)Ir z41pW}xeaf=^Sr`uuoqXhzQq}ODK3V536-7J$~4s0ZtBGE(?hj_)0h}C_$?%H2uy+_ zASE9`T)g}n?a2tk=jU58S0Ef>36`gpMeKMyW(SSmP zw}V^P6ne^wX1!?OrhLA(4}e^CIvYfyVj6%3#KCE0^@g!c}srl`--$nS|`@3j>puGFx#cR(7)(DlbEc zFq)If%>E+hyj2>8_#&^Saz-Qw^vx|p?RZf{DY3-#zK{iVSIv@!s^AlAgqx{Joy0oCaa%Tze&o$y|ifR|4#mg_LNYE%BaulLzYtrRHq1mzCnxar& zzH)+nfFl&twbDZSzB!p$uHI-llPeK!|M;BQYW1*#Ujb*S2?@4$ff>u5TZd5C2RQ&j zg7@0T{FjXv#LqUhHuUpe*6_!tV~uai?&{^1NrHL{_)Xj^&+(Cy>fCJ1dj@>1o`NXFiOM8`s+p6 zRGhZSdIQ3aZFA=AD1R8?Ruo!Qe|V5JiN^A-oY;kmf9nD7%gI`Y{HT@)XII~|4@J{y zjl~)i4deZ_mSlmcTJl@i8osJU5WEK9xRD2?SuQU zW?h|tHS|K(`c6HK*Eh9;7*7aMxiMc$;Qyp16Gt;(hKald*`gCmzD;Y9UP2g>_`QSm zECc|`V`R=-brMpkYLvPprAvvrl_YndNZQVxF`6ajs>(OVfqo@$R*zfgUj<3mCvyT$ zH(E+Z0Ul__Ui26pLN3eB6jJi{CnND?DXmGjczS3C|L19pX<9CdS61*~Rj>jbA|oCZ$US z8q~mRQY3UOHI?{mr`@So1Vxs7;&)A0(@LWVF*n1FlgQde14eTb3zUd8z`w zpx`$;_%-Erg`{!!>}yNZF%}vXL7$mrwF9WQ4^pw~ql=M6JLsnm?M3it(gBq7x=u|J zJmHgLtM%>_8np#MI5@8Tu~IB7o#_5dN_=MIaIa*r?NFuGK@y1{kV_M;@KuWBf0ISB zE79;Y(0MI5(!@pWN?^oy*F3m!zx0Tayn}k`( z7o&N#Y-MJWD7gI|fT|hGapX7TR-3CXQE1f&RPWfMubfL-hFyVeVwNM=A$F{fPO56i z#O_iL)UO+~nUs(IE_IT#=$l!6q|lmf&g>8@Ll|0O=wL3@F5#QY$ z3p_f(rTFCCgX7rpY*%mpg^U_?HoXOxH)2*L5;DCAd9K|vVSW8gLuAbdGk&patL=!< zQD+KJjySGk6!U}cKw-`XSKGgBN$xjoDX4mVl%GZcq1>XyA#JjnjpR`GmgF^sTa2`T zJ3n|FKuh?@gFJl)-S)5Y?NPjz8u#mQd(i%+vOhNc_?#+P*&k2Yo1=ZJyQMY>O)M#m z!b7arMJnj5aQ2*n@e8?iAs zi*==`UqV9s%3gbEEB-3mxg}au+TTWoB^$vC)7n}60!+ea$b~9XivTi-@fL`&rj%m* zjA$N1-ByV@xdN{g;q8Wgr1XgzK_i6%n)(g!p$ODGaz|8m-S8nD<_LU3o(20MZ@`cG zh?t4~28BknqP&@+S{Sid?2%@NMc0GK&IZfYp~{wL(2uw{;w;s{b;8o!a?tj6I8g}1 zRMw#@4q!PVb_?%&I1&66(+e1)edk?h zO5`I2_X7}MkBzlj0a>AVPRVufEig~J+Wp`8bUjgKP}gtt_Hg(Nxq@^%q{`+$Vi~38s64uI7N4&Jv_vpx#BUo);3ceJry^VQ`pRMDAtVUEOc8!8)gc!~ z$p?PJy$VRnf=OZAOny8; zjOIEy1$lH+QzfNRrG}Uc@yuVN1TyGOZ(7?rj7x8W1ORT%_X#JyA!1UdrRywr_3mw}{XH?8Nm zz!EY`P^nc=I3?VbY|vZcE-3k`ND$MI6Hm~cT3_;0c}*lLpEf!lx$=z>8ea&6uJjGF z^W2@*FIWLpA5n!BIYXJawy-Re@d?(_pQ)lbIx2Cr9<8Ka&(&mm)->5*l*Sk_7}#) zq0|bEWR|EKaP5PI50O@)HdAF|i!Cc1NbN_kv2fnCFs9s;-Qn-ITcw)}2JiRfBBu{z zBthN=!0n>vqjaN2jaK<*S8_WA{j9bKPhNL)fL^5dcGYhpg)S3eNjAN5czb@6f~JUb z`whsaPnd@^qXN{H00j(Y;LgBZ;;_!G<@~W55Jtu0xkLr$S@a1A5K&VXX|Dr&g{3o& zsDrZ$!fOawDq4PN)!P7~*e47hhP^9qLVjew+XpxD>}Ioeka4oFV3*SsysjQ=oMG4? z#w^NbeEr7}eR?S9{M}PWdnxBDlUjO!&5FPX#6KQqp5^^8fN*m}4r&Dd-4r-i=aKc} zSx{jOFF+KQaJ+{Wn|icPp)8aqKW@`NQD2@RbP|e37Ja>`(ahE%f+TxWpE*!aW^Z!{ zXbM61(S=g7#+jb@ToG{~{+Zq>42*#E>;K(kEB6$irRGo6@@I zEDP+l4Ce_1EUi>La>I^d8``ynMrzXnFHxXKww*cgo|UussU_e{F?xGu?Mws zB!QSeWV2QKOrIm^gF-=fzT}>Kk3AGV8V8Y&~z^Bov{ zAmH?;1a(&|>832SJ8h9Y4s!W+hiT0S?^)-W|55D#N#{)g1^@vu|6+>%>ws#umj?)Zf%@Ev0`l zz2~FKFmDg#C^OjDsIpgDO0hsD(q&9%`^th@p!bm|S*3n$iG+tvBj_YF&t`}dwKLpX zumt7OQ_c;|=4CPHMdJ1Op{Bf)&(TsJps5A)Klil211kAIhQv9Ul8_2OAgf@J54U@XDFJTjwe~h__PL}j>YP)=C>QCS915)oS z8yXgSk@p%|b{j4$24Q0Nr(Hrtnmjtv$_P^DKf8&h4){qzzCwjfU(t~NANBsv(bNB$ zIsI39=PQY;K@cr$HA%E|$Yx@G(D~K>+J$~r1{Pro(*Y+kaGFSL^Le8|?{!XQOc4CEe~+fKlCR1r{3~kdJiRR!hh-Az%)VnC{N<}v6@`^&1I+o8!Z$u zEVlsez)6om+K*920F~9!xJuuv%q|H{nH)Uc_}Rk?VI#om&TMLlmNox~w>Wc7!_*}< zxTDq{d--#vKpkda{j+R`%Nm`7!U$2i0YsEnn^HJYPKZlt4Zn+=rUVO+_VfJ2vxx13 zBEK1#8TJpxYn8SDPUjpbwwExlAx=%uC#X?LGOQnc9@!94CALAke{Og+ANe1)(kJX4 zRQRwWr~OM0E?0fx0b5Z#}4Gx$>c6S!2VyNn=CD=OxSQ`tsqCf_!hm z-#@^tdu7u1_zOie?Q!%5FnFF(eFHJ-eEzEVvm3Cr_sl`8ZP`QQ=$s4VT)VNryQC?b)h`4}R*l2)lAKa#kJ zBZHGXCD3MMfTc;#8RSUPz&i5W3|6i%Vc1|;1j=<|#&Lua z<}#DIz&>w|M3-_{q9o|XF76OF<6<)PQnTqChHK+ybMP{wZT?8lt-o+ag+u6R} zv0XVR%Qa(A1rx^t0)TNL0ibXSsdOI!iteHg;dp%yp>r6?a!(zh=C>$=LI17_RNCPH z*TqEdj1gV%YCR@bu06Ae!$Om?n@vdcED(GHL1S8ZrcJ{>A8+3R1*<<4wRbM~1 zU{Ja^DB6(I)8QGOtijd@HrQ2Wb+kkld(tn=c$^0V`1GdC(S7f}T$ub{pNHC#IYXEt z_!Pl{HGN5i;C-@K{#aAoH7Dq1^S@yQTmkXV9{F%tk^u`6s zf#sRv%jSEJ>$}GBo!VtW=uQ^@a^HuTeT4x7w*=ZTy)EV}Xo4x?ECMsT=Grls+zpPf zS@fIFirO&)Pv*)|9up>f-L%oLd`gsN+H!-uW=;il;|1A$($AdQa)2ayg`+vRtE_Q! z`TcGg%B*Q4sJ|irwp-%hV>1>kqQoISUr|8mE?ol`reTserT}sDi)R|W!oZ4|a;zeY zrw#z#sfAu)#K){2;%VH3Mu0Nfe3LJ@FC)eS5Ug|5klj{gRF~98yGa(^h;xmf`v>1w z4N&RSL*Kbd8qGsld5rmT6G)FSY}-p^Rv!la{&l~x>|_{dD{S{u9Q!&=oXlmr*AEp+ z`6}ADk{%e-i7J>jMER})$ZA^~G=h(IVqEbLd>RLaKiAPu4_KNx3H+V_f_3Txuo}+% zL&DALW7S)7pnpe;XzL9&N8{*4@tQkv3Q$M;gd_}g#dr`EZpMe3)h8>v16vwAK?IYA zf=Lwt@eu+Io(MgOL;Ia+iol!(Pf?6fp#1l43TN|Mj_VPo8*%N3k8eDwi_^+f^00b@Yh_YC^(J@XGp!Jk8hx0?U3PE+asPJ@5;zM0)B zvTEEiL46J@qJs3DWow*@je2;Ku>=IS2mo@zcAb}L9=1KzoQ9WQ3#VTl%ldjK32%qr z_YxMcGz#)rSV|k#h++w%XPI2GPvNr#ij`qsQk1$3_mCq_#LL zza7O6;h64XTZ)q7lXs@0MbbW;sloigwruUgL3jg#4wah>^+S0L6Jx~U99KngORJf? z#2i0-;R|54RQP_&gx;Y-VZ~P)9B-X4JcP?uip1WoCTWS?LX{FDNX0;O=Cc~=JhFK?OC?a`olaImATMwU!bk@qPUpwJ<@ zE%$JpCLO;>dtP-^tuD$x4N|gZxVx}O1gnK+Xl47e zRXs@jq{ag_sgv7ezQt18rc4!=59p|6SHpKlNMt=^l~1`qh{?w{0ud2&W?Hf@IXil? z(99w#9I{RoyxT+5F_xCNy&u`NogvKRuiIqG94Aa;8?p>dngnxrdm^&M z&zEgxKj$xnQ3#{Qmydb2X3 zmN%BFmdRE9wyBvez0%BHwD5F;%Lj+0UikdJ9kS5+9zd~+nmRSV=%$$2^b};k$Lvag zk**+8Fm=wN{`*dAI)=2%ECnF(z-QN5bev51U?UIaY!DzDFMjHUmZ#-a3rvG@;6q_^ z%4TbT;3NcX2TwZ6%K^@dYN%KOCQmE$H~)&m!$Z|cbM-v z1dlW@13dKwPJDu3(~>8;m&i2mz&;*|hDBX0;sTLI2zMn*ZS9_mk#qmGv(u~xEB@}| zY1833K?M))%C;=`rxVQ8g~qhCKil2a?mY1?9adKb=8YAV7G$ewW%~GZ3x8eQ?OqL7 z^06Gy54-$`a@K+nyIOD2o)Bx9MA?oL2 zjq=&#@WdT0hyxR5F!kG9i^@{-Ie1FEHFYk6X2W?@bwsc;=BDGiP~c$5f6lOD9? zI}s$J8(N5g-VRb&uq5I4x;?p8ODY4C<;WqT-zb;DgJUZwu+043$wQYcE5h1eS4PNC zDKK=;kZIZf3iT@bB`LZj6ghO2Oev)=lLjnFl?ELYC^-Mj%sjZS&4d~7)3@TTXjt)H z>Y6ydE*YaZqQwJlgOH5L*pQW-DzW}1$=rm-olsTjU=Ar7Z$g?fQO{?v#n90=3^LPT zQ+*XQI6ocdf~iy*$jl-=j4+wcVVp}E-a07 zE4)?Qc-1>2*0(Y_$rUGC#FNd!%}@`hrn3KRJ~Vqo1=d|myNV{F4`f#3i%xkZIyy?L zek*}kNHS-Y!R2AcR|6zwp2R3eV=<>sT#yki-BgOqrE?DE_Lcv>zkZM@;Xdo=i>`W< z;Kse>e8$Wh7V$vm88~z7>Yl42;(uQRXcsyrpt0}s2@%A3`E93j9)4Rg7zkC6%=ONY zQ z#7QFWvYyM`Lp82Qx)(w7I}x5P@Q;#`%;WfJX)f9ocH0cwKh z@SL=X5r&wPwVyXZneV}8tiST0-wCzc8vXDyZna^xwwm@NEcGx#o_{fKfBI6h- zWBLeau;~7?8N+#$SDfU?7)A#7LVMQF?VpjA-$}i|o9QIR>DBG|>>EJK1N|(>f%fw? zNB&_3^Or)6gMs*l7CJ(33U1jlx{Bi^=*b^=Sbw-q*F>$lfU*TyarAp{)x>Q3#Nd;o zoUH4#!iEFkSqMisJC5=a_Xp`0eqHlL4awj*{!;)cp5|`hLUrjr2-B`%Cn=>a7waZN z=81Sjq}M`alf(~Qsw{xRADmJ6Vm@tSskNdFpK)OzdY(HHrc(Cy zF_J5P9J32YBhZvOebetqWtcmam5J=3BzdAo-pD0Jhu1po6N|J4iN7c`YEay$_MUQ0 z+juug+>~Gy^G1BbGmXWr19mFGJORC>7L9Xe1~MMLi|JDmQ;ph)EFa>enYr%|vAAr_ z{nVDGVNxXl-x7_ADfE>ai)7<0igYF!cvYj^Q{fRK-4ibl#8s`M*w1$QUu>HaYn;}X z%eI+@`fWyKR_mD|Mb^>F7yt?o(|Sw$z)mfo-YjYIgBrb?S3|rrI=`!om+{}rHr&5l z51K`GDR8BNQUIAgD7VTU69+qI%ItWyGH!xW&I6aEhNVC`c$BuEsF2Plmc@_>zk>Jk zAM$S~O-sa#O|B#yh-<6{mxJE+wrSV<}m$Gp926+H5%TSSpfaszWkL}}XRjX6>XQZO;UC~g#f5fI1F)jOSK|7GNmy*Gh-atomDG&0?B$-w zMNJPO3C?83Fj|L%Uq=`h4AM#rQ(1m5iZLA?>qui)0QrIV`HMk{4}n7mZ8vX|;bvhy z)4k|&6O_9~{|sznkJtI<<&6X2yk;Geu;P;-pCGao$An_@uVn)PP5x0pf{jznN3draT?nn;p#}3ZIO>;I=ZxL;I_O*->b=1NKQqO zPY;8uZ-jU|8Fwe`xr-|F2S4b5xu!HT4C8JGC{M;>mu?3bhd~P)=7}oDT7xH~?;0uEYp57vo!>qSwTu+_*isR*sUgh!nPV4VD zTSFK5y>(7KTWTjx(K2%LZ^(&}AbN=0j))k&@%co0(1p9+=@6W~_^qgbUU^5rCW4zI ze8y5WO8ABm?|X=ZLwISKAIMeM@h+h!_*JN4`8(KEcu%jwGa+HG@C|WXShyn?U(8)u zRalNE4`0}E@BS)C2Qx^=t)3?sQ!u|L#kYNaGiin!48lMpU(noPPGhFOaF2V}#)AH^ z(4a3n{nla8c)ei0-94_&BFUAAk1f5RtbCf#o>FdtHc(9J6J_4kY*htko)rf32z2E< zi4|$=18`}foF(8kWgi%qq7^!6CXL(b5k+d6gr{sv=d$W6B}d9Cci&o(Me~rb^>w+* zFgdQD$aO{>C&YbFOi`S?v@9}z%^j|T$I@!hIJJ6GNp1FxSa8X%K?&N1-CP~~7B|Ay zl!ly{``smS{K3BddnF%ZI|R-Xx@5qInMfee{Dvgwb4Li=SA=j6?+?gMf9MT?9_VLC z)+|^{0p`_!-a0jG?jWC)C@&2^QLZo!k}1(M4E!j~Lnu>SUd7*Q*9UkN!3n3QPt}J; z_kpS{D;Z08BM^0Hg5qhY0eeq`%}}Hhre&b$>kxBj(+@SIVtshRFp=$Pm=AlaVBSoU z(XMhf&EARuW8ytW><|T$d){{;OILiDT^Yt&;&im_=`Jof=NpLrow!S$zm7s~K>W=9 zPboD1G~W7jCBXp?w?*!644T=K?TMkh2*G++u7!>BqhX6hp}vE&qRBr4WA=^6TcccL@QS`6ljul9@xp06&!ZkgrkUKXJR4-AGQ+sQ1F;KL;7HUeqLZNBiEL}9zu zMTNM)cdsOu;V_99y!Hs!j`W+J!Mv=!k=SQh*Z`cp_~D(i=OLbMIg=VU+m-CZGy2gH zodO;}fW64!Wrz)hay4m1;&Z;j;au}#e3^#wVBJ8qezL^)(kxb&7&q9?@<*r~-o)M# zAy(p+VaG1>kgJqbK)(7r;eF|u_h@nDM4WJXJgQcVvQ`+2R-BWPBUwm!%)Z5HgnS)h zOK-93kbbmaAny%dJK78tlMjjYaD04Ly6dD>(FTlLFpbS%gg0zH2Sv3Y0sD8ct{^4N zeSZQsFngRrp4GtTT7qqfRd|<;J*=u))7XO*6fbsx>g2TZ1F}`h^*uj0?4@y^NmCi# z&T&2oY_ClrKARgF7s6|^8|v@j@fjn`WbwKYlGJ0SyfEy{C)Z`=iXHIid4l?#r#!Vf_#@~&1I9lWA-@O3`>jWs& zBwc0mcw(%Ic4P?92Plt)xx@7R9% zkR@QQUcMZ)57qT(gMVx8MLxm5XxtV>GS@Vr{XlTC>yEN4*5YlUr|}4N#ik@zxDeur zIAhLJ@&T5~oPwSkZ;FesAWe*^l$GArAeE5|JL~LGb<8)Uxg98j5>XhwV(UkBNVRns zBM5-*-$-*M_V>%!g*Mh9`no=dTd%YvL4F_UazrF7(tW{cCCCfqy|u&`!!L0B!;Ig5 zyesaT+kc2f6YYg#?+F8cqwbnedO7oH_@i68WtLL8ARIE&vVug>VGSO$gH>-BY(}_j z6O?5Jw9M|u23?gmc5+QUy8fAt?w-JULALvILQ5Hubs*rAj)VTBkX=BAoOC7)A8ajA zwIFtZSotIKvPOjSWv_#;BDwP6L}A))nCyFY~GEP-l#&ky1U@mtl^ zke*<8>YYwd!P3!F`1L|28sIxwF=#-6<0qZxkMR}issQw!AIQ{me{p+??+>q2H2(%C z-KgGRe)wMJ-%W8!6=osWS>kPK6`q^?m}^^TmtD=fYv*6jP}Z!?&0wuLwG2~SmXWOS zzLzie@c$IisZf*1U^jH|5=ns6HY%#?r%nW?5CQY2=aN6@qcslaNkl#uCxVFt>(2@$ zpp0{e{B)~vUFwSzAvF}C%$2`76x@=Yn$KC3B6}ew{lpEZNM70CS$N^iEcO&4I7n<2?g5=V(AAbWQmhtBvve|XId!6TXY~dGz%AQ zxPa(zw{NLcfGn{IB&HNfOc&!!3#9-6EdLtQmg&8vD{wZly*)m(d=}g4A znZmbXHEE8F8U8DRxeTam=>-HxG?bLkh6{XKG8;vu3MMZR>Q0CA6QjT?(#_3=6U#UvaMz{6EC&0y*V(R3D7`aS0Nq_rgDygxAKAS^w4fjTN1-xcZj zL^(GWB>pThD>?f94xBEO5TIosZ&cC*PS9 zdCbf{KuX1U+0TJ)U zbepmz0jZBjFIUsoyedgC`z`AA@OCDgTtn!)Xo~%w_0S~|7U^U0~by%e2u3YKx!N#>yc$##9}>-r!r-R7Y_OqaAhA9r^e;I+c&g~UepV-Jjfi8T zW>3PeyjI@>(7aW&6AU>@Ghe(DieOQI+MqN?-^}Lh7(4ceLFA*ktvt{4Tc*dT)i%ZZ zP)U_0o_2sx8BtcFfxX#>t;q+=UVzON5M1J25$031zi05ioXW`a3^jmwk=D2O{dQlv za5sSRLdREnL_hd{lF%CAlQ!2B<7npFegS{%(xSjx8N!^=u` zd)z8!Mz?OED*^6C;lFCbCjg$m5e=%G+UT6Q>laKs$I;Y=9h3uY_Qm8M1Qyy#+Vi-Z zF}*HtxP!=ANPGIHy{ZScp|G0!IV1&A8S4C?4>6L0>jabvm%5PHxzz^VxxLe`^^9m* z0NS#vKB*YmG&yf*!j-;1cCCYhc|FxPs$A7}WOX;3vN5%zsN?V;!0C}%7g-@{8WBPz z=?8XMDz58eiri-U{3x;V6&F@F+VRiCPriMhyA|suwWd7nD92V-BwbW~E{WsO=uvrP zuGlbrg)q+mYcQux%rTN#p2b;nCCw5xqmzfUWrpL(()sw&0|5R?JbC2PIKvEE($@%kpxtn2(=n@c>EVic*eKj&!1S)T?fWvZ*qwup`~*!^_fx zO$o4)6?V_nkz2}e7D%L3eN8RGQ7m@PJ(zv?lVTPxv7SZG#e|oVc$v7gFq~u-oNRMx zzE#zz{@ILNQtPU9%GeDFR{VRStAsZ>Nr)of@!W2QyiK!@ix-W{{QH*Pj|jRcX_N?V zFD21T%bA6kqoFv(m)#|k_$^TDtDcwZ=71I~u21tM=wz8ph@WMQc&EL z`iL+wA>C0xV|=Km9Rq%J!1MJUA;?B(lhjSs2Uh>Hj&?Kk?Jwq6isby?)>8@nFQoYI zk%BMUmFgn%7wzgsU0*2y({bpUshvw_1Sdz?!A!{2(#W+J?}4@`AFm|qs%VYivDRx3 z`9_jEH?04AK9JO)Nzr9`EGyY1>F@i)2XvsN=Y{cD@OA!`afb0)tphnFoM%>a34&S+ zR>a`wuhfv7^kL(=({9{7ys%?YD2K5c8TEp#BITaHsm9Z_0UN9P`hTD$yRJ^%6=R5pRJw)2Xj~eCePWmEc|Bex) zUjLExItW;M3cs_$qH|&_H?cQJl|s~IxXgP<$?>L_mDQ%+)9oRT?>TT>jka^+*=c4y zEzO`7HRnCWCRtIZwqIkk|X(_x);kPA5>y+y5E2noYb90XvD5NNx!{|Q3XXSpUyIRm#WHY$V zuJ22$kKIM$m0$j{5HGHjKf`v5?o&PW7E!lIS2?vDaI2^~n|CbPS&QfOQPB!p`SjPp zHbC$p$egF1@HpAAHjmyLtM?+P1PWd0>X5=0RC+#V3@I|l5c^25NPJ$NVGqd1fDeY> z;1eDArrZfo;yWC6=G+l7a&7m@P1B6nww|;`$i}U(oO)s{#IdX4U+*^TgIN$9|9Loa z3S~8ImAOV$3;1V-X`+;A6y+{Yd3MD+ zF0?>kyhyruppZ!7p9@RDH;NgictsFLuntH&%sTzvxS)YOD>T*6_PL|nSq7rnK`|}K ztgZvV40=Z!hu%6HWNE+#ULGBmP8BWu`ODx4-!xfN1aVaJ#V6T3G7ZML&>KAOKn zC#!&Nd#d<|gBv8?LV9w(?6Gv8zql{zz@|F4wL41_+-rZT1}>x^=jgBNy_H5t96;V@ z=a`CZk}VhI`<_tW9Fx4oaiWG?-&w}Vu%Ypo8Zw5f(AepCp1Y3;tT#x|IkV@8{E4i1 zU*;wOwZT7GDw+6{TpwTG?;phGf1RHDSG4B;S*j%N>H5Wbb@{Jl_aDnGRaIXRT^NIZ z;d@6@m9J)b#CUkCz5qZ;sEP%2u1NX=1|{}Slj}&k*_yq3OYGqqbU-*$w~dMDtd#F# z_%quguP%hOCOYBcW=5ms6~7DB)7#8UIEY1JTK-siG7HmwSWFfxKvk;1us1L;Ch*RB za5gzCWLtrJmTEUBP#CiCt2?P7NLo*sIrRLiC@J3vwjxLu>}R67BOV!o6T7Anwvx-x z|HIf@Mn$$R%c70DJB_=$ySvl4ySqD$6z=X?xVty*4vo7*Eed^HyLSqwtzps?`|Ft86loG-~g>>T5!2q+vYU=rFCT$ zIw%CBTz~`oiTa>U*G`|BF&x11lWdVS)T05^2J<`nWg1^4YTH&c*W|2T6W&KzN9RsTho>HX@K;aqinG5f9VOY+dgdx)d03qA zY{CN&UsopP13HWeJPaIskZ267Sh-K$7k(V*_$-QZJBpf|0F|TXR%Alh^qL|gK;TGY zbg`{Is3@c=O`zdTn!c8df>-*9oPp6}*Pu{{I%gI)Cd|85y2ci>wqX-rSf((Y&9o??aHNrqu0O3^Ga2 z;4uoS%$*}hw(hspZN)A!?!LbiI$zmIw!2?`d`-wO6M&~*mAIydt2jkYQy z^Lr2?6}R5=g1sW0Fp`iX2IN&6qF*_cW}PuhRsG~Cf6*z12pk^i|go3y>9nX{Fv zk+H3rw5yrj-x%T-!~CgxiY|6+Bey64hg1+l848)K?DTEg7_wHHINrTpsax*hJ`igGqYDV*>%X#7?%DMRD-P+2jx=OK>?;_E2uO z8cooHxByCo78aWL#jZd9h9KxKimDH`5@E^b*KzQ9{d-Vl{e0KK*@Dr?(a6NojM2*7 zi_yfv*^Kf3230vL6El04|B`?8`)?>swbyA8q9roG07+=rbbn@H7WP7BPe+#$5@!>j z>lD>$*t9F@Bm`F6V?(I|6X9ofdJu$YHxuo$q+mUv5`1STOb&Wc)*h&ray!YsU~NnDUKqFD)B8b z_ZxE<2*zEy=?T4p#^V&&f}?z`xh-V>jdc|K&ECBYWcQPzy`M#%PT$_2d&=EQ1}xXI zTKxA5_6+riucjqyv+K}G0W*?(o@Md5}>FId;Vf9q`4iPR_HyRN&+SYdpNg%LMCwiuEIx1sF zmiu22u^6ZD(@37(F<7$vspvj75~ACz*1wI}N*MvpYZ%YW>&CO>8zroO!@=y03hIef z8vSM`?H@Jp?-$!~#8~A0y-qP0jqR;t5!v05;{N?n_Y<6axl>uGj=0|$qbp3DYNg-4 z?HDW;@`m(_$5JpW?Bf=RyI^SD&2525ENBpA6FKDWB`#OzkQAlFCd}WP6#q~O-z6~0 zrNF`_!xO8Rn!4VKXqf#va5DuGjHLJZb7gKTUf zKr?}W(;NY_ReD1pm>+wclf?c5_pcx?4{%d;`vg&`e{?tacg~If_rF?R?7zFzzvH`G z-A47R8n(ZJeHMWJ`X2sDr*b=s8;qeOmx-Dg zX`gE8If7EcxNaD0Z!gE-=W-w+E&%p#?{ihDbc(U&aWvA`JBZbvzH71I^Xd>aMg*|y zTyJGYQ|-Ry`u5JI;ci){c^I6bcO-8g0@7=4bynxs_ZPLi&^^Z-r~G!?ntja`15ech zj)hGpuRe~`r-J#|2O#{mWlvsN(X2V6|0GJ0-cQ&n&@}%v@3&(DjM^=54B-Q2qg8j{ zZ?diKhHmw9J8itTNXX_sx1`^rw%us1vb7Rx_Ip&YOZ44K4s4BD^v~K{Cy;gA#%ubH z}LP3EN&cpSsQ^u=5A%2Gw+s5mYfCkWva!PB=K(kDwK~Q#ej*2J27@_nZ zk@3qhYTd4jmIj=_!xlunvNm z)Rr_{YA=E{HuZ+;&qA;t$P|G^nN4WB%jr0JT$cAeR;qbC>xi+lNhCYzy(`7WmaOy1 z0(uF^u^PG?cx*q&Y|=vPzZU;qL${8{<+9{8R%)d?hM~NShtiGB@0O>xLg<4pW0w-~X0^#Km2PC6X;hVD-Ve%X!T)^w-W zEwy2k(5MUw?5aDVC%U4$+NjQHUz6s{@tEg7&T$Q$D-W8pdlDE!l9o*owotq-%dH2)6|2LrpKwMl&e^ig4kTMgn+TD3L^M?g{X#n0OQ_z4BE?{%pD0P5hsKx zmg_xlE=pRr&^fG(T!UG0R8OQKxxrQ>R=X}7gU$$S!ZUm(p3)AgvhMWbio&l!eFZs3 z4zuPx`qBiR>CF?Bb^x*G1b1#O$ZiG;n_Y`@z#wZ$R<( z5UF#$2)UXb{Gtc|2+SLg@cu#HJfg@R(Xp6{`>6UD-XUe91k^7D##<(q<=k=)n*j{+ z@%Ed)_)D%he56la?JkWCLv&TE08dAmU%*zb9dt7|E(5DLs5EW-yFb2!iM``-+AMzH zvLnCsjl8BFP;Mz2eGvW&xOy+*W7R%EtMDI%$o}Vkk^ldzB01{oDrmyU7Z7BS@vrhi z$~TGmb^|5_1BWy1KG*E5+$r1d?|LD1-Tz_unSSiv}x?FESOSL7nOw@ zy<}+sE33#lmVnnTa-@mVu6BUeTGq(II^E7dqqga&Ta$*j(AsA2JmV;?#Z!~(45VTb z{n(aH_m1sR@D%s#uWzK_p%-`S7eum;pVk^ z1CFcl8|{+2$A-#GX(A6-jx5n4XHZx5ty+97{IdCY1w#2=k3yNwbu;!d^T^CTYlEW# z0iUVg`yM(&cHmY%5g)(RexK^b@@l-ja&8kTw9k&QY(mIw_v96{QR-usD9bV+qAX>N z#-c>Einr@Tv#`1JTZu)4lvxbFqn$EtdL22ubVg+yUZYlmj2FH3eTjl9_XQab`fUK{ zPlgJSGIPs|8Z!)~;2u?L+}}!VOzHSbaggsN1KlsyWus=}4c5y12$-NHszXO@)Yv52 zF_7x=Y7qy4BwrH%PF{xz^#E;Zwk+HIo<>xp8OsD7skYoKCG+4xaPE0x7b(M^opj{N z^IeQy$SCa9X430gojdsiZG4V0Z`k`bOre!ke$#ry?W=9=Sa`mpd=WuWK|AI?k5FhZpSOq+g@^h}KO6h zRn;WpN6Ss;N0RnH!^FNnr^<9LovxDl8WX_sZcr^1y+LR^W>Lu8%9byLW>$Gz3Eo8^7(tMVHvw3D&rtsg&-mu|AMBqhtcw5W&!+T0u9g+u&77UB zOwIn64D0_ojrczg!GPaB1xAIDKPbqw603xe#e@bqv%odIo7qhiHUvc9bjMK4TXKDHL6T&sMPK^rOSay&~Z4?G~fgP0p&VdN&pT zgW$m~XWfP+3~eWq9T&9giVl(;K@yEf&XaPYQm7N*B6B}D8|Y3xea7S{Jwl|yBX@uL zZMPwo`2GxgSR9{p99L}*lHsJSyvj-UrxPJHfP=KYZhB&4(uvZB;WgK_VITg6zwKZm zJL3qV9{}UhT{Q-`p^a8T=a8QI3x@S-4T$&Ll2N0nD6WMG{583ryl6+$yM;y*LlDBXKu;H4|DoVS_%1Bnh zTZx=SENwHg6n-;=kC^3I7uM$?1K;#QfZ<(eY zP-~Dm5%{v7;-7*3ot6ykAO?9$sDt|%Cc)GiPP-uhE`|`=SSryL2(Ba&-%lFCh9ZsE zI@>8bt4a+Xl1Mn$)}Du16Ig`K^NsZzFCLF{fk|7-*O%B?`QD}q>tkMCqqD_La7SY%A^nd^SKvzgTab31&Yj|ZoVMm z{wkyhw6aVupf*3PAuvX~(;PC1#4g*g`t#SQ!X1KOyy3GBclgJ{VupW?7b{c7|E|8J zP5-XHW4>uAqA6qZi>76?EX+e7D+!rqDZqg>l%XrBQyWQ#r0<}K@-~TfaLYx!p}&Fk zT<1s7M^H$7+Nlm-DTGA=iT%i0CNpeYt zC)>iY!lTkhs=~>oNc4^5262zXsv7I%IZ_Adpd5D~Pm{Q~iY*gG!_YSc)MnJkOEFo- zd#p}E<${9c2E<8=%E;MMexkp>iAZB0$d6$y9O4~XnTiT-Z?UTRQ2NC;K`L=)O|hiCUQ2H-DRlwSsMWG-LOW z(5dxfqMoum;(_x;-=I6{Rc)y}vN8%Oe7BurT5f7gE=ZH>)-0PM)J?Y3AFZ%h)&BH# z`Ek3gR1JZsHx&Slk-RLus#RwSd|iKQjlz@ASx2GyNwH(fKB~Gn ztk8X!)RNcQ6{he9onll$wMBTJ;kNsheeF;aq7bjPmoZKQIo>B5!g+!% zOz1h2VOUHR%u{{1y*D2qoOzG{KFckZ$GGZ!n;1`@RJSNLWWC}fzVd`bL;U!dCvRP( z7Hvc&htx!)9To#4lzRe$`@W&iCanF>u=Fv%c5Qu;Tg0 zu=)?}?f({5DrWAVCX^2L{~cZ_pY1K02DX1Jj44cINFY{|Qdw)A80a1ldfvIQwc zku&=8x2ipX0g?gKfrdy*DHh+NJt#i)8`2*2`tMp8sE^-@HDy>&(p3|Ts3+nFq9Z|j zRF(UJBJCz&g@oW|1u2lj5hx%!OoHKboOLK}=O-!D?IxHS(Li&;)-l#cDKX6=9`>3v zni^oV63er5nOY1eRK#T!(IZb~a_?P@*BWA?IWg9|mu-&1o{C?dJ*>exE4%np?X)5wdFhn*D{H{{(5=BlGg@=)fdK zqh*!nrxT^%(ey7lQt2hdS2YuXSctVM#sF^w%-d0M)ZlwUOv@ZLxq)0%E}v|UNq_L5 z#-S#L*?PnD7ZqS#xsHu9t0J#9>(#K-T^fIvc9kaedv4#fwpn>`mKBKj(j=;|H$iy5 zVC|@H#umyu1V3-a&x_MX!X$wiq%~os98tw2aao1+#k8r&>NLGyVX3;jHjJ{Sl4E1e)AXi;e^>}soqgyWOFn$0BHGxC zdeG^DjqNyp=M-zT05b}wG>hm9V%dD`zyVpm3VPeF0$r@i1sA1zL?gl|a!Obn};)7*SgP!(ipFEH}3-EZo=g|jMRl{1BvGovX_BwYp*EO}E zQq2cM=OqgALzi;F#Fmg|>uc1U%&7eB2=grY`}}+#cs--}NNj-^Z6W|m&7ZMDGhY1WD^uPBfgIwZUC}cj>2O%S?OM?8Rm~iSf-W37*q;)y%LwJK6D#9+yYg9 zy>cC|{ffvf+w0O8x|_pPytpg&ci2B?NG^Lq0AnYe>*YnwLqz@N)F&QrN2tm-L#4r) ztN+oH-y9nETK-jRb5O&rY}4O5vy7T?;saN$9C%=H9G>%z+_^n7jpnm2dM+T?DRwTW z$FZ#;=o99+>D9RO@z<320BkHx;Ioen|HnR-?ms5}zx&t!#-$uJE!A~(^p6l`7Lmmj zb1P{n=wiBt80joFxDZTT70WtlxbN6fXsbOdup-VO=<}1VYt#2YMm-9}U8p8TK3A9m zYF546^wYSrd)+bH;Ym~LnQWgKkD2%B!lI&pkN0<^FTt~*p1gteUlzaIg3v{^k&=)a z3lA2ClcRWu`;zSChQE?%@&}#l7RE(!VZks!l819K5(nDD{60A*86OG+k1#>?ZugWG z0n_-}?uig7=CN%X7>$jEW!e=tq1!bvR2J>zOGO&`i)G%IGsXy~m5F4ZR5$5{xn?|l z0E*?vNm&9ji6yn2%1}xIB@G;U6V%u5Jqa}P^`(@cEN(HR1~+st+RjSCNJ_{O$=z07 zv3PMl-p5EmNOqg}_4FJpwK0rKS&{vX%N4XQD3Dg|`j4pA^!E|;N&sQ-aQ2~@Ig0$7 zsY@$7CT-nFgieJW>jc*reF~KxMD4Jxc4&yUii>#aEP-F9DC4b&0O?8Zq z`^=BMpF6j|5I2AY*8n@(KrfOq74lNNc3bVGv$Ai_ALRRPb`4G@KS{oQTh|SxWD?pw zsCANgu5zssaj$jX(KL~R)#<4lkG_m=GGa4L7D^5F%Z=yP? zc2jIrafKb~#owOyfQ!D!KJ#uzhl_4s6wi2Q%U;UV9Mv<7;smGb6GxW5tp-RE)uMxH zS90QXf66Il=6jaOBu^$(C>!9P+{x*!w%YI(V3hOduPfC6bV4A5s}*r=TC5MGY?$%% zt_~=309GCq9riV>G(Izx?D=0-`Ban)#tN#yT26rgIJO$le5xHuUP1*n5u2w`r)66& zV8lU<)nm3|AKq4x4yV*tQTlffGpN33)J+MCGc2|t{XJcT4-%?wS?$k9c_Z)eO^^E{ zwggHcwKO*_+0tual5qv&drzrv_O)7UEq+yE1XHA5kL^-uGQ_5J?zR+lQ=}1fT|~?) zR%wOscbnc2)?ISmG8YEPbCOqL$c3un;R>sOAVE-C7cwy1^3PBG(|%_b7Y>lfB1 z8&1Eh5Vku}ta9g`tHzuzQ1Tad{^IQAlPJ?72t>#_1*cg_Al?Y9cwdn$s-h>@a&k$d z4m7Q3(1_?ZE;JnpBS5hCC2wBYb{}y-vEta z9SY9!(+eGXaM@7?~`n?e}Zw?^6N|LQ%=r6!NmR1fgG;kzte0$+Q|)5niFcYyXOp`AO5F4kw^( z#|%_CY?sc|&exn*@3SbcRwx)d$r~O!TF{k|6ka2=}6Zd}EeVpqu<8jITI6WzF zHzP?1#q{-0{IwGdFZl-fE(*+c;W;mcxgYJ*-V0TW^Z94xlxE90|cj#&_^mZs2J z@xhP)2}nC^^OfOi6hNG|;gB{U3x|W*cQUA6b7ZHP2&)-W&Jg1(I$Vv&Y*2)yxur?l zP>?jVCR2u=+%P6U9e_OG4u?`?uBaU}frB8~}y$!0gJ*H&~aqYlI^?N4pQ+(|b*hLpevf596yf z>~VvEqqwJ!Q+fjf@G-sLPh!a(&SA;z<23b+3$v(`9o7O6USCUonnM$s)JEskotHjk zM_ZsIIU%-Acp`dCJmK_E?|W?!?|VEYN2hu1A$fiqCcII@2{3kW*jZrN8YIARV18}A z35Q3Eb}AtN$8yHBz^uwMN46HklJIB(0s z;QcOIOkezXBd||M?84M$1X{*U*kMu8q_$M$SBj7HDtnN#W{enBCKIDx(Ib}idcDHH zZ_t+2v{YNA4c^qw! z2+4;s$F8I$9}R_t(P2#%WaAHxL-|H!LIK59~_TT5ClC6kxM#66*F zDAgtar2ODAbJ^#q_#ixw(dc@Ptg);!$@<7cTxtvhEb{59;H4?cCp?}XMBu?26St*f@3SmZ z?%o>N3PreImja=>xUlY(%>l%tTH2xzaX_xGvFFTL=O=(_qD3zh^-8nq{)&oM2XwGk zJPq7q&Q(c(N0aCh5H|xX#mKEFVl+Z}a&*rvVf4~irgG>O|6RGgYvWa4)0upl1 zgrd1mi53CUZV;1P7wE*P9QP!%F~~~$0;=EK*b(tCfVE>mKlO6YDIUrG9Ag-6*?Y>h zU&;r;!G)rd#n)Uh-=Ph}8Ha73#-U4284QaIeL@N_04~Cpml5ydD(G$Pr0bDkeiiKh z1wOP^VZSWL`evQ4RSEFqSGl)~$Tovmv^)InC^mO;q|cJc{Fqtd6=OYsw>BTx(zvvH zH3oY7=Z?1_5QmiNL`s#Fw9WjzSLVku1|0(6>Og1zZRpZq`` z4}m8&Ia*0fa5`hxQq*%NS@Iq%j{828JEdoG>K;LsFnF=?b~$}%emQ?he|C-SBff|O zE91W0UC_@RzepKHPO6ln6zSOw(E=&8v!lKc>JX~WzfELyBN#8!^G2A} zgHs0Kyv5qxVEICqjGZ+?_T09_Uf05KRXVknjBXQ|eWHsv^>mYpm8o0a#aoCoM}D5o zn$ogFk~!FaAtx4F9hY|t1}@<6Y{?@@$K5wr$qDYy;`=9 zlzF6M;;=Cf$?MvaCheO#i?FWNW=+nse#!=MpdD7xt)5U@=PgAOEX7PGMTlJ}hxKaJ zIC8}qNg!b+n{wSw`Y;XsjEiWA#>RXQMem58dPlW@=XCEo-~pT>dI+-kIND*!Y`T#b z{`@qLM><*+vr&hC;CEabwAFy0ZhB?@$x{LqWoryC?!&x9k=gc(#mj{1GH8{8K(t_h zC$o`oU> zVv?9!I(e}~;j?G4Our}*TnU&{M_39vlu1o;(_UIr)TyWlGTWb4OTYWHmk|^?2Np4>HQ|`c)!aTPxA7Kh?V67$q-yN|)Kbr3BNCfpZo;F%E zfr&r^j-v{_hU5{gUOGL5!-(Y)y~MDUDMiG0yl%3T@@m++1Au7MTEk<|Srd5UmoC(K z(?gnW&i>obznVSA>G~jUNnrqw=g(b2j2YQ`>;u`wE9bw4I9~aUs)3&a9AvD2s_+>9 zhrQ{)R2NsB7 z$2~D(Ob&R-TNv~j)Jc~r53!iK=E6rXa}yO1M$o!XxC1rxYF?6B3<65%yzS?4*?r{^ z@YwC4@Bi|+e)WZJfE%O}91rZ6o}nI# z$=S7XGHgphv)Y83m2qNd9Q9SLZ8?V9(q$m*H$nJ`sd3h)7G7!uKEdQ5B*j_=H0#{Z zpgR&xT|`7R!KBDwaJweyp76Ozp@A5tC<%eYw?R@lKG+BttwEFUC{I*BlH9(}YXFP{ z(YQ7ot4?Q-7@jG?R0Qn?wXr+;RUFTJ5BedTYrKCem}F1%;WwEviG*P|SLi#5LXpNG z4jC7hHSY4FV_B8M30^#)IGzTGT2ONmh_{e+H?`?4Ax%EYXEASk5{Y{@dl+48gY&{f zdn~FcquA&+LFZuQCmKin=vY?au%csm(eZM+6hsuf?9^mf%lYzR3aGD$=iSv*>(wU< zB40*q_A((qHq2?liufJL#{S!jO|f*7cM4b|r4ZbNAUcKwi;ywb9~ z5+k#1myHw7bF(RBz9KR@(Whn;*nBgCc!B3ao82(eK@Mk=R#z!ijgVGb{*{x^@kDt#vjv zAo@2pqicm9K<>#L78Nk4!zXjWYr=zg3m(SvQR$1&k%E-A&#TW1?jF^_5YnpUqJ*J-*y z;_y;fG34F7jUE!VQ-5(YZ&XlSp2^3wC_i!0{6142!dM4mU<}7V5?u?uvT)KUoI|3d zfa?agm;*2M8+^QMsC9O&)k{_?y6^}!2#YSQ({^JwKcgyT(kWJmEu@fxeZpTNg%k)5r9 z0?xKiwrtAbWBcHp%YA(->SBOSA-^F_JlaUwe^8Jt%!;m0^zSvfrF(*oKcCF)Fob`~ zEj0fzw|w$!ZHz3;7%W}wY*WsMdT)171CaZ>L7#tWe3Agp;vWC{bdOv>rp1KcG z04U$@9*U#uF)$27r4^}mr~^lfPOROX{e9W|Iq(PwQRZSI&8*Bs6p|*$#L~qQ<07fkvEfH5FHeo@ zL-%`;95hHEsAj>WM7AjthQ)Mt&CgE z-SoivjEZ=tzMqq|I$RuLTa*&ryn396t~3tNII|AngJyZL`btQUTfd6E{N@7=H{t4w zRCp_@`35PU$x~CnjD=_`bR$?0FXcmD=>nhYBMO%ldWD~?r5rlU$u@2hkc5)CHFCt~ z+&l4M;yjDxaht^@PX7F~^=Ah^8$R^QkhKwBKGw#m>)J{v`$B_hBQ!OXnswrHDEQyFjMt*h6v9u(C8UpBnkbJLp9ch$&{JY^rv zU#257u!L~xV7T`%8T7!Qs5;=Gq`{zxYG zWajCVzft+XvXzClG{{uEeeu&kX$A$)04$S z61Y{cxD277&koO$_}B+cv~q^k}MO~1qIkl?1k0KVW_@6%-V%P2d#xur}f zW6k7rY2f{mZWH@Dg=9@0t?HBmw*YGF)FL^24ll9BEEhY`Ddb{HSPnD^Y_t-B=QGv| zjTwQMTQl@B{qT5(xT%B9Bpy&l2$lO{LTcQ~STHoXMlsj*{>Su^_~m?4NsgkaxzZF4 zo^|g=!^`dwf=}WN`I_|d5B?Kc_#c9yFgh~ln8J6|Ey*j_mCCHn%c0vgK1EM-@b2X| z(Av`)e3RPFnhz&9BgSSE2i9H5cow~rZ>cK53?nF$!O-#kC9@CMB{mzBmNHxVC|z7E zMZefK$P{Mq9~+jBxu;xj9f_~$M`)!et+^z)4rSG1)Zzc zrdG3Q%%swrRnDP?Yjl&dHl4+h64DfVBes3jXN9kxW4P)wA3H@gE1TkU@ZugTbncw< zv@4{T&-K^&IuIc8wgu@&o=xACKIX7bVcnQuEAq7nF7IfzJ1lB`K|eSpY_c_(H0iev zmTcy7q`}Byqnn!504JUSdmQ+Y(|PePB+u`j&AZg$j1p}+r$6yTA$Aft{W|TrG6(Hq z{~)m_mSL^cKCJ;t;I>+&e*i5KkGx8b|477m3YA1_F|Dh&KzT3yV@Gs?fLpExK8R~Kt|}zQSgt+ zzmfI+<}MZV6IqS^(E$5DP)S@|ja=Pa7*+qe{av)%#c#`i3ZoDIaMGA^n-a_bS}JS& zg0!16o0CTdH^!2{+KTyA7jt4?)hKecC@zfZItO;IG`A|xMg!Ty>T>j$!|Ea+u)PI> zSCBBIj`7@>>1=6(3Kz(C5^Pw2vE)nPS9wU1uH9f}Gnzj?IlP6@JN8tD#d=EHB1^)UM|#Wj zme49DC~tD^Q?5m7Om|q(5AM&7+nZ?o__1L$=6K%*Eg%YxuYE4JU?isr@HnAESz!Qk znp_LTId_{FBW&0b`!aKdPGr292;w=nY&7$3JQvE*?M?5I_pEj&jm40=LODAUL0NEb zA-A|g#?k9gE+_6Th<0XG@er1YE%5`08Scsw2+!i*mm0R=nnr;1No0>O)rl936@u>~ zcJt1+mE(U=6!799E3jcnug#Dir;s5b-$$tvFSeX6SPH2w$sSCOq@*jCr zmaMT5GB|Kla7=W~#NHqx$$k`Q4KgTHDJdZ%cMo^gF;nv#QZmmSKZEcxDo@%%ORJQ2 zxs;a}8o`&bfZl+C6@K0<8&1s3>bIlrOaC92{+kX*?;npdKfbI)U)?dvD=0u-TYgal zchng)Lve^|BiX}8(Gc&V-Q!2OifW_S14AKjlVHkNEtuAy1t zPqA!SszG}gk~5rGY3O_f5oO#57FU^gPwi+wR8xYKeWn3r7-2jk%Y^V+;eKN_Y0V(8 zA*#M$_>ri3WFKrq7B}V`UiD6kK)2`?{7JYHKVVjf0&kk|R&=bzW zY@?TyML?uAno;wW+}m{HU~U;Z-U$xN)OX;T{>~3dRLI{`G%0Xs7h$SsbgA~Uq z>Y;+U)W4=}v!0DqdM`41#f^=B{-7*%FCCLQs1bcCbBat&s~?AUDSo~w5rrH$=)D-u z!Gs=>k&0`Di*3z@Ac=1ZsJ_Ggo#mvVLE2KpRU^N9QzXuuTs*$md{@<=Hd4T6`!IJI z_p3YRbr1g4Sh?am-BWjn`%L4-(J-rN+>X!7KG7FiXlaD=DeO zgn(OI7bjPFKtgqy8h46~Pfxtfr=%3Dq`WdxSYZh;Gnw^s648pNv$NX4@3O7(OeE5- z^PLUp^a$doy9l)`EBzD>s#2Q{E1L!5&)T`@N>G_Bc2PUG*P;qHm54Jkm?P`EhI1)d zi*W5^bm?OEg=XV3qp6~dpTSuxsa>=;PyTwx!Zbi!CxVqp%6y)R!QTZ+lWeHk^UaH% zR5_K_%c1ZKc_FAJ6P1xB<^khw-)?PuSO=8Z1_Vk}0C0QOi}J8k5tT4QKXY917N@0C zHl#v6tZpf5%*r9<$1R;5A5as-s$~StROO2HRkKDLC!M4jvs0=ZcES|d_#-#ZxZy1U z3k4?(%#oRXWr9Ptpfr9%7N^5@+*Ln{JELpLGr3;tq44Hkz0r;Z;_WvwjEc2 zVp{;JhgSwKRxfTf&AF>9Vy>)-H!wE6KkwZeN$6ksx z+=Q+O$j|r^RyVt#hNdm`{BSW^N?6z9jZRxhN#qt|JjIl%{wD;;Zqnv~mvuXA2A16 zR$}?yUt{<|mHJ1fU>yC-Z=g)>>jqBa$V=+bg z4A;{V=5SK}NjKL@HUG3IEH_JSW?SJJYKYc45#$3@Ouh(~vTiA)3FOCIMvN$bN7IxC zt^<-*qEt^rpr#Aaq_ayZb+MX7Ip^~F+!9mwNg!7g{4pfxgI13#prG0) zq0^A_(UJBK1UE|TvMLi}{8=C8Ei1_nfBV6AnE0?Isz>s)MSE#D!q;r#q}`k{ZS9MP zf=Dm*mG7L~fO!)aV-Yf+AN91S13GQ&4&&B4r73vp(_8W{rZwCP^v>pIx~GHxr~L8n z4(S}-jBTx4EE$#lzKh#iSlR#APNJIPgnhpdJl@?J2cJ$)J}T5sZ-0(+`Hq+lnbK8O z9c)xTg*GagKolBkn7@!9WcSd*5*8Kdz~|oITt(=l>U2*&I!3Lo( za~7`6JWjzarUQFHH=BBZDU?BH%q^*Uu0{r7#=gD1sC~@CSuyk$&$Mnn7De94L1uI^ z!eQx;WnmdhOqJA3sh-M^+3x-BcKOW=rf@wfwZfpB)Fana;qxZBkBfsGdWy2^3om3! z!QQ9QbKs7Uo2U41l_q`(B=TWu#_opsosN)~zm`W zAU@z-WG3h^IQyO&!&V?yFrz#1fdilbvobDn6I4o5)1h^0yrhC%dK~U9;-b&QN)BQM zg3(2>54)A%1V?G+OEcO!#|VO|TTln?(dIKcIDq=Ld%5c}E>2!@S}#mrdYM#LgE>we zYHLeWiVa!x3rk$}Fj0M!Wn-XDe|$K(FfmufWNnDHzDZxj#Okj8$G0Vl6jyGo`3CLp z_giKsUi_+K<@M3l#j@J~CY)!=v0frvKnh|XF+ zP1-Wjl(!IVTvZBZ-|nTJufYnP3=Nt5tuDWL89FN7k8q`Bu7Sf)kf`O0J5P0sI}qA3{~<0q%POISSE*cmMSTXX&D6wuT+q9_h^h~$S*M)55-Mdy3!|8 zmt$6MTUgeq@7jYsW~dSenFTJ4Hini6Ge#B?^4TWzT`@0^h%U?a2Z=Jn{RX?Mx{xkb z;=aI!>%i))+=__09H4+c{<5Mszjjj*99+kA%TlWnPljfbd73==l1Yt=yljzUkn*71 z@s7pE*jMwZA9m~fhLw7obdYBP1d2Mes;U-arX~5?m)|?xBH)$DcAm}SKiV#;7W)Tr z{+hS{K&x?`E*!WV$LVlReS2iyJJ|E)6_ob}yxnn~Fire^P_~eM|00=P{O*2wmpq-2 zSKHa3wC0euB>g_>l`7oXBz(&9+)?yhxbu5jcU-?ouK3+;eZd}RFa(c$GmEVo!h$#9eu%|9FM)5c#pI^H@!#}>WOIGYT zBW3-V6^7*HH&2l_fKgE`TGaO!0jbz!5G)eB76cV!VF`|D3zL7s$>Cem^9;Q3496yL zqG@#X$X&IVoUuF;{MYmeS318<=rgg+{9|JKclrbW1=6Ap_U2X=Zq7!opR8>aGZzP2 zceDQ;sB^S@3{?*?{^U@c+AXZ7Vk&`tgM$t!28M@YD^c6c8SBK~(iD1TZKMii4)Vde z-F2THnJlzp*G{n=W)8ZyXT_JlryKZVuS-Tuiq=mdJ~!fCf1BT=72o zZa?RYcuc3=uIM1)gVhBFnLWwk5T>}e1on9m8Sfrdu4iPfO)G%SrMucqmxsHlAxW$$HRj`gE_Pw=TV z>}T>q?ptLD%8&fOocxEsLOGzf+<1_hYW+S)68=-CSdj=CMhUB@RFU($DglLGxg2SWwO%7 z9YV5dL%DOgeh;jevS#qA@`ftegxPy55}u`9!W+BUiIIXl9DJA?+H9w}gzi9;Jvoz(GMXarkN_xHZfpUfnMd zefe|qsbp_YK9L`JG4pf8u^K-%C?OzEWF=cu{c`pAGZ?G|P8fJRZU=Sl@LQc>LOmnH zC|p0+6!V(eQ>nDy0?*{mbQX{q2xO(?wRdzU8oYWD*w3M5W^GARM_D39QZKAzWo*WQ zIuE=JMH?U0__-DEaMdK(UG8IFMvWZDknM*&-h8r#euEI=5+fDRXut#K(7!l6v{k<( z-U0`{EzL2@dpn`j>g$lNlk#@XQLuBwjbT@Tjr4Yh7rY54IA=Q9N-c2G4v7t zI>C}9Jl7>mo#5jm8X`OMYi36yAykhvfTnCEd)g}+Cjxm2ft9u=|DRkL^T|;H_M^Ak z!vdaDRM4Cltp4K}y+Tk&HGjLq())`t$l=tJat-F~_AVYF6ggBwnvM@;9j2QvdUv3d z*vemx3}`erKErtN4hC|M4Yph+A|g6A4sLeeg^y~p)1^Ar5hIH_S>}v+w9k$g{r7Uy z6-RHgbOoDe9VtfDy_krFt(Jj2sBC8Fd1&$C;;W>Ork%Ak>R(K?Y23Bi`1l5$AI^qe z8BhuxG??(@bmVJV92r54TEzQ3`hE!nLe^c!P)eRX`eYoNT&q==>l^bs-7)sIS~Te4 z%ePx2#npTj!v|Mao_;4t&naq`GNjK{jwViCVp`Ps9f5&c$+&0BMwd-Hh*q`Jt8nC5 z0NYIag1|~<^=!=~PAS=+`Xk=rP-&ubZ_4TEYh}mSOjC#A-0h8N-Rpxij?UHArY0A+ zTJ2q;NO7i<)#7LSL9TAxbfv40p%O6QAjy(yPh|v%r#inde+vbTF;cpIV&!|zZ@g?Pi+!cf9Ru z$>t*U4wr> zi%DwOlsBWnEryrGfu&|X>+N^}SHMJzg+rmeW0J>4@$CVw(}T4P&47#jpLx(jqn=+$ z5QizN8g!U)Ap3~{D|V{;2=H&2|X43sBXOAPgf`o5!n#rjD|I=UZDJ5Byz= zzcA5SInftGrY3obLaF^HU#$;cJ@{OCqF46xN`_lqCabG?@6EXOr>5u-ck&^Gvm*Wc z=|==r*0TdaQkU5qRz^Y=@ZRGXDX|c5Grve|jdhRL2G%SQaN@I2V~RR;^hmMb8KNC_ zm53E4`@l-yF+c`BmAoQidx>VbleOL`Tfx5#_-1kj$YL03&%}%z8qPD=D#Q3x?uK}- zJMz><-a0}0mAnJ#|KU7EK;2@F@Bl@`b{Z3X>s=zPEIA&_Y!!$d z4P3pKr<1R4 zC?_Qr)!92p4od?Ijud5@YR9^Qnc$wQAQ>;?fnd@@p2$O&yr_IDmbaV&uzbkUqK~wv zML7QXS)Rxx^H8Ps(`FUL(PbP_agQEFegmmwPtDFm(tcS$A%SV}<55^|;UJZCqf|-EJ2~5i!C^4wR z;Buc&8#s}*1DQR6J=DbU>`+v1!^w;>L_geEIBaYjTBh@YR>SKT;Tu&@e*ODK^;?NT zX3*1Kd26o&w%h?CI0%3%vHc?(j3q^AjE9j&ckzBV(A6=hQv!V!TkC!mipHz^{D^aO zWhOhOnh{uQ6DT;Cxya$WMm(UV-)7->H1nf+AN4STuGg+DcHl7@FW16QAE0ed6qZ;5 zQV^Zq%M6}S*>Hn<+8fcLIri{80UbGXjO@X1UVk?a)@%k+(Z_BNhNb8q?!*&#y~c8V z(ojg-8TYXpVB4CS?FPWuQ>*S}U%hO~2e#f-fgM1*Izq)pdv|SS4xsHA1lg>a({_%g zJa!=%wzOkM+Eg3#F^wILN^jL4THQdxpAA^j*td7Mzrf-u*mOxTb@CzFCiEq2oBQx(i%P4J6#aW9>wZ_ zUP&gp1ib^Yza?Ho;6i8%FWgD-@d8|FG>gF>nQ6F?#2&+DrqRA|BXff7j*y;@B5sTn z=zHwKbJHIIbPwNn$v?qnG%HNmcG`J}ThH`bM(TYS^$9Pq6i@y2XUo6eKG@P|1mwKbF;LeiNw24MA^O+ARXLEG?g z+Z`n^LeHFX#FVR=yl_s%(uHcEXv4FKGV`?TlHjM#4r0cU>bgQI#&n6B06X~zD%!eT9_Gt8GXzf^E?_9QeYl*pC?RRD40vGEbKPMn`v@qpKP05Ivsx~(EUvFY06FS{OyzxdXqRjyto<`^-a0pv?;5qXt6}A zf~XBRo!%%r-Huij>7m=#%P>$6j&S;%p^ex?>f>sThr)~~$r$e{?i*w!qlXQtb$cOxi}$h|rivL-k|I$fK}g z50;+asHf0mo6@raDp?dHSh4v(3Zu+gwI$c{0LR~}?&Xe&8DU|=R>e5V-*Ev~Y8q~iG7agS6u;~KoTShHiIn@e=i2D-Ji6Tx1(u{(BlTbA!rl9#D_*E9;_s-s{yL)A{;44W+jmGtuwc7mf z{kBxm49Iup*wzkXI9k1GamTi14gN@A$3xuLd-ioiMI4qBnvo{)!kN4(fHUAmj#u>4);L@9S3M!6Ei+5e;!2% zriqw1Bz7$hb4vaKt~^-LeeN!RNF+m4GpMLwdA@ks_xGsT!kHW-_nZ)iG!hBRS7FjV z(hb`IUqW&`O_sn{1pGDXb}nJOqjvznHTs9D%eE>Vk~81cMo1U}b=1Vez* z)5`qYW7nViI|;6Xrm5{F^+WX`(v-0VY3pF?Cd24}5%GwC4VI280FMX@i>XEWtwF(l z2Fxy)bgtmKT+`D0E12b&)^B_S57MLS)lEhqG$l+}oHhYZ(DQ$bYM~~cC+>fX+TnjK zYX1K~8~k@s%a}MD7#lbn{GYIq|LG^!M?r@kya;nXe%j{6}S_9@?h-63y<>Jab zPc|Lgmd2YFC7TgHNB%+LJOCiyq_$2zWE+yiEpDfitwyt#F2AlGptT`yWF-k{1d;$_ z`4AcgQblo+WFh=^RMR*U%tn}FoOv{D3*Rc9_DRHM7?qH!FAREZChK(*n+xrxASc@P ziya3soN=6uYxjiChHK2_hvt2V5jC}e`@!-w9XQQ`qKt{%(#L#Pvfm|D84pd1WfD$A zmiR>Hy1%G@Enw8#4ae@p_t#3z!sueL9Z<|oSdxk-oyzSL8UVSn@_A0$+ckIg7@Xnn=_VbG>6rEv$VdsmK2XjNm zcxKr{Ct8NcP7;BLSJiYAev5NYq*S04L=Nc|l=D>CeFHxWu=l(Noqf^sA_agI%osGu zdEQ{n>z=zL>=1sFhCqmq;(HdghI2*6`>xK0JxivFd&b6v-z^{J{!2d99`ok)`8(g* z{9n0}|AYbk_mln~6w_*T4{zna#!OwNL=5uSAW(2f1E>#Uqko8o1ov7DB2pF#n!%O~E35smdLK_r z(o-dZ&gegTyl&f1v%FS5P9BDO3HZSEiN2Ggy&4@jaO^4pIT0T!Jb4EiXup(sN)J5H z=*s}v5s6g3wFd}Py2=ic!%|eTm4Lzs{&IJf;arujl7nZ)v@kd$2B=K146qhxYm_!o zLMlWI@wT{VQH&A7s{koeI&0)MN&QwBPNB(uPYf1o2O_zYLs0TPE{rX6Yk>5 zoPc!R?>uIRLy;;(c5?vL3$(ZD;OczwzM!|z;Ojjn9DiRb3r`R?C|~jZKRw0!p=+i4 zdE&SOt*p*4*N``hb0l{RH}m$W1gm$Dd{z4j-hzX%_ZD!yePXP*qu1EJ;aaRbAvml& z;hQX<&~8@llCFNyvF3p2zD3|(GETiU=2#6wMBjp~>APFWdlJ-aC2pOD8(J4ZmMUQ#Z$D?#qxY zNjw}0IFZ-`b~5Z4#9c{n4_1bfsyo{7pN9t(Y*CA>mv9*8b905P7lK>Tn(UjVFCzh; zn=ZBU^XJc@g&nQDxZnB6~FZ`6Gv(#n_4cAJ>0llJGyA#d|1%c>#_R-TrAeb!YpMtmMojd)VZgY zM~wU4NiJneqwg!_eiIxM71Ycn_Kb*a8L5wYUAfR9C;u*UU7X5RB^gV(uVI8xsvBlb zK=3hHMSUGcsXx{d)2MQbau>I>UT}P$E*?*d6JHwZ=*M_yo{vV_>{IOFBQ|7QG?}$u zDPoJMv&A3Cpo=;WWI70DT5Ysh)5Y`-^jLT(&Zk`4$C7arO`qou#YcxC--wd8Cah$t zk{cokclU0i`Ok&?%wq*>2m+6^d%_rPj^dd-!rsjBHeAtXV zfdRuJ*K=ViGo&OgdkQ0fJ;n7Z^<&l~N(NIB;|MjyL_;x>BPqsB27Yp1YFf1{`eti$ z`tJBiXgpZ>^Hqn;nE3WJw6!gZ-GU@=R>!UwU(G|%vSVFq%gVmsUX}~SKK!`=!eQEU z#@-`e2!)$xK-t{M8#SaMzBz&1HY`~Yq_&MA=(=KeB`$B-P z_%|Lp13*0dPMz;63}W7~K8%Ah6tOHqo5Oe`1B(1&_&ynjK~lgeR*Uq3u5O zi;j$b%Mb{y{b4qm5mugSnjXV!)#=78+)@)&uC15`qt||&6IU8K+S)V0KoBoHNwwQ{ ze|@yHl4b^TU(*&arF~ za^%jvKk;%~n`Rp}NTs-%)^=256$THFevoV>9;lP2BA;%s&TTZ6y)laiO-|>(uUOcb zI8|b%Buh6n&h(S9gW+499N2fZwUd{lIbRJG))m??x7xwdx6|yd=`KgQfJIh}Gj`$R z5LBgb8O=Cud~>>GRljwr!pT%-rY30IRPz%@Q(h)b!m<{#McTnA9W|1;=a)MhtO=Dq;P^#c*PwF42mM<-OOi^m@f-EoHzK51iN7-(D*o%LH) zHJX}qS}K9r*$S4N?ixi?R~G_AD!o*b=jsuFQk#n9Lt z8hw{yc>@}w%65Qkv?i-S9W0+B{XuCelXDU6z0g3|P@W~a41TIGTT}5W>j?Vfm#g#) z(Q)DYyArL|vqR@us6toxJGWSccf01@v4JlJXK~23gx7U>=r)7k?)LNFoEJqmn4|Mw zGp6nTYR3HEPVPcT=&@2Asl>Y1Tzd&Id6Iz5o9K+TddDy-jdzbA$E zR6wY~hdB^IMNsXRM8FS{@h;L@+^*Y3t*L28+#ae# zP&8yFQU@dG1|x72x4H=F92XCEgL6&v3kL9hu@8~|O*Nadnv9}jEETQ#x>w$UY;&va zj+*E(3(;Y%^h{4;eFmk!yFPX5d~?mUMD@5(y7o?~(L`6DDv63N#)GJgn2!qdQ`KZK zl?oeixtUYChSM4Gw=SpE$AskTXnJvgF} zsoc0dtWEb}c$$~{nHu*Mp71GyzK1wqT6z3=N@xkN#_fLjrdw;} zDo{xjl=E|kON&l<-7!%&S+UE@dXGfx^<-u4uRTx2=hR6Hzy|SksiehMQfom%#S7>~H0btF z*VMurMdXz7pu!?ZuR8r$*$$O3R1`(Zs7qLpoiQJ6F`b7!cFYq$S+BY)!uX1v{- z@~uuyPIYZfQBQg2q|dTHqD*;br}G<~%x1hfnSOVr{|rv)F+5^Sf1)w}w5I#nob0B( zwVL)%4_Kq8n;I}i(lm~-M(Uc@@6gOJM8cWY&(pA_52u?tq-$KUMCzE{|IxT&kA#>$ zFiQ8Me5^HgXs4-(8%}O$lSEgWJiyk_rjEQcuGc~5Hg@Qy`6i9zpF9B9*rJH+9XqIP zXp=x^pFBX<*rJNuG@hr4+%$0rrNK`a2GYPy8XjzL7ekka<3M>LzqOK3@)enr4J`0=(*_M{t2M;4%a)C-EW6(>oR|pwJ&JfZE z3L{N+RDQ(+tc7%UkDfMC)70mDkv1dS+E;(&4KroDtDcv(5s|(N+*qb8w8sJL1etWc z2MyXq@9zuxu9cVd753*;BQHQFXaZ|F*F|m~}N6)jBo|jiDSepdVPu z-42Gv2nZ%?>ZYlW@=6i7EltVgj@Vxg>JtNq@y7I2T_21cX{;fS6bH(hxGud<1GELP zEvPRV^jm1J7W9U&F0qdn^o9hHxGuKuH)zhUJ2wAUC~tv17+^hQKzd&-BnP5CC@2uK zzces9uny!AGN!0LuKztKIkLS*UWkt=P%;=)mdu_e@G4SUa^E%R4bdGWFyA_G^A#~L zy@oy+oYuH4$(;1wEhs+8oai2+|0q>dd)0m)w0pL78_APAse{z&NR!`P7CqI=XZ z?&k0a+=+edpmc=x?Ec%J_=NU<`lvy7NbWfOe+UWN-Q9`&CD1vdZAk9efoqZ7qx-l) zdqn%ZkTDJYxn8osx~}Me5m4Mi`Z7VkMfOl&Snj-GK-XA7Ykt{d0_BS8cYp<4DZsEa z;56uiZwly>_(wu%`~R5)MFkFoge7N4=v$*noAwp#`#{7@11r9Q1y)0B!vZ2oMRuel zBAB%m>}!NtgM=kyV1+qZ=yI~yX=ibzli^G)$&xZUU08ebc1Nc%mNV-QzY+wNLv&B? z(*o@wyVF5euKxmsM+TUo)0oI10mRU0%($Vhi0%3Pc|kQHd8GEpfb9?gh(M_g`mFg? zra$C&yudlX^R&3@X}fjv)PcEq;eJ{`~(Bmj>8 zC}b1mC+m010RQnV#V0fH?mZmnj3VImQ#AOe@2BF9;H@3{CtNT8F88e(`X~5S9#AWM z!T)Ix{FACzc()5M1=d3S=JpfX4=sOX@DoSs+f@TS6ciC3%V4;tMvL!frxZFlr4-t? zqawyJL(y?BChmBMMU5ZaRTO5|4mg{sCAM!SegI%ZjQ#4uUz$(!%Y&qjzMi&I6(;Zv zC4PVSK%I}hRUKmdWD|Khy@bjths5DmKmvh;C1nV!RTNLFv?n)O|aNN01)e zebb4_|5gpUh;&hlM3*WOU5^k_;+w39^uC|!kg{(CaS77=xpyXU%<(IQA-I3~CzlB7 z<8VZpNOxoS_SJ&9tY~P*sXU5}(E*SyNh2Jb7VC=4<7kTc5fjRfOLa68g)Z zz9LI=!p)$n567T?aXTsx@s3K5Y~qWsZBL>52eODehhAcAKzRg5c5Xf-zn>-&(03a# zwuImcqWUDg@(@>#z~8XGzo9}WG7%h+FQ3-mVZ<}}J5EryJig_`EsE#fz?%Sk5p98D z7^q`nDjn%PWZ=|?;)u3H_0C8;wV~}I&R6--Zcx|9z=*b3JLRD-+29z325O0Qic0iWlbsMk4^}#&<9_v$ahHKXHBE zlex-6cAw-TpR!E6Q3Aj$4&nE`i0<<79Q=9W0e)rT$TtDTLh}+>pTOXKf6VBN2+daB zvAbE>z_*z%eORMiDyA=CHkw3Jqu+5r@NXq)7(Y`jsLP(;Dwi|%VM(?2S*$1O+dz z+0YqbB+4$>lQbxHw9d+ZYJ|r zF3Xl6`!c3+7^}>7!xokD%Mv>tLoUw@g}(h+#SO%Nu4+w%_!5{W%u0lp6KcwZ z;1ypv;913G9Ij5_^e>=1)I2UZ`Y6chDQxi?mVp=53f;>xjl|L7eaUS8j@4wgb&ZGp zIT;05-+84gI(RhI=K>Yi^B8&Yp!W3}b;^p-@QWIYabl(6xa@;PsDYnt^WXYipBQH) z%>tJL^-A3MS~cL?tT%bwT@4yN&ALqcV*rD{>n9)MbX5VCOYlY6f7&VjK!=>gkmTXO z2zEM>ytF;lnR%&sc2Q|6BGPjGITS5ox6L_WFz*9AfiDf&DLJaxm01b@{gYnC_j57I z#-Sai5KhMPLEwr>Hl{9AL@9HFs2|s3*~Jr%J)m8!9B+q0XRZ? zffb_?J>19BfRZ+eq0bfOHSuy)dr1pQd)-sD_97pmIo8U?xE%Pp6nE56P&IxAkS@hl z=Z|kB-krMm9E<9`}7^q!)6?Asni(9VMx_%rwK0R3VB*W5Yv(#rhV&Y%#$P zQptz;BLqr<`L@G7WlOg03@s{7u z$i}4S+zlXso=-{S&csJ?VLgtga#He66~V~0h*>B|CF8)$N-z{`G?|P`Q=`*{Sr!qI zZ>G=Oz(85t|NN2l0*|+Uo=1_zhDfLc1jx=HXL=QHRSI+nqbTL@|8+^kxUM)qIa<7j zW7_OI|8VOjq^%MtfNVT9#$~V~Qpac*%-8303#5D1~AP zD1^U=cO$jevz(*KQsW?QiuZ7aSB?lIy`M!@F3HYfpS&NXom+kqOy<=7{7x+OA>^K5 zURZE$r+!vlWnC$Qsq=6=2r1s!P+B0{xhWy6mjAm%b^PZXwp?={RY3yPO*a&FSgiL6 z{(dqh=KyKD61{h^Xa{PS>(x(Kncq(N8_2TU$IR$mc`>$gL)gT5LYy*_?0g#M&~;HI zX@g&uvpX|_F*_GBFu!-P5c$?|(PfD7G-r{E|9sv=qeyXi{FfBHgba5MY7wWDrCHL5 zrJ{x1x`L9l=R~Go(~;ul^HCvLdy;6Zq?Pj%lmEz+v{jTtj4bQ@Qd1Zye+TAiT@wQ4 zIoV8P^JQVvB7$(O1i_biA)AcTJweuqcxb{*PRNYa=qwUl_@W<30w1}@TBZ`yLOlOZ zu30JGFc9jh)z7Yz2TS_XeN)r$bxO(=p)=~L8wM)P*Uzb~p}1gGXIO7(47mVmS;9V< zg_Q6$gK-q?*;9LROUU|tZleF3uF9+Hy1o6grB-6?Ji!lDrr^&~_J)-!c*JB$P2pkO zw9#6O+rdLeFKcm$Mttca+o?m771+ z{kOkeD(zm$!g>4z&kwL*%nCzh^;jCKn$rA>k*kX}MSg`|jL*q9%USjm#teMiAA=xf z1(`C><9(42+Yru)F+6R<9UHB66^xsDN1>33>K#J5$-p&4+KcMoZ3OC4R9D}DQDc`E z|7v!(ict!5(8SZ93-ydz2PX5?^J%}(D_=4H7|T;LyzOe%Eu_ZibT+}}@jVDaGa8AX zHZw9}kv?C-0C)@f;2@u}8Py8X4sHt+mLU>iOcFYYa3BrS&hwKD5T#hQy0x6-*v}o@ zW!j>OxyydxkZf+Kw5hd~_ADQ@TKf2E*(Y0C*+ZyVps_={p!uX8spxF13e=~;N-w)q zY+CVr!MFL+!kibb@DHvEHo zkkOchwv4O5xA8Y_ZI`2cdoSSVo|Y<)bGWv&V9}pAAua7?;8dkqFCs*l`mvi=w$($q zbF~hYKti%eK5WYw`EV2@4C36}@ZeOxdz)d}$erR!A)kLUINW14eO{ zU1{@|aRbsfsGHJC&=`~UI+8S#)B)t^7dv90$W{n64lj@-loxiM!!M<@f6unkn#OSZj>mWT>f)I-kg$#a5_MnF>WceIBeK-<`^M3za*&!Ur08E+Hw-AMZy=O z&5jM(oOnj-36%Q#Bm_+pN`c-9Vr(AhaSBKCmmCpYsA8LJ!3I;*)*D3U{hZ4UxPxBCkwUZAOBAs&94%UKnZaTDMxN*y`us@O5+^TN=d%2c>q^8L z8db{~kZaKN*wx>&REVsN+%LYwvpDp?hNHvr6YJ-W5!aAx%IopvA;1rb12c=SJAnD` zUZdJh{6_{qVKcmQBJB0TD9(TmTYSsLqx+Tjrceksx=oZYP_2xf(qHmX?S|Rd%9j}j zo*knvm@(L~bW0c2a5#@s>eK0`)xT-SpFmB6nSX&zGFl+JoL?aa`maC33Hk^Gb*-aN zz?>@Q{IM-SN7zW-l(Rq~!rV<&`p6(x>t^H4(3lF1(8RAIIPCnzIn0cSX+t|K(7K?* zKG@q(kMH9MtN7z8o-Fv3%81DsjzOCyD3XcJ;u&bZFn<4hh;2$X&W{!~YBK;=j!-Mo zDVw(c+L`%8A}K3mMMx7FL=7DUesAYBS1*inl2@McuXCvdJT3c%Dt5f@?Aj&8tz|R0 z5V1w9CPaujd30omW2jH}R=0WsINF!>h9wXiyNI)ix-Nu4 zO5@q~=OT=8?0H`VlPoKJ)e6rtT5MCOn9tJJIciSsghH{l13g=rV=wQ`i*+>n`WXvC z{tMz4Zx@o>0Xuw1$In<)7AoU`%jY31I0^U1Agr_3`4>fC|It#|zE{fnPR5;yEU6~e zuYz(h{AlbWgc5B20w*_0*b&^}Zu~$Czbm%5{x)a3Th>ur6E{;cyDt*{j4)32wwUi_ zpg@J}Z!YrS9>e*pOlmC!Zj+ zU)`;I0pf>#_(=nWWQ2zrdDyc>jO{aLGkQ_2Zlu`&Mx;OwF?TT@FFeaDo*!UIVC(}u zOoW)r$RhY~EAc~Cs=xu+uOUc;%yC_FQFy>t;`2rNK|!7?ZE6|iwO-ha!X>sjdAXnp zqY5${pLF?A$ld+4VUo3TI6W&1xGIt?$#y&8(#^x)xYuqwxa`;GfR(ghS)gj=`&5x$eBCLKL;8NzzgA!;Z)GF*EHRwto(`Ql}O zsvHQzU@A!WRMPAD&p%n;2Y+5Z!~Lpw{d;S9y&e#nPFFv9uh)lmmvjDN1=sDNKT=*T zcXCaqVxMr=>7%`KIlI_$!M)|Y)gzC#UlP4@D;Gr3JH&fWW>|mj)#_hJ#Q_9;o?bB8 z*1HJ9Dzx^8x@-DoUi;deYcPMcx#1j*wa3-&FigrEEZUr45xL~<;BhYloD`|Lw~}uA znC`k{TEjXFw$0Z*@LwyY(CUV6H~x<=10JqoFkL-3W@eu*L5q3RLi7(x;l(C#5WoD< zWc`i$;06^6j0A(v_x#)CAtL)BN`*jDWJ3u7`B1>($0pxp^);dK5q)NZKl2?~)mNxK zIy2q&E!#PktkD)Vrf@Z;v@KPEi}tj9>aia#Y<@%PGGL1P09;GAt6=7<7{tp)j2SUs zRbsolZQZpXljMoqtKbp}1m3$+PMnp>=)1$+^;v9+3|?J%J39#zED zYwjs3oSonVk z)lJHxU09jm`$IRA1+@0{4TjK)SA>e#_;0fSU)mHz;1jX6B^wCOO}2TK18%V_n zj=eOerIn{8o&xjwa9}7~Vg2NV7>^=iw^92qpan@iFapB8mfAFA4|1t;JYj1jbsbVP zW*R)GVV?XTvl4~wZUd1P7qntH?Rwna(dnqOt<70ev%;fDGuCpV(P;<;p*Wc$K8e1? zWTm@|zgD&^WGxSgzZ3N>Vzq^B;|+?_y0G?_d&*Bnb0S1u!7T^l8&HgC&SYKPz{48& zwjlznA`k4CUT*-)X*dbd>{c)~+R!)eZ$Mg?I1sJGPEd>{H+7P=|8DwDyN

    `&E36 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%0DdqA{zQ&boLy&i5?@O2yy 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�wM2NLZ4kjR-O(r!rwA!`+}Fu> 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

    PC|90;l^+vZohK#bdh*5IcETNpTPj7;BzL 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

    -!MS^!jk};?`Okdl_!z!#2qVpJfpK+(zO)@g&X zn3W_GGglJkrH>K{Iwd2Fa*&8DM`wiZArKB~oN=H|T&lTa*IPpq*S#iu zsu8-Dp0YU+y8_ycQ0$y9WGy48Llld>LpDdo<6Ocp!orwek;CU){C zcj^@4p{^PpCq9+FRvfM35A|T+W+Bm`x9V^tb5#t!LMyTTrGJ;o`>VX*rrfToz#<=I zoeIFE-(?dsLeL?Gv_VH<2QdXPbP-d6=qiu{C$>OzP2%g`Dzpj1C!@XB=!9IOZu}|6 zSSfiZ;bs>vq#b5N>V%0h1Ku+xk)TPMBeF{uOqcc+SY$dbu9h;f!H;N1#~mMI$$-+7 z!0=8s+++>aYu=1QXexm8Mv;ZUfFK!TiZREe5k_o3id#EKDT4kfR_n7Epd1MBY* zJyQp;u^`he)hI!yxe0kv@*)JSAO{nh0^%1_LPLEbOTx+$KAsk^Sid znW#QCcm2DVdc5F?iemNOzzCGClG25*t80l>>8U) zKUAbfT7GTA%ez3hadztDmd5MG1r0J-wix!tNQC(Q-#xP?RZkyptkAf6W6%=^^>q^p zc4X8%=DnQO`7@lL?T^=1)DoP~$2yis z*)CU$Udys`+n*a9&^cgF64&L>e682@(A}-JQ-Rys>{osGv~L`EB&JUJ-&m|s2zNvf z%{OiJc*dEir`sXj)*^4zrWA(j{0g%QeG6j?LybtrDU%kdWos++)Q9VQjffjcS_hJIw_-eoKQs>3U)E3d8V3>SMqm>{V2V~#H9x5R;qSU9Exv9$AhWyOWFOu zv>1aPlplR;l(aj-sOUFAVUi)`Efc;~y4hjnuBwAh9fiB3TPk+YS0ycxC1G4EmAkY} zRJI-hLpfxo=}m<>GwlV+y(m|4O_^&shiok6)7z`RP|Gn46Ur| z`?FXsPD|n(sx3}Z^9OJBw8bmv(d-7`Hk_@^f?LIBcYp#o&E(~Q^K|#S6;H$SWo-Oq zS@V1yK2|n(n^`y1EVL2^c|zd5RfDZ!wtZyUvt2u@X`ZZP zL&mecAk4XZgD(T3O{(KDzSR;u4Z=t)=w@={ zpWoxkdtUeY5t>$NLG>d1vtV2WR4FUQYVM-kbLFD#93g&?Ct=i*FGXatZ+N(y+Rofq z3z{&G3_8$(3agaluxr2`Z5K|nB~8J2b_ynEdw?3ehlP7EtTUAWchNQM2Graa+nl61 z7~zZW5}NpVVho({*eYVaRF>vAYW(6Wtb$hVD+J`0qAc_K^6Azuz1}a2)q|Y(1Xzxk zwTB};`w$DK*3$KF>o!bU<{IZOX@?&^?#@Re%fWnENQ7T}Dl_4jRSMnLTB=u!` zl2%&LK_A({h@GR98u}Tz1ofkT5Y{pkdcxPlji6^b_S|~Hb@A__D@(2ofbkWjoizW zLO~mj*H3%TcQ>El@5Gftuv!sTEqm?sR)VDCfh)W><+PoNZpq@~7o_rSNUjnK+nv#~vfMG@lPQ z58Z!}Z)_4SCPuJt-|kTVSwH$0(&WFu-~XDMF)q>n6D8;~pTWS~;%u`_xxCgK(L%eW zw&hv@aV<<~bC`YPCoPFI`iqhd8Yt-B;0!XW00GoJn2Cv0&(Dn1{_N=KWtVTl6-?BO z)C|;2hF^L~6->tt z@C-$!RgnejHDE*bSD;n1=ibn6V3_HPwymwfT^hX>BMkuJ(LZ^wS(G^3+Bd$D8ePW z!a)`z$@N%=emVIliYt!3|0Mx--i4=hD;(_9-p!Cbm_~0iiD#MA@|}|V)mz>sE3q42 zMlDhfSAZ9&5IgGZSh#QMH?)je^p8?;12O3Gxr~LAlb+N+UA*;bB z@)7$9fvlnz1|lEE>kN?@KW`$y94n%_ro+H!Cj8XqEg#@rMdh@d;LzwveHcX5sa1Vm zp_Wl;s0Y<2d?`$7VUWi2D6zSx+RM&6TT2tLUO7aDP*ks+F#KdqfOe&OVUUWo% zHW6lgKJje{_00QU`C_Ll`=rPYBw9^@bwP)~gJ4@$dI?xd)7TDF3+~Q>ZO&qITEG|J z2yV0Qx14!-@crF24T3_%|5}LrFJhJc<1@%a-)j6B8n=?N1D2O5*VVLnU*ak zY7nNV95Pc00~C_ex5yIA`V`|90R!15c4)uV$WUVjhO`t@se{QYeGCvx+X=( zIq<8`$fy@n@Iev7NXyk!^5Ke6de9Q-K{+F-(O0%=&#O7mGTg7DrpaNCnG@4Ta{lR) zU*=x#b6a+*);yOl#4LOj(a_{lFVv*dgQRbQ6jmfO;wQqrI74L4m^w~*ffYWmAl0NU zjz}*D;R@bFP;8D%t!;y~ZZyXOqY4=bC`1Q>(chl$V?EJ#g~M! zR(MH@jhZn>!IJdd!RL{^CNm2LGmSBkAg-gyf1s|utnnYo%`ONoiC?cu5sBl+J_gQ|03kYRj0zMZ&q#F6J6y z;yS5D=N(|cvK=sU#(aa*IB}5%mc{%eo7Wfs2i73L;%P%5Q)2T%owx172gJ2X9q_qp zGAFC}s;FK=9ct}koRgihriwzkSN(9jG(BeU%j%WE))0+iH?4%=TpBV5LqrriPIJgD z!e0~(3sCcZzLI~9p7&1UKYvGUDkU5aly8|2IO}y_t%^QxF;n(qp_v58@s;IOxh_z& zPYF66)~Yyf`5liK6rR6AGHnij4P4@_%`k{v^ zu^k0ZN89f~+wTc;+qB#H`5jo_f52NyVfv9xX=C;f&ZqM)+*-a^P0MHxP0S8H$925I z;5NBDFvr}!sDhOujfXvN{Xoqlg*T}&f;hObl{WpB>ml*{6B=fQp&XsM+B7h%5wBzoehGM@K-pTzZ${JX}fP-iRh!f z50y9cob{>`GKpKlN~+^{R(PEYf?m|4i0~@1)7`aEGqp}HwT$5y@G?Wl0S+_E=_LI2 zxFl{P>NO4OD;L&PcX83{cQEe)#b@NgSCg?7{FbO+w#ePPtls^25XT@}M-TCL1ry#@p8r(?JyoS#u=k*z@_GeLL zNVjjjpVOuxF?PT!w8zWWcd){|yPWv|{@Z63o_kDTziwxI|1@UfUnn#Fz0dqRGpi1% zrnQLrK?ef^L#HP`(uKe%4j~Bv)32}3Ob`=;AV)B(Tghv@6~NFBlS-0jRk=2K>Cw10 zSy-u}xlB6HV6Lj!e)ZaQ)xK%Cz{C>qFA zJ~?UU1W6g7qSL&#qjF)r2vEtiUOZ4aYFT7$Ub|E|YFmVB=A5paaw*TWZXB;{+ca<1 zd`QTHn5GTHR;52KRP!0`Ysw&1rj8_l6HK*>H5sV|aWPoEfnE??o)) z4MZXfA;G|3T8(ytJ!E(0i}VL1K5pJDiMqMN4_m*^8aro|wbaHxp;2OH!!{{_2@%*Y z&r;r_HO_^7u{pguRimPkHpIf~1jwQ&T25X#?u!dzwavA(&8oypVFMM>tCSzF)iw2$zt_?$Ne-MlslCg% z4~Vla7^!a(YHpI>7;oKJMIEOyU%`r&bl`*tKj^aSs*Gu!2--h|oD`p1GoimYyqd7) zYDTBEE@MYWj^nLl$g^dng=C2zBGsX|eDFikC`mQEV$9iT3ca4Wh^H($d1u)LiyGW*D9sdf{vke8kg>_uj@`$sA2Gq<-WKf7 zb?1<-s}791qsq?laupY*Ri!_>wco-C*caR0`{`e=_)z!9%-%CbJ!(6;uBD)-uzRs-4!KCdTFE^dMN%BFb!+hpd z5q4^FncJ_5_DredCpm*{IF4l`Hh~G#%c$eff$#3L!4U&6WT_1X>av$iq?oKjWQ6W> z0E#a0nM{x6gT@P&0mpk|F5>J)lg>Td4dlrPbu#j?tt9L_yN~W-Yst-xr*1=!A4pL_ z@mwfj&6@aCmufR|CbQfiyn^%8_jm_p&3++a7Ls_;gZ;_ zN};|I^N56&xw{jWpx+ca2&e%ZOfz#QTKtyfcff>y#{L{WFhg47+{a@RR^>;Ob6ul( zTXnwT)#ln|Pu?Q{g zgBFu74H8#&p=C^`lFD4_);_>+qf)30*% z{bR~!`uf#$eSB&J>{jumnc%h&5As*M4AsR19|Adb_RWomO?n4BSD+D7Cu{)$rQS(y z+A16xXmvcXN=asC1XV?Lrcl`_ZcPX0MM^SdKPmYBcc!T2eLk#Px?X4Fy$IpfWF%`k zqeyke&hJ${C|;0z7+3Xn=Mh>iBz?;%?KUF&pkI(w{DLGXr9Lb7bl<)j`>fS;Ud{bE zoQ>#Dwl+@D_k@U1DVB06V@egxk!8TyTq&p0idQ}^O=VEDxu6pxtC#k}i^zMWyM^YD z_HM6o8w-<~Nl{F}cV{dVGJ|tfN{i8y)C`h^wBK5zo@wfbGSVO?T|Wc!k6ZA>XT!Kv z1LF-&h9Q^q2%zMB^p(D^L@T_3KJf#p@7I9}J{93pq)9&eqgWdJgisnmAD)=L|0bel z_3#{65AdMgGBoLM`EPD3z$}e+O#3)A7{^38p^9Vr=jkKE*EDNu;*j zD;U^JDQ{L-+>_}Ia3F3>w+j~5wvgX^D7MY@6QZrZFzU1vL`?nh;tYwGJYkj8;_sTR zdx;mS;lroCpn)sYN>IV*5@9&9o($`mY8(fM@e$AHpqG-g$*ahsqOm1#Eh!A zhJOvf;u@Z_L?>Uq4{M#k+JbS_)KX5}SOs1y#n`mHwQUBBCmJfG9SScA_Kb(nLUoN8 zx=xANE_TQ&zR~DfzEkZyy7BHRw-$sK+CHPwK2!!V_<+I)i_{EKOl=LTbq}1dz*eEb z6DS==v~~??X16)F3kT5AvwM$26n3Vss9~(^At}`&St?`|H!8pI?@&f;-c@uKCNIFR zf`E-wEJ;jY1sD+i(F*Ob1iMuU?ZAi@kJ==(v$%Hdg~mm4%Er)bCo%UI+a#6876xvT zTp*}reG*?l^c1u2^%*L0l>-0KN1Qu*sGqcTt&m!j6WnENzXeF>K(-lrbwJ()QGp7h7~rL~Qxz9cS`%4uDW|B2O*wevBBG1Grg*Jo z%#0zc<^CEvcnkK&{QXJy_kK-?&b0vbWz0zY1#teK2f_abY~26m%h8z(sj8^@6&@T- zgOKG^Dk+RG7($Rkt%qs>8W09r3ToDPM&#yOOaLE`4$|ms)7akBblF6!UEzW1&r^(_ zgI`w6MBo!g5b7!;3-?Lv@Z05bBX#tk>-UG(GnzjUmHS3#k{}roNJs=vV zAfFIi{kTSuzcsUKX*D)fBT0n|*0QNT2$njLTAA89$vg=w=?hm%ilmODPFKq^aq~5T zmPGys4;M~-LA|c7qkm$!+B-LR%Q!BRq^j2D5F1oUy>1`t%Zke5Dm%!~k-v-WS-8v2 zt6&GjpF~92H5^PSqc1iHf2@2%&KWd3lLXhJQLl2N8uVqn{pIl#uiUPvbsY-J z*P9VNj$?oJO@o8u2H8Bt_x9086@RvK^TnGGN~mwnKg|ftY=a>338Ybv2V?H#&0V!8 zAGt}SFQT`%W>C&87;%?8JYI2=(_c8W7t+)UX$0zY?T1}CvGp3NiLd=S_973I3g3OVy}7zzy@)AIg)f% z3#n4Aa-$8u9M4 zsC@3v{^-b>Aqb2`yO!0S`20{E>{&Yn1%Gw2#mse0G2dU)Q-jdH= zuXyZn2q7LL@QI%ji?`~EWj|~l9p@w2SFil7cSg^0dlGT%*_okMH=&3v;C&til}D6j)9WI~MGq%*k8EULnaTFGSHoU3X3lcjQE~c2YE&x@*UX*`d9-pa!2~R zlfI(#vQQNPvK8e)Cwu+wu(R=k68Ua%7-<{#~-9$j3i9h<3%25Zk*0rBv zn4%t_j33v(?^frq4LuD}Uf4$hQs+nZ%nanXw2<)+FD^TxGv?M^VtIl#>=GzbboipV zf%0nrKMwa0ZtYU$Y(Ypcv}@v*HklTskr`IC_Q*XJdiTLlaQev@TG*3B+t0q+q^V?I z@_INDODSWLjyJ!}4=4Yd5^$A_+4l2mp1%73mRkGUT>cHcNsM2WS(p2A{rw*AF9?C4 z@YqBhL|v8^RH&>xAguXQi~@qBa-fg_+>w|rcCW!ayDTAG?8nVFZl%Rq12lO#ZOyc8 zhxzI_b8&?FRJzP?1uvu z2WJ)mIxrJM&bz=18I_)&6(wh8AB1r}3IPXpBG3!n4>(SY4p~|Ft33|Ujl)9G+bD;k z+H|xdeFZufdO2Qy?-z6#*ReJK(SPtqhI(^+17Don_5Up=_qR0uUrz3Ssgsz$=D=Td z@()f9jgx`i=ReZxi@pi^{)3Z)G|$ZaubkYc%hO5oWmnfW-}ejy@*1id^#97q?ZyAM zoZR*oC+GTaoE+VS)arcUmsN1fNau*5RYaW1vU1UQU2W8qhBGtGuZvT*R^pKM;Phfo zPD0}PHgz$l^312%ZIyuf&yV4B2v_&(O&5cDD1^Jvmb%H+8JF)jzPkQjH{+4YQc$=* zPGa0!Ib7a?CKtX52FKXoIN)2&PyfJ5{}i(_{cBE6{9p5&e?Q}2%=GJ5z{$+g(8&Hj zWq$TX4o;Si{~_{A{C8Pqe$|;tnpcW2NNtQt!-`@m2+1?C2!?O;9_Qc)JJp%BTsLKQ zfZuT|KI&wZBpuWeOoqqNmd8}*#QTf)>o>W z&z)BU7#3OOn?aH{I*CJ;1<96bUL$^d`LQ*2>P2(7bRqGxE^tDwAr^2p9jR4}z88py z65UA2rA*H0!4>;;Po%t#gAGVt-~t5h_ZSmKrfILyP8Y_f_h7c) z-^qKFYASYe{Y0VgsGj9}Wud%PuET)@d&B&~{K|kR{V+tB1@vkB&`_8-eKBE@&uIY8epu9GhJM?4 zJ(a=c6hrdTmhg2-w42B7(Tc%-?9dKdYN^*|ZeV?C|&QCC-7*Ke)GZI@d z4llzbU5nyMB-E&^RHfG~welT!_LK^Ls;)L1Sao+1ZjuG^#-Vlr0Hg(Oq z)-vVNXG+_ff+4HK*;@b(J8vsUV{{xnn+d~7xz@}rE!F5u6Ot-Cxa*MbB`Y{o8mTQ{ zeJ?jL3%<3?-O;$qYmEX3guM{iB<4ubIn05EiQ2<&PacbkB1_ z5y)|#pD5@ZbTOrwEQhQ9G&SLBRsNXi{xt{{@j+H-iV0g4pF`XjX5_l1gVi4w7H-gx zQ*zHkVO54a?MZPYmA>ERtW%7;T{TAOgm|$4C)YbGz15%0;DpBG`81c(i`eE8 z%*8JohIhjSJQiTZ45aZh9nQ?81lu%Me%663ogGnBr(Y>xgKcd9-OkXQK;YJlaT>1S zq?MHSlcrDF?g`P}MDWt*gRIS=dN#YS`nXVB)f%}t$$wG$p5_dH@m>xb_;_Jy-xel+ zKV9aq6dzJKtJsSb#qnSu8 zf91X!NWg)o&b3Sn;s*g=#OFSG&rPzfKajdN**dxqfoiXYfI`Cyqr=j|-SG7m&Z~zZDU{Of(z}zBuMWcPV5q z;vWq85)DU4-ejQ!iQo|_L`c}Fg%NQ^?Nvm@r!30H6g!ZMGZgLtkvdJ}lMhBjh2P!* zuW<4XnBn3B5~5^v84M%S&YIIIQ9bzLl(U!eVZ3ea{=uT&=RML8Ik z^B&Yia}LyK8N%KmfO7EIle`|?+(~q{djB0;DB$Ns*FN4CO)h-}pRF{RORSnluecKZ z+mG&n)T8%db4eJ?h+qYQxxDTyykv*2fUDn^y{D8OykB=tDB@i?rxNTUh={h8(RK>* z4b8<1tDBHpo``YxmG$YkosV;bLYN{%&5+QXj=F4Jt87s-9Yzq+-w0!yhkv>qtA%$p z*_tzt7E&SU60zIZJyzsqP+DQ0Ki~Y0)1?Y>y|dPA-m4PHrF{xpdbY;GS(v zoZm`*A+Vdk)1+R3WO=lPZK!+;gU-WXppzk@Nj5g~YG7#jW*#GgvyBpq_Gv?NmOiSp zb<)vo8-`J(&x}g?DJds1E}!M+MR6emIX;V3YJ%rWfd|8nZO-=>UN2mNNimZNzMRcM z;C{Eb)tP4|{u2P%Pmi|TZiEIHrl71MOS3s5WA`pCPoLiP5uB~O{ux;cl}ql=;_+A2 z+aeZZkLaOLg_BC!4V!>`Bh)?b9BTDB<@y$d+C1tL8cvu5VAvbkPQ9XBYJ z&0ZxPH@|zU`=i9}*eMgdylVgaxe{t&qti{0gA^p0s1qReL15iTX6#$DEj zI8YRDn+HY|1||x0ag?LfO$OG-`l;b$C=&;E5bkw}N{Cds(+WRX1)MNL5z>k0J{r^-hV-KQ!3@3F)b%^6|$&6GI zrw_zlus2cT6BFCv1gE_Ca27ObMm08sC5yF(Md94%6gC9T2~c!O(jD`g<^I&%jX^u! zocrR)U#)%7+-JMgD+7plM#FO7@4Y@Nw%+a@C)1y{CPuukZ#=GmXJ}t6uG6vJs=$7i z^=6~i${{+1sTG8!aSrMrXxLfooW##ZnYo=NN{2;Vtph?j*#(40!V#Ggh+4_ zDRH_IEmD01kn)g8AesVNeR`0Pkm8VLkfOvmiMCRGc96-Eu2BI;60TVRRK!{o0AVB@ zY5)n64kf@Ga#_qZHNb+TBM(hSiyAPeu|P;lKmq`5Oo=Wxul*)MgSLy=XgUC`tQGt< zOOMxF-ooD(`CJyZyn{{Y9q1zY>=TeJ_FNjqw+l~+*9Wh}9l@o6`FJgJ{Cu%mi=oA z-hJJa+h`3fRApbt&^tjSzU4xzAB(9Vi$&`y2bDDn6Tq-X&!AEpkq|pWmM=2;k&v0= z9eK8$Jrj))TA;*Wh{rgV6RANUZgF4=4m&QELcl?_`nX=(Vr^vHowb);(7}o5>AH6R~LR zSXxph)D5U4sd2}-kL@|=SnUVRDJ9Y0@^j1=Y6Z{FPu{nPoJ(s)LE^r7$~r`(%UMRT z*bBKoxJ7i}K)%MoO1r5v->KN_xe6m2R$~?p;555IBi9Fu;(Cm&D3Db^8lP`TyCRH$25ZQ#oHg}EY^2Kd{f^O1KeB}ybp zjw{|6W?<;**!@4Gy=7FJ4b-L!6xZVJ?(SOLU4pwi6sJJZ;_mLj-QA13OM@2=1q#JF zdEaklzL~Sm`R1%Ozq0Z>S;@Yh>)!Y7M(ENAup!q`HNVJHK{L(iwjtJKIkU}YAwKpb z;MItXokH0ojmt5z%V=TF6Xl1?exuT~0Bhw;@1&gEys1qN8tor>bi0}D5!#;;q8_LT zo@Nvm_r?S*?(n1=-_>>f4Cdl=v;M>W`eSuO|6r1JQ(5F8owxBG(?0{mP(kfetFj@t zJMw#QhXI zf;_y5tV><4^~v-q6SLYtOKe$buC3=%e=b2~pU!W|EFPl!k`y;&^1@p|)Od;KRZA0` zY?^o4vyB~TQ^U^w*rh#SWSaHX4i-93y7DH+?501Qo?eV|fBC7la_0j3GI>;H(r((X zfW^~&J^nrR*;S;UZ?8H=;Efj%EN)la3okf+q!rxjx6i9C)BTLyu8^r49Hw06l3i+2 zpl&kFVJPbS0DID-W=KL@1v%WyMnqVGnt0viW+zelvi!}R<+z1fZ1)#o0^`n1iXtH#l43GTspk{>E1kH-v+RAQ&$>?bCB%jbrYzQizb_T zU!fd>-1}CE)n1Fde7b$}Hl-?Fej1ZmRwHo-)EbZ-yZpvJk$>*m0+9wc{5tGgb$vE z-@saC&aEX)=d^WclG?PsHyB^sp%KB8f%h_OKQ_2fAaFkisuE0oPU|L0^`CJwO<&m( z4o$BA?IzbRvVo0EFa*Uq?N2YzMjxu>iSa8ZLwUm9oHPIiuGR?Aar5Jv-F!ZmWK_w5>>@;_DbE`ZhEagY*5DLotx+u$4iSpFa_e5mB7AmW0vXaDq{2HUPVt=&7DzB0~`M!g~ zIHQVhw@bPE^C}xvv!b#&-C2Xlun(s1>ve*Jnr}rdH`Y(|`}5|d@=N-;LjyCejbfkA zk-v_O4L=wL4Cg~D>ZhDe$#_j=T$fH|Hj_f?3@=`nlD~XjG7>D2TaL`jdIVq71)4v;2#PMV8dYtx#lQe!>OaX;XXE3G^Vo;4DTly z_V*q~w7!)}CNgztx_>I&(dDkjdI%iE<&2F+!wV#vKlXtAWpBh}8(vYcq;_XC$j3?9 zyz(7G*MEqJ1!Aey489Pg3WLoH#7A%8c=g|E?0K223J3g%f)a!=8mwo~Icz|fy_FJI zG4^(Olb?39i+)1`cS0EihV-|C@JH&#c_wzJvMGFBxF;SCyyEbM1U-e_iBBWMT#Emu zYxQiY#5xSv5~+|pa_*dYd;u>Y0B`vljCkYG@~Cw|IQ(7tJ4fCJq%%iz$8k@NY#td} z%&WS-0_nU?4&H9>1{si+!le-6Td3U33xV%bi$Y7^;2UqR-H+&|BxM(JJX zK21tJ83DmMm>$IV=D`#5ozcz~zhawj-y&l4R4u79<946mDt-BFZv}mY52O1?yomQk z)gRqfWCI%;eOp^qErsX%b6b3f=a0PyTLk}#g%?uysOT(CY*rW;WNM5V&forx?C5$; zuXmW=1?0@{$mOoA?a-y8v`oEj5KB2;IbN`RH1<_ui{+9Y3&tvi zf!kO2fzFTFyGU8@(a^te6^p)OvLhFdB?cGnL_6l!>?&=|?xCsU5a88a549}w8s&eR&?+dJ_&St0=| z6=5L#K}H??;i-63C7#008Q%MJL=jUj%L?MC)L9IK1hc(yq@PtMzQgimzkuCB0k|N5`0EPJjRhOPG( z$#;235i~ePx}04@Q|e;G*^OL#NLJSwzRqyFc6lD3VpdaEGpV$kiakXF=Y>w?)~_lg zlVq$slN7$0Ldv3UGp^TDJLj<#2d4JiV}-l%T30lA*S%po@zJ%zH`VK;)cCtuC-{(v zrLffoh@bwb?f(@+8p8~uNE=CrY2<^!wPnU>BaPWYlDUd`OB=oZgB1~nx%uFyA-t3N~25hp;lrBc$=y2#oK65{j#~G|vO- z!zl}vfY*Op$fB*nIP4&gI*tBID*#y>{;!$?|L2y@e}0DlT$DE)!+c-QU^U#MQ5_T{ zIn@od6mFu`&&U_%RORr(0)C(^#wjST1W5aP&kz!7(Iqq(2qALnCb!a;QMONK8JF> zx?fBKdUe06c}=_u^xFJa4H_Kos=XV|!PQE|$K$augVp^SgVj{Rsfj+cC!j8o_>u$P^|AxHSgj6qKXBD3(4dBH zX9{dtcXCKqhj)N&z&d=|=3p~rp}QK|z#W*l|6;&8MBio*wI>CY2My| z!0)&kOYYDHUAW{x-CqnTU{|PNeX7tkyk)Ix-wKSVLO8q~uHdJLhaH{JG72^rte>+R$7dwO;Px&bY;` z8(V54dP-;V=q50kN9Zx(7|T9ZI@TCdrLb&~=6c84peMeGt8avEzFGO4wABLAxz6WA zMmLU?oXpKVF2BUGU8rOW{K5WkGff~jqnx3GN{gP2yYSmUqoOX=4nbvlhNk47&h_Og z&XK7XoAgn&GR;Fl-7Uei@6GtS8Meoyf^dvC^Q&4(9)!#C=>{%e+bl(r*r7t~2`Sr4HBbr(s#FRTmjyAmosHr%?N9FD_6wiN;yN#(>{Iv z-nPY%2B2>$rfhc*vmsf7m{IwiGALaH*x4(J=@TB#IA>DGbN*T-r=vOB4JlS%FD>Pga&SbY%IQ5M zwktdxpQUQt>+B$tVteUe>;_h@363~F=3(rY(8)7BhVQOS(mtZYVClwi${(i?T=LN= zDsDc!jmfsJev)prDft_n_ISZm2oClcSH@Ip58^Cn*W+n?_4z(H^I5t3Lg^vUz#kd^ z4S7Yq>bx#Q@8G>BSXPR;XV-*7boaQvc9O6v=N7GNAmwa|%;9o-f2z%DMu1!BB~_NM zCs&N+(58JhIB`apddaP2sX(o_&&ZbVXZxi@*Uhwr+9#q2xFNcpdH5QvHXw0v z|5)sz#gpBL)y0b3mYav57A$1LG()HeI4QVY`IfyoamP55$}{6Zx0~9oEZ*0VCf3;* zpd#3Te;umqlxuykUywV7do=B`vB8M`-Z0^1_MTo>)v+z+R zk#swAuV9jZr=Rg}yQK&jKll5fJg`HUgqSfyF3zo%o{WmPmy_Eij@}bFelB%jf@8SP zxQW17iY1kf*v9=3!$;0ux;58{*KZfOf%JS*;!msDEcLG2b2Nkfu;bk}Mu60&Y!^#Xo`6PxZG zH)9L>(bR!x0)U4Wk1>H=9*4bF><`gAZ5VU@KYh7 zWlRq#+XaiXc$P*x4G6~$hql1I%dc>}_{$CBJaAF8dDG=wuLMIx!wrlFGWL{Y%zChQYNZmnu+cL_z@zfUZuot#H2et*1YWU6| z{N;__v3viO0A*7g6C`2>B}4o71ug`7l3ih8uai6KI9!MB8eQ5{;S95j<%P7zq|UFf(&d{g zfwn7{9TVjqC1vao5b?aVMc=5b9xHe265S)YP=j}XHx%Azbp^9?sh%mQ?Ygai$|%qG zI^CA}(2T}wHSD0^%9@~b67#YUwGHd(BabH%UMRGZ4lE`gTwmL3a>0xk)>QC9Z18@H zC0$*;OOxKY#+q;4hnb9&1OHY~G8FlGyreoa&J5QENd_zKf$qu%{|N5^@=zugY3gbw zc7H6xi-bVhctQebKCJzqb^G#EG-^!GT5E99uDB!Un+|FSv2r1}NoqIGZMFXEzRKps zBvL$28!sMTm1cFjm?jQQ8>~?VJ3{tcA0fY035AZVC!K?X}3`ioN&B6m*NWNIsvE z%n5w2+D<*5{rfZ45jx}O``1Ewlsgn8*|VpD$rwYrM#ZUdlUL<`JK0h8XnZXsN?e97 zU>ydecbWDWy%ksRge>WCa}vC;HyB!eu(LXc&7`BZh(!~9#$e!gsg4aZ7r(XH&lmV| z0KWfxFvGg$8OdxPBEPuv8B2WR@mqAQn~!o07@!8Y9B z%Kt!%m~8GeX$V?yL5gtyzfBt?O8bABPa&fz=YKwK$DY6qvYa+BG--5q8-x$zgo$I3 z%dpaKr~C+pRt-$rkWiL1gOr8B+3Wp;$|JK+B|G~ru{}VLk2!z=!LTz=BV=qjH#pfFg!De zV#o_N71lhI8WuB@;j3&B_9$Wk77bDYb{<9NSCBsJJe@E4kSCH0jW5~I9g+)`FXd1y zk_)9T(vT98i7eP7RPSSN3P=aGfzFq8=mK$10xTQKkJ+0I(hcqSxFZXOyy%6(YNa>g zn3y81a;Shy6oFp_h8&spEquSCc8uIucVY)ojtqb(LoqM|!$GCZ444uWR+K}*RvN16 z&_6OG(8wsC(1$RrG>{52#x$SmS*k%bumh=MTu-SiY*c$5PcbZPkuTOwSaXsC!fK}@l{;4+rA;YX`QMDffqAs!VTjXKhIs@Qn2pT1Kl`(z1W5KHVV z*jc~xdl8?23|LRUbKgbZcb~Y~E{c3=Sgsdc^Ek>B>-8%rvH54X>r@F!5Ob{3{zWqZ z<+5zm7=eB_{&n(L&{GS`_`YGY0MtOrSQ{8ypCsIL`atL@o&|j<)7qUNhz2EsB`c(G zk_zfCj#&%B$iyYIXo(|C_2pt?hT=w_*kgDJ&>3dxvPqul6A#mD=F-XR2bc_gSF&Cc z!HQ0*7``*2>`oIjnvSCwxzyW6zUTu79C{cV7FcE_$QXjWi>dRXbXh80g^Tp5ub9hJk-a~au$zp$P-o?b%LEo>D5D!E2%QM?^= zNt;Kjf{xbVK2&_n6qzWIR%@&4n&+z_*;uA{R!)mNq z{vA|DTP6~u>Z9Iq$=3T-xjiB5@@<^xU#c!6_GdhF(p}J4tJhg zO9emG>?6rVdMnwpu(*91&yGAI0de6v?ZtuZmH+zLhFBtu1HOT4rM z=mdVBT`s=#K80ZNc3D^|pv|S9@)yf3#?kJOub1{B@Y$}!ms?=2YPmh>H<$pj6^3Nr zX_kro$e0wg+;9qpae{_OPKb|BPHYd**_X#64i60;=6+OXY43;?5{YGd&#beU@@4xp zI+@K0Zybb<&I2c9<%hlmZVwyG?M`>&S5kN)GcFKJc*tR^0rk4^YRuhhK06bx>b_kGqvN7$jHByLX8!BQt za143j4NZIIkeO;PAE6r8!3Jc&3t z_g4Uk-74na+-dQ&!h{dpt%-6CJ0jcMJaVCw63MjTw9z7YdA|gcQ}>4u)vwdqAdGZZ zY@NaD+p%#?C{ig(QTGKPyj;R4x|6|aQPJC#q=KHjK&mGox>i|`QyMw3r-J`-V4V$_1PcJR}NFzXd~w|jQ1^<>j5;p(U`|EjJM%L zJ~E>9d!MB*7fbsbl*g`&QIs)MQu^F^Bc~j&6}7Koff_nWbeArP>?`}|{vOBub0K;U zew=RA9&-SK-j+2Ac!OtWHKQ@W=wr_P3xjGd7SfL-lNmlOpxmuY!JEb>3FAFtY7-#x z9v$4uspv%sE%V*3F6>6E0x`@Q&K^2!EvvEw?!~D&sG)}e&%fIKeODob+ZFkGg13Vh zKOx!d3VQ06zz^wxK_%M%v#x3*%`iU!wzV|1sxw0%OnFu>mF%ZC78TV9Y|%{<*xF8Y zvquD+wi?gjs0+^Q$LCoo_#xSjmuK~*E2B}PfbAz2w2}Y@HnfEDZ)^?#$rS)Iw1wG$L z9vys>IDN88bq9Z_IABLA5oYko#kRDeOBF{Pn!@>Hzv@(+FA)u4V%SEvI1?NK(;?TS z>}qU^(sQHZ!ECZux(VGWg&^;Q9$2xY9z4|{rn!y2%|D{9gMxrzja-mc9`tS=bd7I) z;-O2Z54Tun8n-#>oLfEVQD>Weo8yd*iNV;KmVOC|oeq6e(*BvCW{D_{bh| z;UT<_Ab+P6_>+xWX&$DBcj(3Js+PsR_SU9RON3Sz?L}BO zE}k%mo24t0n*W5-$Kz-fj5OCoO4327%4M=Kt|7Mx)e~CU-m;QUvN}PuFrv|5eRoBc zAOx-E&bBe_PP&<2wx>Ofpp4&hh{~beNh&t3x^7ed*URs_F-aHE`#9RnENdf^Z(#kw zD)+*8^5clOz?f7XCW8BR&P6n{;5jeYBvNQtqrq|>NzYwdT!fQ40Y#?y?;&@c&^C(m*Cyzh zGX5TVY3B5O2elLmK2da0=5`XzMi1MIe$dNobmHHc1@>p?iNf62eqTSL_N;6ZIT8gI z9859;enDrT-h@vmdywRtkNpZMwe|(BMkm~`5#O}0q?aF_E(s|Kg=}50$inKZ>vM#k zPsI@!C=F|B0Td-sA*GnQ#9wI^Z-cJ&30?+1h&lcaCRUto>fK!A61#_5DTaI4wcgFx z7gAy-q>lG7yeNy~T#Mt>T_%9M&mIS;Sk_v_SrLWRp;>0+M;srPHP>JAICbB!tPr_A zZA-uxMxJyrYb^Yo&yIAFl+!4w@n{wKws;o!k%*+ye0C?C9yZ@WDg1cg9~JRMLK?UU z*{&1%FSqOd|M3FXf9C~#B;S8|LCHYh1PmSdZ3I%>lWc7@+0n(xm$eZ&iY`qPUR$2d zx)4G0L3PQf4{}>z{@!IPl264@k}_+dtjs=@?<}3KZ~Gq7VWG0!dUI7D&MH3HocjCl zPJC%Q_#IMeF`KJ@`_zIV0t-ck*%G6vFq0|WLSsO)$P~?{aG5l!s&KDpkx9dr`Y$O! zAOQjq)c*@9Fw-{aVZG3}>mUKlTVbsPq}@7UnJBbjblil8rV;m)z+%K%Luzh2@OYgC zAb8@m!CgLvh!C`wYCeXrEHu19PGXj_2Az+sLsSG!L$pO7QL)33N?_d~?0^(X^C`wG z4#Y4dFcc5VPSKeS!V5J-a-seQ7Z6R9!C0Z0SiPa3bl3(eU)~`{L=z>jQ0O{#?^jSf ztSgl-%1}P6E48oikTBw&0+=n-2fH^4#1{Gpx!IhonT6rX8sX6y4NA}ihFikAvPF!6 zJqT3?#jQFK-WgD^MdZN@Lx-?x9|KkK_{?b;l4Y`!{)LBuBpF%}Nw8L^>68U@pblOv z$hH}P`h)tZhvgE?uFpcd$K`8)x5_~oYv3CPGHmX|An7+R z1_)p<>pM~XvRT@O38wOm1Qj-KAd9Dr@jd0T_=6Cd!O6i0z=^h_>rVo-C~)trDB`aG zJZK0;gn|wA;|5KwIFWdteS>Cgg5Uq!YU#0#I znB&rPkR(K}@hhyfKnlDD6=2Y&l{!!_Gy(Da*azCR zQU-y#PSD$9A5oebEo#o2d+)$|T>>W8c?PB*MUB1vCCzta-P6%&}wru&%HTUvWFR3!Sxf2Lx1m$&63K4IN;#Rrjf zA(JAJ!j4vfKg*HZbeoT}p^tj~3E5iTm_3$-ZFW8R=1Z`}7fIn5aJ)sxvT`+t=q*08 zJCmoA$hVq|qhM!gJ7q1%Tx0K*Lj(BUI&W)-wWKZ~LUV@cB`BoYguxK$ZN z5=`vts1I(gyl6Cb&h<)m<;K%Emfp_DGrp6=R-~!(Fcz(l$0lNowc3F$^4^`d0USU& zl7jt;-zOP^ZtZ<4_DHSpxeEx4MeylOZi*6Jx08$246Il2U!mZ6lD-vV+yUKNd0v{L z1_-~54JG0+>Rwiao(o%_QJR%=;{g^b(b_`|QLlHL0*_c7Z5M9uO7x z^0A)pZW~XQ>P4=WvEnm>a@4AO;vOgm&`3!0W9lA*V83W$nQy0>o5O;Ow=cU`rE?<5 zrIhZ<*IgP#;mA#B#7W};U~|1@$Ko(kBbq_r&g>qSe2z{^>N7E7Nu33_?o5%dM)DcG z*sG@>F92Be5b52fB|cBQ$bG*GCz5NbF2mC38F7$~uSh{p?4aE^NAhjCV!e8&Yb!^( zt7N&Tjt~^FXSV1+`|+DbTUp`Nq$-$vbX9A(_~Jp-nLDltF({e-Z#%_c!QB|LD`swd1*p@d2ix`5%P>WEFTaSTYKrR1hiSehTV2NUv1lVap?OYN;t=1hcyM5 zJj5Pmtzc9a5v4??Wj93GG_ng;j>0iH_0VfjFsx%ooky77OjvFg`IOLsT<&2|3bpOR zB+>vWkzmE)bL+PP!3+ALfbmtS~7JCzVm|3!v}p)fkIExwLB~U zw4H5i68G0GbG?VojFi5cuzx%S0v6pQM`$FQ(5444+mr6Z`_#%f?d~fdoI(Td2 zL=fE?q-cH&oP9PtO;?mn&0O*gyFbs<0(Oooxih7-+KpU_^axmbDQeCxw+myMXJTiB zhV4c;{|>|7W-O8^T;wl!zM3sXpUQu7Y1N78hX!U0d&FUb5f#?I5kZ?q2z*2H39}M{ zo~@sdU=o97TOTp&#y;s`3qhhk6o6J*(5y9#S#16V3ou4|9ElMwz7$D&R8X65)D;6xBR`E80%SgfGLSZS+rFv$IPx0IxdNfYoRbR2c^WNVfGt5q z149whx39E6Lhl+EqEc4boLTef6$ZMset8*OY8w1VS?eHp##_on{SsU3O8I4SVPa3~ zKC`D4)uZ6EyXPB4pm8B{zbN4L0SQEC+t=`y=lc6{W9#ycJyByWk=?<3uI2AL^S*XPq{&F3eH*Qb6BR8t0pOA<2hvG4ycyp=8AFVwc{MRL=~e1@BwP1@P8fTw_GaB z=SE5xb%_erj#!ivF@m^%sj$tOtUx);g}(dd9ugAL#L+5Gag)C?*_|t&D6G z8H{$8SEh(WEhwiqEQ)}q&R5XuPEFD1&aEh>c9xF`LsC)fPN_H}JKGDm*=;@TeWY>v zBag>6d#d&~eeUf>@U$S{y6aKY_k2^1>;qJyfCa86EvFh5X$pWn8ZD($66-_C24!@9 zN~a>0o|1(+Ru7GPJdL-KMIudo`e+i(Z$*njn)=kyhm_7wSp2#nxahhes2ZUFxS0rf zxRt>gBoisHL1-%M4W(}b+Ds9a$UHorFZ2+KvpV!m$0yowC!qBxyeEZy*ffZacqUAP z7#(HXOpK&)ND}cQEYbmN6rv`qeJJYCKQcrPDFIWY5F`W3PS+U$qJ?Fr^(7pdMl$*2 z#yrG~WFiCh58X!ElW@xep@#}1T~l@DLO_NdaZehI7utcN z#6ll+LSds7v(Q{?!kHj+)UF7O%thQYLP7y~aqVFIP2PuwfPoWOBUBALFadN2YfAA{ z!cslz6e`#Z#B8zREHs0D)x|^ER|qx5d{xEseyU)}7;LcCKoO_&)x~=#QN~k>?m&fj z1_TkN*mN+@(4$oovKV8mtgj?q%|3mz+i;=veyFY0BI-c$7|~M=%g)a-fKs1{d5R25 zpdMb((^nRQ{lsRwFw?064-Y%)A-d36FhMBCv;|7sWC?ZLtQwPiQer5B9nku|~xT07TD2_!|ri%rAklo7t<2z+6!>a-w=Tjxi0fe|{ zKvB&Cu)#?(DI)TiWk_6Le0>r;To|VWv!_PtkBOs}Fed^f`vAIQ@E_5Un38&2GjAHI zY@9_PZ%`V4{WUH}gS*!c=;OwN1coNe$AtY44itT2rXpFYk(Sg9^^)#PMbbEr3DQC& ziN$vAs^KTp0z_li&JMMv3OHw^+LMZ+>)XUA)pDz#B7a1}HWunSy7c{T2Cd?xY8C@w z7eCUq(Q77gs(zx7YbzC9eS`b9HnS+Zl9hvsV5hs{Jzk1LTQOlHIDsKon$5%L^%$Xr z6Jb53?@v)ld3I~WiN=_(oo-irW}|P&y)Um`4&ucrJ-Y}~Xf+(^gHev}OVi-paAQ1n z%A>d<(MeH+TDO1GW;!$AO%_RtM#s0Rcd|Old$VTYWY5iwP3nBD-+$^s`M$XQC$2XW zo&_Y-TA-th;d(!nl2z^yYs%$ZZXoiWXTY*r!#Bq)O6||Z$x7ZEIIg0oQX1K8{eT<^ zh=J&l63%&HjGO$}L)^Lh2inD!u||55q1j#XM8+XE31PL0guU6=M~5DI)7tcm{;FTY zY@eAp>(U-*4mHGJYFf^{t+6my4L?730adSO$YizzCF}XYBHATdZg_hKIQ?#yrwGS| z8h@1p0^_15?+#_XRK%;(sH?1ncwdW`gmxPxb-cDFo1+DMN7D&GX+1?-YShg~Hch+QG&(}cp?dJoL>*v{I1)+LyJ}Hd zS|QW-wY|sv%uE?b+wE@$&lB zNA-jYh9VgEcs|__XO02|cx{o8g-h$1R@k)NpOoMO30i`cL;Arutr!xihSoJqwEN#e zn!p_*b&K67E=Ab0ua_5WSQD1^v?NMMK-5v}3GcBsJJ4a?yQ3o=j%F(Qd}Bl37&*yY zW(VWDndA2;G5JrtWmZ)7&^qJ%ay2DS+w1UQ1Pf+d31TxPySFse}E0s8%m$w(rJ86NvxZ3GghoWj-yC3E9F)1IN`Y|{K1AW|KskyPTzaG zZl3f5oW5aCvrGPfGwB>f_`hJoK!)_YigJlGhCW?B-2d=t1)Jdi0X9Vc8`w-lX+glI zW~2&f)odC9HgZw%|ANgp8g=qt2j>(9V&vsd4_R`qz5a(If@a+EY;%hPW<^qDYTIr# z5Gt5lxjzx%*w>Znj?bz6d#u6AwHUT3-7>a9-SrV*d7H)A_uJQFw2p>DyIMH#DN7i0 zGR`ch3tN$^oNia6p+{*xG5^*sGe`ZlF%d4|Au;Juf%of_gC<0kBmgydGgJjdOoD-{ z*!L&XZ&%QT=a1$@vy(mU=1giE$ywgd4fzVV+}Oc-7Xt|HnCbFQbYC2|QO!kS#E+sI z2w*o*#KaE{brx-vpktBu0vA&wA_|OQ1r!;nV9sO6(P7SGvDL?Z*%Lh7W{mzyA037> z*z8`NoZdIX8>CgiFxPbEO*qhu8_K73@&^S6^E3VwuuHn_H((jsf4A!W0 zdD#c60pAu=`Q2nU|JDg$^e;@2t9o6jE!4peyk?PHjh}b{MLF+ds8HKwi!MA{X^Spg zTdix}Lq^Y0!XHbpf?1)z218Z2+X+R~edH>UN!^W{c@sRLYL#E&qMz7CO(g6!kSg62 zL>DFT63(*)ejsT>6B}3Wxv>R8{yxJDq7pJR3R~<+dh@nu!8{Yr`}f|%0*AwVs#m%K zmg*O1)kh*I{K%m1XoA5C?0?K;@wWu4Q402V`tlAV_ua>LjY+(|CZV2lDbsVe^Vz1> zY$jBJ_Mf1Azk}HMv@`Wk5_}ot@_vq(BsfGRs%38-xP#r97en3is=b-zj3?RM5GIUK zPk%iQOErdj@}(L6Kta!o(kJm^4Gi-%<31g9DCj9TeBS>=2cMsZ!W0;aaxyPUG+4i^93_yQ7!>|1!GDhzuB4d!KoeGza8xTev!0v=;mWQw|b zL;MMO|0r8#Zp#gu#NKG~MR-ImfVgAGVV_$Imz^+5H$jA{Ht*omTr@#oR`e)Y0@2qf z!gF2{-nl*O`eWcWh*=9IzQ&K_+3$B|SJlI<(RE07N-6p&dcM&f;8*It_t77amJmi_ zl}DomGrypk)YYJR@ZA@RY*oqF3#6newb1DYfh^TQr*%J$eJkC9a2W>1b=qSFeRr(d z`je<3cbJpBuqjIIWjA;twdDS8^gH#aNtZPp6Bqt7A;U{L75ntvaDyuw6=zYTX*6>| zB#oBYgx%E7?6c?fH%XB~EZ&SY+bOHgoSzNlJFI@T6Lhnd8FGC-{Hf5LnS32J7)058 z*A_eOzJcTeg73z@yJd^pa*qRb?~D(i)3DYXXUKgbV-k;&C|)^i^mqGiC4c_;M#F-S z=AnCbHSDz5i9|PkBn>%;-{9BaI-1f;YXFagtyV#Di<>dwrTr8)kA4nsh0&6mRg;6^HrqYDx0YCBgaTj(3eAYSCpX#tiA&L(H$b?%Xi}dQHAACRjzBpi-C&VdOl|d z$=p$e@S1hm`A+ryr5?Z$n}(U37VFtfR8KxGJ^QQ{!V@Q}`X!F2Qa{hlKbx~w*CaEx z0h6SPliZCq=Y%vr|6UWl*if&*A#JD6RdCko)N|Uo@gzPHh#TTM zB?O2)z*CG`e-4d4|Bu+1HyCE&90E{xkPe3b+t?V-{|i77#vy&=bh3;!vm&OR)JlRT zGqWK6h9qqzrli&`F(S`p#x?^+_R|rZ^*n^njLPgkk6%3m77e#1hGm z-5UX_gJ_t}SddF-0OGY*2rd;YzrY|oJH2nj2SpI@gQ9uEhlk&SW@2%IX3(hE6n{uM z_n>1b&LzHPeVB{j7()D%LZRXo0tuB-hPsE2z^-E%$$_~-iIGeczz(77AA2)EbfN2* zy#*jNSXUZf?4eoM8wy|gp&rCN6$n@b{Oh1XAF%^NKvJ;ebWeywa!6vb5Icn#DvWqd z^CU3zrJUkZHO|S9bGbTt165}^4&LwsEICUAE!dmz8Xu34ES&ti8|pi2>>mgM;Uo1+ zf^|Y0CM}X79a{-388OKEEYtw>;FK|B-wePI1D!wW{zUm5X)70;0Q5U2}rJ%akR5vEfmNbi&=7$WQ_?5`SlVo#YYIh^u~l4{%1 z(JVqBxn}T)nspP|J6$XqSXkewBt!_Q8?hV7AEW@gpEPFU3&87x=p3hrf$s+v)oXH7 zZ(wp*bK1s93#cIKSmzRXyfBD13TIsE)3zjsBa4u;!o>I1j~jNiZX$nYj>UO3#XtW&_n^`|IUUXu)Q9s^% zQVH+^^023Lu?d1xJT4GFbdM}h>7w$(vJN~RI3$P^gLD8Dw^bZ$}~@PPD{@Y!&2=sBLiRo z=l=Gxtiz$Xn%CWoYjc>iRj}@PYx89(Yv)x*Uz;}6RZsl9;_~lIN3dFKJT1wv_y_Ra zUyTJX39c+4h6p#q4PuCDvgv!5r9aKISW_nQ50sIi#8=11Dw2;BG=;^s#s)_;ZEe$Kr>Dak#np~o6K-YERWTr97?4Q;cwHdo{dnCL~qOFRIUR)v6 z(X0xhB}1yIB7r2(Ec6SBD+D&H#$`(ty9fIUw;3zHd}aXkR?P0C$EkZi?xZE&EnR09 zbaRtOp?PKE-|G+FcL`U;B~%y3PnK~dtU41@-O4}1KxfQJ%j0qkuw&^xBoYZnvloTl z8Q2IA$dBsa_vM`squc=$q^p7r*UP``vO$jtB)}+sTzBw-6M)CIoGgN5(7KJk^C(Fy zu9Cs9>uO6<_WgwgG$|~)$0;%!H<`IMFh?BxF2cIm6R(g?CgU0Sk}Qa+#+>Y{C}GdR zDb&wh`~ct2c)aGx#a@rN5-{z>A@@UMKi>)CZhnY1x8U|bI z5t19l#a1wu45h=XOC+O2LpE`^dXhw6W|tN93;{a1Qu`DM;fl!?%E!+b`c#BvaG z)R~2s>yPLu((G*RJTQ)TE)I|N^!d|C$MnDEs0kCd@_(A6c8a(fO=83V0Zg=qD)S|D z`V>VTMK$cFHEfb0yZCbF86A>7bKLCTn1MGd^a<9|I*qI7O!uBxYl&su4{N&8np(|6 zj4-!kOaf**A;`%zl%Kg3_Zzl9RB0kC&Fv34&2vnHwIQ z*85l^7`)7N<@!ClH^8k;6T_}*6Sr&^y3$s0SM9Lkx-dnBrRux6Gfx|1>>+sqi6nDH zA5b@`(C;i+zMA_`W-#RpUe8cnLwGC7z>T(YmZXcq`DEy%v0-+-*vx@(ej{>A#AG!p z+FC=%{aOCv7h0PyuB#`<_Fnf~N4@Au-0rDFfDPu>5|Yr6MYA$}>#E~~<_nbN`$|Uf zRR|WTmgosa!=Dk`_d#?YlDINC|2P~*^3mBFEZ$mPIFUwzT6I`-0HQarC|K{q;IAYN zZ=&N)4R69@^U_PcDaW>E6ubrT$FCoceLF~C-=P6?#b*o|k}MLf_U*z+gmWC37O+;V z9-h5qzDuAw4xGAcg`da34`9Z~?yypq5H;=1SPpV>V@SOx-{>0pux z=flqd`x)$8qkmU60_c(Or_!Pj&4GN`QM zzRr++k{xoAjZJLl));sTco|yCBm`G)!F=Rm0mk9z9{Ygq3DAYoH|$$IAR9A~lHG#X z%S2>8Nm_uC6@Ww6JL2NF;NkOg8kFi{N8w4$^=Z6SFF*B`8>feyY`=v12JAZ@>GvLu zEYHuw5B-8tc)#rMF9J+ksyY+)myC@lBb99&_nB6D>@9c3A)7I&o}u(ymuYqBp)a2p zFC=$@we}%fhj4z%GDNof!{$*i_DDu9DjMw)t*>a!4~k!n{Nmq|f*Rf-O;y&3@NF17 z$0F$+4t?x>Qgh%HeLVbo2_$_(U1dlM%5Kw-I}Mt|cBbX*b#YjzqR6lc_mBh{VISQf|`yNr;%?7oMF{RJwC@Zrj^#^A@ywi~Bd65-6o4U^cQ zXin}fbj=3LJ*JEw1eT)fm!!NxwPuuj;|HV~RwkY=$D1(svI6Fxj8$-yBPHU{8uI=58h=& zNGf2V!Zl8V8oXrU7D>YjoMa=e_}dOSSy~`-{7ejYcvRlC%5&7NXSYinDE`Q_mHV^m z_ui}DRLk>~MSo6J7{6?$bzb1nZsQx=pgYoa0@+gt+_g;%OIgqWH0{VaD76=pH*|!R zcXRq~8Ti2tv90q?2zPWDWEmGGqx?y5h&|sUg5JQoyhhb{4y*7^Y^{27IX%N`ABm`7 za*T*|nBA0jmBUq?16Z2QqvE>9>YMqbeJO7=Qpa@{S2Y_}-zQvT?o}^o;h%A)6y~%0 ze;w1kvXAJN?oR7JJH~V?^y4i}oy%I1zNFVmnOEQiUY{Fkqx7QiAdcf){pS0ipO_ie zOWi(ehGV0SWV$i6{;`x1IQTivNdt-Uwyd`trdp$;46E_#7JG3+C9Csxq-D^OSlsf; zy;<4RR?Vb^dA+JV87wu+ziebLesx1+|nW2@MabpaqnYxjLVN>hk(`@3Isp8-X zK>HHauOU%Rf-zraU%uF@scAAz2O)EfQM|5qh0)L>dyRm|zdykU%=wtGKFF_3v6Za( z)pky5XKI%6Gx2H&dILTq3A)6dvl6)lip?5X?%YKJIprnu5!$Xby1y*7m>Ykpa6U}! z?jNG<*a5zM6ey*N2n5UjpPM|Q|F|3Uul~+kwjHLA?^{OX(7iQBZEelmQ2&|^5&@b| z?;+d49`UU`%zIi8-i~M}mL~on!}t^(=lwgE?kBX6FKb{pepP(3IkG8IrnkzJ#RW7O zjBD!z$t>eGI>b{Wl^oR2)W8>6(3)8Ix^Q};Gw@s5#Q|O*nX)T!<`|+wa>0cF`g?1Y z^b}9vEMEQXkwO56&Wp8zBcq+oP>c`WM zFQV5BAB^~jaQY+{Q_HHmsx)Oko<53wKN;PG?b8o)4bx)FvfW`rL=lLF?voEQMjd0r zVuxeXLh9|zeFD;9LI`q&eHF>uX+bAW#}LG$8J>wQr^oJNC!V6T#|M6XMeqMng^u1I z=obhO-(aE($W9o+YzgL+*kF$7FYr^B!f*>o@MBJrbX~Tdiq2`ryuma_FP%9Qc%mH_~o`J^kN!``a!fI$g=}g}~r3&$wQB+ps z-`9<~cFJf^Yat95Rxju5Q@!ji+x@dNnMs-*L3?er1wQRqAcxHEw(es6fgq?mr5W3h zX*Ak21lDX=$zN1+k*Ayfr{;pAY2mwnO57`U;9 z+UDN*JxITebtGfE?qbZTU^r>!$KzIPC;0+kwmO%5n;xCW70!Fa*O1k5ou6su8s20H z;NVdv@G<4H4J)%(-e*3lkeC&T{`O?PegaKT7s0MoT)qCwIf{vJ^Xaqbk~rh5)SaH5 zCgB0YU{kE=$&1{Qm_RV)HI(ZCMv@)%Kx824{Q#s4!$}YO#@F8DP$mJ6Br%&Ep)bCM zYTs|ZmeT1l8ASvBU;w4DXLiTMEbG|Ir;8$P-vWF$r;PCQJd6czsFYVlITFRtl417w zE3fGV`d@zb`|duIAQZCP2!1)>K+Ua#Tvp^55<(n$Uua9ZcS^4}?1|8MJB^`7n%XnF zgEh!TQvV&2|3MOmXMZ6py#d|`)AE7(_sYN5VIko!grn!{|8|i716B~U|Er*J zKG-hvKte(aLh86cBDg@(xIo&tK;nu(62nVp?;Q+e2)!M=W@IcJ#7(?%y-jRv%*729 zWGKDuEC4J;Aa%9}z_KSUl(=goL?EknUgz=-l$s|pltdu?DhBF=-ZBO{oV-5@sB)kq z;H*|l6ZtYD-~i|%g3@3b5yL7i1d@GWN?iH`G^OUDh3v#zdhM&|7%3R&elgG|mLg~( zkTQ@e-@^TH#2DynK7%S3)K^0)4Z?Czo%;5_R>g7!US&Atk)PL&d$59Azzk6Vi2w=7d{z9>Ict|J%^U8*dNoy-d>Yh!wGV4huRsb#uJM!`~3o6Sq4teJJ5c42^ z!*VQ+XyV2=x6}k(r3*>mmROz&x&De*yrvLLRwr91b_7$NzzN(H2~pqkQC*vf*`pvZ ziT$a!-Ry`>&{To(xlD0oXUQ=ZFMx_6qfDuX&P#{9JPo(VsvvP;&VvEB$l)u#U&E=I zdRGN>BU;yfiAU(3wLSCj&Uev~iD0)}vu=b_lyH;=1+A8ajy0BpXPvQH#TSTqdWXVi zouu!=kZ_yulLjYc*I)Zr$Qx`DG7M%Ltg<9gTMJ<_F?;2LPFK=!HJRsSTqvvV!B{Ua z!xS1OGU6MLsile5cH3xTb2lNDsIrzHN2*zDD#H|Zm3b$rr577n7j+O>oLKc0U7V5^ zbY{JCc6laU(Sh&x-kHQoM0s4pMFyHGuKgr>Dz{ijvwEN?3CUd z7>Z$Z7e{1eJHNUPbccqFXXg%ZIYE*#PV8Gt;2rLslyR*5inNmpE+8x84eF-oP4cX= z?u5@WBZbe3xA_dJZ4~aVfFQ0=r?WuiX*}(|Ed}As59wP{8AzrJiDnVd4QO}HeU0Cg zogfOZ)c5bvIi;CZJ^``HgF(p;|Fa1A4@XDQ*#5tdPK>lHNC^SAtz+lF2=xKsZ~`HLT&74rw2*amh*Ch=CBlgRz_neOJQ@k-~06 zInA7i11I*x-WZvTD4yjZ_VC}gMg#&J z`6901oo~NXP)_OO`BM{|$1v4V>Ck2XD97pw!)O&9yL3p!aX@3H>=0_GY7tF)lPb^5;+um2!LLBGKNcjcD(SMmP; zQMm;`m0RXVA(YDCNDpd;u0}$}SyF2J*vOBVM75-n?D&-As_X0oh=s|KAEEl74PFbR z5jqh_Bslh*-Uv8{XPlmq9<29kXN3Ub&!2qA`Zw&q>4sbqw=;6SyqTKN)^~rrwt>wIXkgxhO@B#( zQ2FBIm&T{9r<{X-qa_GHWGaxG(1R<48*Q&lm);Vo- z*-mI;lA3Nx#8At!{FPAdHO{oPwNgbpov@u8+;m29ypZI#Mnd&2Y0f_N<12rDzW-&( z2&sU_a0B%+j1IB_XKnn-Llg@Bc9YhF&ki<4H`du#Siw3Kc583+`r`~pT|>1FPPhBo zPSryjHh~#8-SA(5Qo~r!MHcG%hXpXofm%l8SfD|nqu)#PPrd|yNLk;|x<(kySLQA; zGJBdgaC7aiKi0c1Ln)gIFx+@dt6#gF`(9((k$%bB%Bo@d-&sTnb)tuA;DxU3=*!%> zfeqx3I*#gqVHVL3#;Wrqt^l)Jgzmsz?oV>`k)jQAu)#yGM|{Eolebx6c_O>uNjE37 zaaZ_QuW}|=VO2v$T|RzVZ%`y*zmVgzr>})}+hK&wud<;M-{`qIA^$%5D(63aT0oC| z_Wy+-_LqbHPo~JH3>0{q}Z`$~lP71=CBiUjrtb>OE`XmVg) z&SF=pyCCkw1Z;gx>*7+O%9%oAm2sY=*MpnNa$ZYp&yX~zk>FFz~c2X z^mzFbe#pf9U=yWfL({LCLadjxlAq;T)CvLih>LzCcw58>`=S% zcAVKtcT7R+U2iZx0U)eF*v>M{Ri7*BMkE%>Mu03?OLEp#zrDXL#WUlT(#>~lMLXUq z{FbDhKQOpGlQ6ggl`yz{I54;*ZeNN~xg^ecfLPE6>%+I;5VAY-Fj)h~C|p6b6mFQL zC|m*Ss9ch~(|Z1VQ{D)=#Lq0ft|*_q_>sW2<%BSJszcaYWRPGFnB-~U0q|^PAtYY1 zNU-+l1ZlUR`QF}Slv)33N`XP@FtsqnFg0K{nE`tj)s7PQ(C0;4;6z9d6|Jpjs)ThnB&yD9Czv_It5nhfOhY}UhaJJ&u} z5*7$QIz>D`p1nV>N!^-pSqMFf*+-%bziNn`qhmy4nFxhp&J&nBicKZ$MNxYgDUotd zZmjo;@$nWwi9Ec;)y5bPJd_;4p1N-jPnLL9J6yX8HG+FEiBJF|ygiT_Vnedc%GuU% zx${KfdEJE#yD@N=3B<28*}ISsNcRdF3`aubQ8e!A<8n1LexgL5X|N@ek}nMfBEg)X zBoDrrjn0h2t(foH_|-M>O9XSU+r89OP-~6>RBHz|pcRI1U5{cUIgjb@_|xdJJ@q?J z^|w~gtYw6#JUQG=i`=Zgf7bl|opHc`sG_dW8e^%|{d~!xBV)9E^(zQ=!9oa1P18ip z;`St^-Lke8Em|2%&b4y4LH^wsl9-*IZ<69le))SrNxTufJZUt8BukWBxcd9CSLVVG z`0EMT-S}$)9|aSv(d6so@5Osw{wMlLDVY2=OGSP4VW%?MK?^r;h3zv9T7%j%>MWVC z9GY5~Ri+CC@f9f~ohBYI3Z$l4qjPO~Sx+B}Wt-Y=xu@UZk}*<;J!i=iZ4KH!94hfW z4ThLc`W#aRbd8!&K-hzT-MUkAir*(8QlcL(L@SAdw9nm^EJwg}wVX$;znm>XMQSC{@q^(LXFC6$@$p8XD2r?KrXUfFW;(V#L^ zD4ur8q*UnaF}ocSdH6xk@mo-XiiL`|%HM-(7z#2O3tnXh*aBE4 z{&=LA>Rm^x?__#)wD@;(snel3^N-AgR^<*x~s_aGeih z4(tRb1Htbmp_O7$*BJaeitImfO5{-xJQ<<;6ayGvqlXdKjfQVViX?*}m0or!iNO*9 zv^7VtY@(Aw3)SQIGMFl*5MZNp&=WI5!-ByOCK~yekBA6mwf3i_y5VpavS#?=PsQ)* zKL<>RsxV?x_!76Vc{}!;C=jhE)p<5f?B}?pg3L2K=mC{X$d|S0+*}x{ zF7tDK1Y2JG zf<(Eb5g`-8s8<-V7d_KF(uQ5_n(rWK=AR@o5Dasy?e*by;rYY&L=Y?Sg*h{*!LQXPIxUw>lB1Mm($ zDBL-|SVm!=M(?AP$?Cn+pNmqzhcvGRlCT2`FW5}B2}J#L#i*K%v`bJcTc%7{Mhe9= z_V(Ny!wx2UNUe41nYt3B92=`M_((+|xAfz`ubE8QN#~^J)AVs-oN)ld7}=%l91oWF z7+Kt7f0YMrwXTuQjM!%nwWDLvw4~Cz;#M`4MzS;!hwfbqk`)KvcX|kPd zw)7S?t>D|Wt+~Qy4-^6Hj;4mCxqN>f?)x>olgnEkw#e>0M66z7Kj3z7N?PxKL31Es z-7!mAn=jePyG6G-sL<|Bz4`-~emps5leTYO2)uv+c`b1mRs1I8Z?>CIGpU696q5CLkzQV4&p6xgA z^eb?(oz+)-E@uZ%+R*^EW#!Wh54U`?I-q(MZHs1Eu~_v&^1p26*0Rv{L#)-*oUZ&Z zLvX+neMj~gVQ)$)rB#o_8&TO{@-wMIProsgxz(U)qjtkW4uxeuGw2A-<%xE@WcMO! zcdyW5{!BalPG@TSefm&_8Bw$omRI2HBo+UoJ}N7*{LFaRa__ZV5|8xsfP8Y|YxRPs zt&8H=p0nf@?>zgK)wv_K=%sZ^a;sZ5(FYD*^XNlC40@_&dyz@I*+4p@bDNAoJztU_ z^ZKFsn@;+M#c<=;q7UGWIHgC+rf+-h=_i_IyQ0N5fgL6zdRIqVXHcyPrDY@$9(5KM ztF^d3gHFIxAuG`->HHk=^%zt2B28Y|Y7 z)C)@GMJg5+iA5@w7Li3-X&U9FjLeNPiq>fw)#S}ZYs`zer83f)%HbqjpXqPkO}J7F2B0SFnYKC=GLz9@o< zhH1MjTm&7jZ8E)t+FdLc4$}iW_xp<-PMZ2RfzQ^tLh*%>R@SKzxqE5xM_2BS6NG@M z*1H-TnsUl@SYY01!T9n%^Pe_WOqho%H#cBUDiR$TmD^eP_;~O+h4J=-for1(1_(N zWrSFQ&6z^EP19%P;~y$wJc9E+-$(~M^xMc<^(6U>lJ7Ni1SFHoBUYQ;7}OgeN4Zv|t;I0hxS`*Vpz^_|UCNR%v1 z@e2V(NaRQ2NsWv+Fi=ua<6{a9QYYl#2pVo;y|XL>r!^s%2|-n=T$eY7`i=s#u~y{x+r)%l)zc2@R8BTL4U6h zD5_YFiY#Y=XfE^YfeE)dQk=%ulehpD!nQ|TwB`qPuv+ZU5-V%XEN7k7w$GuePS=;v zLFBt42J(d8?ld9F?0)g9dyQzcc-1Ce^#|SUk-%ZfTY6rK4-nKzvM-w!88xM;7lur# zQ{1yTw|W}SUPVAMbd)m*%gkTi<-Ag;S?FqOWrYh!m3wZlj?DR2v z3_a?aEbtoZo=?aODTF*KYf_SffjZiJYx>KbO`W0<6m%aR4RV}Udh|oi41X_<*Hca4 z-TkOSpN^AtA%J~p$gl7##DjO4mKN=O(Bw#h{UvvKLK~oZkjKgbiqp{Ef6U6dQgE%m z`5UJ}!KOClL=4L_{0J;A9%LZ#{5`SQ%CJZokz{Mt2glIQh|Ur>isX?8mo`ce&8q-& zz@0ePYu^(w&@WQ2JGoObKgca-FO1JLnM*Qb8~Mxh1LY2s}MNGA8jd)ycc49${>2qmQclVAuAO z+Dsyt#z0f`ew2*-TBknSxn5z$1x!D_A>AMfj}~}5#T3)Vst{kZk;I7jQ6Iv#KXz+h zkvFrwOMCa!n2ZDS#Dh3|seCT@COl}0ic2P%Gf(wqq*^-myWX#|dfd7?rca=ML3(Q+ z`20Z1H$Sr9KRbPlpV^i^O3`QBJ7fXK#;$pkJ3l}K=-6(%(GE@<<(vHmPC)r=$Akg~ z83^;tOo$hc(;xIK+@2G+|eK5z1I55-~M2XLTl^bat)e06hEZi{gqn;f&7_GRqlHe=eH962_}b9pFaQ~s zP)44PJC|^n>(;B0D-Tvt3s`zk%4)v|uOCm88YT~-nm<0)E+UI% zh;u|X>4BR=GaK{5D>P>4%N=Lbv=on>E1uEV3uU)dIxSBSaGX7+@n9l0-0eghn`~MdcIhYV?xJLKOOL!)O#cEo>nPV>H%{!RInLKMf{-K!)%9 zXFXEy5BMs=QBW8Sm(hD^dSwxkZ@=e7RpA1K1x~Zxt$5LnYv5v%)z?T1tg6OTY9?q& z$_>D@B8e@;Rh>JfoH*~#Bb)1#zMn!iLw~IA7}h5!o=}>^7o?wm@@-PfC!^5o$MX%P z+f_^J$oMGRWw(37(1_b}SADlSX_wD&*|lbATr5&yAs0W6!nSXlawJm38@BgbqXFe^ zNV7OM;(!&mds`i#JSy`sEC;t6zx%{acWcDwE#aS5^=p2qZzpby-VyR!TO!XOFTEUO zhOoFE~+ff4qAs@XdqRqB%vEllY7vc8ym4 zdpM+3LW$;$jGsr75Y7OO!q+T(`{Uy?v+zolsbsX;Et_PDVddwPmWJ}em-6tKP6gl_ zPInBpChZ07S|lkfDs84fdIQFvTT1a;n$f*Vx9V@9od9RXEqu8ru?0puP5s-GlZ-x< zRPS=5YqV6#OpElwXP!V^0?%Jcv(e7KoSaw`2Q?{6ACIEq_eLENPQ95_6KF^bS6e$T zuQY3owpYZCA7-#gXEyD58^(}9(*%f7`z6<)m;?u(Q|N}60tUiTBJp$R1t+JisHFW8 zhYht<-gP?l#h7(+o3!vk>8N&jW|dLJC>cMByE`vigqaphsnvZg7dhwgF-7#M9*7rn z$C_QP#bDP%2de$^?CFX-^>2PUr@XVPoZ{_}a9h zlcWQTR~uTFX3bG*9d~7p69;8XTG=oidr~=- zd3)|+gv!IUzF*@3XaYs;>6qAfMX9D{4oj&nCV6dDWMg^TNM6CVia+@=wDHR0N!mE~ znqb~+NK7m}JlbAk7ZG{d0bUMMud&PT#q|AYLSUAr1zvK)HK(=4jqEk(9@qCB@vr}y z+4;^32O?he|6d5X|0S{in|K8ZZ@^kade5e|#Bq;bnKD1NLkME4izFCVz4#`UF}@&^ zgODqeTa%>Hv^m$#2TOf9VL@zV zPDBYr^L>>~d)w}2tiM0%MCJOm0J^!qDXlNRRh6b*S^!8>FDeBTshC@27Rk&nct|r_ zS_BkvrCH2^l=m8wb6ZhI+FA*DHb`n8E-MXVFYLcK)_lVU(toG8G5KD0!vzeWVMEdn zX_JfWrbVgCveI`HLSoWX@Wbo$=q>PxmOjZFgD1c5=ZB+TKHqLd=vJ;noI);s5 zi{!BGjVVs_-Z2MmzbLwm>2qGxuzhwE>GOH~GZ#n&zhd#u-=gpoQ|`#NDZrt&WbG!m zIebgqQEV>88=F>u;O8iK!~Pt*JVqYq`PEUW^fx8195~a>n&3EPr9ED%bE*P<4K; zs5-lq)}{vOw8Vi~}hg@8Y6_m$<6*b8gkr9&Fp$KxEt5p}F_P zsmVqTD7qo2YHA?axe;}HS(&%1u>A7bugYbQU;DC;)u|Dydw%g#SLKcaRDbZMpBoXb zV#;+C&=8rtxqIH!#np-d<){@o)*MoGs3)gp=akmQm&^coaiIA50$>J>F( z#o+0hTB{%LoK{c{UYp}S0Edts98xP_R4`dUk4Z17BRd@Lv`MYKoy>%gK8hj<2c)GR z&$*0*NLeD89hyiZ4tVRME;5x%YxF)+KV&+JN;NuI zT4SDXFrYRX(i?QhqF=cilWSwYrYK}Rr{1e@swMh&(PLF(NB(I?y z>jr6VjA=Pp7kt=sdEK%MjOi1)|h0SQC3iky?&zcf-cBZ=2X2-mfl#=%E-P2>sUT3lf_V<3JkJh(t z159qT6)UY{siRn;xeHgAp#9ej&MBiIP>Ia#jCS+pCDN68*)3>r?ED6 zi&TB;$R_Z)Hq(lBFr#FNz)7sNoPya!=u$#ebHj8Z2t`qkH1aty(b4US0S68}A(w1S z`a&VNBkw=@j@+oM0}aW8()hUSopd)mii?^-n%f#VHzw8TkH{N0PG;|{muK@V#s$pF zI{B*}gG_VkctCqSA>R-mRI8!LM9DPnx_bo491-s{=H_N@%=9w%ny@?5D!W5I%e|j( z{fJSxLmIujNuC&a53c7qd=jZp`y*{(D5#3YC9)0=-aI3 zBw|+bVMPwx_oCTB}QEt zgNE{;48jTzQGR#yjm_Z*=9&YO2nIPb`9FS-88jvj-cdH7SYE_won@ZnGyz-QdKpWfP)@wy6@MV&&r zy7thqK18_8ok^RnYio0?&%LIxAj%;6U8`=Gl9M0xxYJ1DiycF>i{jt5( zINM!Iy25|kK9XhmdCDn6wsZkv-u^sPdEV>|Gxp7lTH~??DV2_$Zn<#xm)p_d7al4h zR0=vM=fH>8ZKY3`iOe>Hf3EErQQrBsj|lFg+a@v3Ze%;AyBab?(qM-#)f_U&p9yp4 z>$HDzG=j6PO^CF?Yf^Fns_Uh)u(E61rArEU`R+mCYI|^@9RSwV1JtH;$m3V9dUbQJC<2V;u$2+v^`J|cr2R~a-MdX(%L49l;?>Bt(2k} zW$a|~ly;qb*712BaLwxr%q}omSxK?E@odFlaqa0hKPPxqk2oEE&)2!M4O9*?`>$(* zp+@f~fvp0l``nJ_Wy2a*Nvjpjj|!*#rO4!Jr4O}L|Kq9 zl1g(ohUhqSyi;*(`AiEzq6teYayrD)JCo>)Vh(+c*n#;f$)QnxZz{HxCsZ@q6-N9d zwxrX*+d8=MGcR&X()iYCbqX_59psi-P4@xx`rZ{NC8vQ!_g?fkt=)1&flpg0vMib1ceL zF^ek5HYtzgyfJ%A-I6-t=`O5_~zR${=Nozg6*CAmNpQ zZBHBe!$s{0T+<*gpVncY=!Sw75v$w%qJn373DmJJSYFLBZp>7-F0Q;ZvYuRWSzD7? zTC%cEcT zbzRn&pXZL3?0BZynRzENd=|2r$_gy%`_wqP?;qN~G9H{Vf7%Wtokp8r>;{Qre~ z`j=CFK6*E9*OZY-7D~9z)^xE_%P5v{s7ERXRWd(!N5#DgRtZw^X5`e`PP7`BYYG|~ za|&xfq&+^r|1!&kUH#C^rrkXhtWL#L;HY2zG0WetHyo$ApLl|Tv%8LcK2eu#2X$MH z<(G1Robm+%>BB?m#QD&3f zm&KF-eIOFB1`E}#97`G9D5PaqC<6Zq2phba;wQP85(#cYfY?g^eHygxNn_BGvfB!m zOotBl8Tcb$^qWCT2L4!#9^1&@Ft!2=1bVGryTDxu;CK3tlwAs7GM(1sjRG*4UaSAc z0jNsX5x1)hx6IH1+;xVlHgyB<$HLeexG@1X({)rrV(Q2RV0Oglmq2oeSkmECSN2M2 zsRU?rXbd|)r$(LMg5!wZ!h^KC3a}kx$mtc{ckve8r*PjM+q5Nqcbp@u#~erYZn^jd z0+@aFjObHJxudVGyhGboW@HEQ#YKSICldKcU$SQtVe#gbnR%)xS-VQgF+pB7M?aI7 zuIl_KB{Mi8ot!N-?(8DfMsE4)b3@h9j$>O|xb3+GZdXzH;B!$`(#>+=GY2>{RX-wz z&(sa9HuW&b1P9}tU9>Y1WR&%t0$DG0#{)7Q-$!2nq%Jp)&))u{8FsSCw~a zjnRN8@)I?`D}G|*QUvKWP9y@VaaGm#i@E!BkeKf{r-{yv93K3)m=|Le<#%z-(z`xH zpGpR~=F9@oKV&B0jp+|-kEQJ^Dwgm|jSZR|nM{PMly!=r&&^fgYhx~uLJrl0YLy?{ zw;D&boFzN*+PzKL1jx7WqA_~ydMHAJ@@eBHp(;?uST(UKs%LaIf^Z2XfD#yKRo%# z=~IqSC}uw@t{%YSdCkt&rN)Y1HI-IF)hzU?EF>WcLHX3yj^pZZaq;G=7%+!8q{&j! zr5E6&EU0g$aeB)MA3Y!1^jSpN7eZ)v=9cQBC8$?k$RKQttk#=Ai*HI0;cYFi-=l;Y^X&*{rtO+EPXQKc;T zwpu;V@f&>qa((YM#QaA6WDWfvId1}SECQ1Aq#!wO@+$}==O;aRIrHcw<8*jNs{uND zW^aF*bXS`D%jLw;f-L$REA(Nm{GQDi^gs@}Yzd2h?}Qg>*@-iUgOGmOqDQ{pmX0k2 zKMhpGN>o6;NZ^Uz#Wa~!)bN}LTarQv^Z>Hx<8URby+`<%*E$O(Oscr;cuaSFB{y4w z?KRz6z`5YNN}3NHM)9!J{?Pgs+7H8=$PIczlZHVX&C8`}Yo^$uVX0;fTT!xwxjFiV ztUdF(_=Bm538?o$LS*JJ>mi}pNsWvneTqK=F2D-H43gxR8djN3jgYz4?VZ)&y;!|$ zj6M8Zx496Z7FHQrFh3-RzAa>zrkQ$c@DDcq;eKX=0uctu3v~L(AJm^&Q_qn?gA_R# z7}HQ?BG(l)gUMT%IIMI7P%K~p68cRyu!!!lABt076#qKuh87zeMxCV*8u3v&S(7lX z6CDJsoT^l~^_iCuq`~QtUxa(D5b!wWGD0E{6d9OE|4MomQO)$`bbLdmI38ZyO&by^ zj#@)M!Z*;8V?pYV+E#Kxi>n-AX2a8j=E6qqhA!fjG+WzixaUb1Ug7}>O21CiyDdxI zg3hf4Gk=9K2XB$B!!Q7)Av>=*e zy`(PJ@Br6JB==K1;)lqz(=xX!=u=IzYK%)`z(?VcTFt=3kw`uD#wU;?4p)N@_VVx` z$~YV4j;S-u>s?2pn}E+0yln8CU#(M3pGso+05SH;yBXQGmeB58pIOnhAm?uxmsP8M zOF9?LtYHt}o#V2< z$>N+VU71lxG|A1r=QimRsu6?*amGGZfjgYZ;v9qn)?BS$^MOKB^7G(I#7p9cSMU!`N;HujkNGt-%ehYaQZH`MH3p^>XgO z!(J*yEBj!e+hJ8slzLo+jc62Pt5+u*?EK4Ce{d7qI4>ujH5Y~n&?Q*jK~|{HAgZEq z)Fi&r>Ja_f2N~KXK2ePWaIO`*Oe&ESf?|$y6(%?>lSLQ7-2;i;h_^*@D`g9Ox>Jd))^W zB$h!c@4C|o_P1&1?+hBuuQi9$G=Aic>pmXSI#pUH7<{6CMOe)*4trMaTwj7m<|aj_+!*W7Mlx3Q^zy6(+&&!i15g|2{vgDXB~;iU(N*_8iLwo3pAhUpV;B1XYORF5uc*?b?i4$a?JK0f(hf}F#1UFP;0Uo~8c84jM?D=RGj?4I= zsz}M|>q5{p2{;}ZY9AT;oau7kgE`Y|Wv`Q_GB0=))8)w3>@d>qe1wjYR5##h>|3%0 z-BZ#5gZe%lPHe`jten{>Y**W1+JtV0rbF1$pmgHjNgu|u9&n{*L^SWaaO$(p16oV9 zFD+Nrw->}d`!oTr$V|4wR|AMnbGw~MYxmgBJ+nH$Oq|i8SL-e-bFUoUDVaWKt9vI` zUK_kwXl@&yx2tupEfvo~{Kj3fO)z+gvZ9)mR;jY0Bu{F%zaElUBZ?fp9^A-);aqV7 zZ1q{>-;l|y2}CA%P%#ZJ+kwG)MUw8^GH3E-UK63ACbDc6VoyA{+A zFoA|+{;&POzX?YF*!o$f;~MUb;#S8Ane*}h(Ik1R7Z-#*O8VQ_olnXd8cTCyXeVJ& zF*E6C64wD>@V~xDXh@jn5P>{>GHdgH5}BK4((^M{waJ^9;!xvR-nnGcp1OTO5f-f1 zmmJZ&o*#mrW~gWydU=U!H2{`GwJLyfqS+szB;pyQ(9uLqVtMKS>bM(;JY|4&oLRC^ zFcAmwEODrf_%AU7OJXYEca=Vcudyu|1E@rO3<cBcNpxTXmUH)S1~`=-ewdx zuqzV#Zm^G^-IO;duOfcMyF7qmF)w95)1CORPp=eTyg~=2QRNZ3RRSTpO1@IQN+SWF zu|g#P3>y-Au3>U!DPY;k17IzxNU*#4VQOD=RfJHzbdeT`EOvs!)FP@viEqfDFSH@M zRRgT{M5KUNSt&a-be~juQsi(66DV#TgQ!Oho{Ktc2F6 zH1MKI>UGDTZ)eLPnuij}!v$B(Nc@Zxe*0p%rx&lGBpLY1L}~HIAhrdQ*`zzGKbRoa zDNO9|gpF`+7u(p^MIbw0X<(5E%p_63nRIa9=g7UfCiID~+$KGPZCpQk=IDe@Yf!Lg z@U-9=8@v@Q1RnMQzYYXLE$5|f%s3e2zqQu-%{o4&tLg1_oRh<6hYk}%Gvh9UaUw%M zh#?C5xw>NH~#9CQ520*z3hz(;N}PSE$D5(@UcQMnf67*ZO64JS7E(-$vA`i&B|f%st}l)eDl54GReKn&B_}d-_y|m##VY#g>sK+o4*QnAD)wZ?$X^ zHR08deR%=Lls-hZNSl~0!6ieBgSRLSG2_JRIa|_STf43IZ^J;CJt1UrXKpGE9?m-m zHxW&EE$+vebnjLJY8ZY}+NnNFTAGQKI2esaqv%*- z@~j!b@L{AoY3-j1%>R9XeJ6>Sb{k&FzM95NbvJ|vO#VPQfFjwKc$W0Cml zyk`Wt0rPDnROgT{CYwwObRhUGHf}~{H0M6D?@R(M%QCS%TYKbfxLo|vd4}UNd-Ay0*1nG4At4~xed)?{U~b>Cy752;uOPn!cnBlGsQU5 z5DrSs7Bt5;c9yU$oRHd1_qI8Hbqfdmq55@{4Y#uIyyLLVvl+63%*`%~nESd7Og(Vp z8zC4|L9Ogo(U-fevQ}I!DL?j8F{vUxO;`D(%(H3y`cby1p7;Q9bYgk)U~?E9M7z2; zlZ6+s<$13$i2?KdqtOBE&U3^JRE@|_r}WUFRTm7Jb&!tAd(Ge3a{Oo}(tk^x=YIj; zj~+3Q0@Th(RvofY2s620C2?ouN?KL?AOfk+54{JFbTP4`OFx*x8##@sbMd}aF3fXJ z1F|?%D*WavWJ7P=JU@3h<^*Q^TFCziTrE*%m+hZeGs@YBXu)VMna``T-`rWB!M5n9 z8ZMty|6)xeo~BM0RFU3=!C`ZshME!glAD51vE3w&np6i@QW|ZJZUHD!{A#KQ&2A8g z^MZ@`r6qBBWvoTn-W&N5vlSPNFJcoT;96m=h;#mm@F`%q8AT_sX8*gUUpCQ|MzXq| zJG^QXa_g+8KZQ5(+stb+jL29$fomRe~RdadQrVSf8I z5bh2H;e>9h7XjiQE5_umtg~AqfqAmb@@Q`3X0yg->NIJY%-F1{=CF zvW@Xn0Xo=67{eHpQ2W;G+8#vPa7=Gl`f!ZX@`DWruGJ6n z6&6^Dw&?YXoV5-Pb7RG(wQl+=Znrv4hhlbc7!h2^+MuvDO;2tc_Bh-9`sOPE3MhWO zrnK=oJ-nveP`lmyQ!U6A=PCXxKM0;Np~oIlU8o1ArIckJZR?qv=Z1)ypP;(?tO?I{*GTAcp({r1sza%<5u-SN^c`H+2|@%N`$V!)fmcElD5Q9IAgwWh7u)%` zTf5Kwz;!lwU%nUOiOuf>qd8W*ZTN3!Z^$eX3gGWY5^?Rrf#`9a24_PAl*@YwPOLlH zeL|&F5uHcX(YEGArvjSHC%BfsK*>g#EnEd5l%JU$+6ljoDrp}1VbO6tHxK4xu}W=%B5WE! z+g-a>!|PgfZd`Tnl>w8P+l`q%>N`oY0#(E2q%+T?*^AUDdDR)l{xX;C{in(W=#a{;iU4!m_H5K!I^6zcX3qeFf_x{bdb zT&-?l?T(XIb3%1>x?DX}Q#uE4<2&@Op}6mgtJHT+&vWt2>|OP;ZDX`O86a@*(fEP>3k;(9;V7wLJ=uyLIR05&V}!+`ZB;w%o6 z218EUEsF(l`eg@aYj#*iCGMhfz6M^?Hs;}~atRiOf?nOGA{uoX; z?#8I+JxTQfzpG(9KnR$gbsQiSv1?t~a|6&B9-$ptGv&85!uG-D+T?pG-)3dIe^(#@ zfB&=cu-OEGCjInI4WjwG%H#h!%k1*cG0vETHW&~gIKW3aR)>B6fG~ZNkXQE4%Q|VFBDHGcO!L|)Z48&e`Hv1wFL%}Pz~{X4KZafZ=ZcK~^B2y>hBk(b#`ZQgrp7Lo z_I8Yx|2EcPRB>@KHMAuoGqkg}Gjp{gBXh8Eb^hnqzvdfaRCGRPq0!z;>DS%bhs4kl zcrRhGpG=obuS!>akkBtlLSe%cy*y+vH-I!?tBFNR6myDT zXE5C(x8}waMGZ;Yy1zw0fnRMHNPG(Tfs7n{-lDp80R@OKs_+|%Yb|RPxN^%3>*YL! zBWL;PTibdVCY>M$Sm8y|7M*$IX!FC7Y1WVG4e*)mpRopT_K$v@4Bd!%N(f)+sOXD* z=zl?1dQDt8NfL$-vO#gFGbHH=7Jz=KmxMax@F8zzi>qM6?G!O%UBiSUb*<=k!($U` z%IagP)`dK9I*-D-JIGG?pHP24^fZQu@Bl4 zl&WLkLLQ5kIglSeAEe=Dzs_L-O9}-t+}B#wRubuA_(dvjXP0IViW8|RbHoW)(+d~5 zf5Fcdk`eOC?;X*$rl0ZilP#hhXWV@M1J-8WI&g)XF#>&~zwILw$dH$$IF>Y)2;nW{ zgfYt>0*RJK!LRom+>2P>$-4A=Bt>O!q^fKv?Q`N{4bq?j==S`V%N2}hvM$`Gxx>xh z&R4Vicd+{x)qeQAD?r&`j?H?2MzwCPseI&S{Wi z2_a596QYloGX~DUt!4aUaQedGx4(eES2jhCAJ#^bqQiY%5y50|)#S(`G@(?97!8s- zOFgBbBsXBsRyC~o##XR2d1qN00o+1K-Q^1eP}1`gLj^%AepH|r4d8x*FLLmd+Ob=S zkeFcC3p*&CHw4&1r9kp_gmr@3OG|}vS-T85M^oP*)kRp}u;w;(o%L?>IsU9^Xd@Rc zw8@be=z~ge9}XBXMN6tI&mM+bZuB?P3tuh;o?N6#zQ^KmJx(|_i%D42?Dx?M)$%(N z!Q_>)x1ZOe8&p>ns7)f=sClSp>OOMt!-_>jjrh|liovY$q7arT%ZhbPz4s)j_>DiQ zy0yOzvSQb(=aC46K8FrfnURzk>52?!_R}q329fR~E16gN-2goP;71Ijyr+7J)&KcR z%0c*|Dq{3W8WNwU_&*Z|_kVYSh7Jy$z`e{Ilz}ZjHx}+Ew#@OXbUY zmUQ5+QzYR_Wr`{E!i46tN~II>sb|IIzej|vm4tjzU-aK#cXCXo!_Zg2E@Uq{57&}L z+n)aH;|9R9;aBr$I&bl|o^Is^zp|?`83LPN0aBQzvTay{CivuI9_jt`h_DtOngi^J z?)68+-FU#(JQML^xR`uw{A*(Zn%d2f$#w}6j#apl{O8DRB_w#GT94MO#8RA?Jd#dlq23bO?SD%m(R`xy_4If1eFHq*$(uoSIl%%5n`@(6Y z%jON!ShONjwVqV|s#F6TM*GZ+hnN>XS7=Cd&{{;RelPGC9 zEN6-94+?EMZt8+F6VoP7xcs&cVDJib|AD$PZ_BTVXsy@+^6PIZ1_QtFbuyEPiT($L zx!*zf*+mbX^8&%MUJd}OQ@s1652UpTSB5`d{}FP=mae5|KaoTJw|8ImzeA3RrL&8r zov{lU^ZyP*pJ=l2{5P0#l5`Y4!8G`>PM=yMW*1pYLZz;Wk!r|10NW=@&gVG&O>)n+ z1#UF{*yX&1>`Cqk+tydJKbPxc2AHE;rXXTOcSj(QtJW*Mn;nsS z?SU!G5cYXyuQb%j(lhSTxwN&m@vxomK!{}LS;e8Ig)+#*&uO%`&+>4dtvctF`};}v zBneGt((VCwc1W1CPMK@2SOSV}lH#LqesStBDl8^OEak(kg^uQtUaGdqzTH(O3CmOk znmfPr?l(HA-R&C6A6wh9tXq*fM>{b99wN(Yp8ZP80>-TLSW}Q>3X$Za5H!)=KU%*} zH0ba0s8@Oo!WNS5e)GS>=2!?u@9^xx6P&?NJR)j}l(<%8Rrl@sNImj zV2X(x*%kx`r|^U#Gzmu5L}@yaZ<^4z<@FFLN8XthXTSm3n6v0kLoc22k8@OCR~s@U z@yQO_Q{QPX5Un{EpkmB~9V=0>VIGBD%l)_+!;<@jKM`=T-g-{J7DWQm+=fb-iY-+_Sr9p~}>|3&UUbITkRTkBaxG(Wq| zhSH2I=d6Xc8t3KImPRdf@MT3&I=B;6o)Etdxy!qW)#Bn|MgJ5~z3IRgxhshXN=per zRZZr2=L>$uNzb;!xA)s~-T;~lODan$>!k$;2@$d28bb*Y_9(d86NB{GgaIE>3>S;m zQezlrZn7WgA~1{aYu&#BK~hPaM!PZWaO&%A^20j<+lN?9WEtT?CYkKfPY2&ZRwLW( z&oFC@x53)saLF#Xbsbl)lSwk}bamW#47+utmtwjxJ4D>1s-7|pdvFHB_YkXzCFxy^ z!%r?u>a!NW%#=bZ_$8z?AmYl4JGY7N=d57?8NXcmg_bO#KbV}1HS?%iHm-y>k=e1^ zus|Kj-Ap8J?KFJz>ur}|$>v23JmbM z-qNX+U}9r7h)H#O#vCk4s{$! z;)Zw{L>*v&9+^kfrTHGZ#;w_i_(!7R4SZRdP(%J>+j@pRFy^nN--?s^QedB8oB3Os z{J)rB{=YMGGfNv6QzugsRl`qDx&IYr0s3gY~ph!!m zT0sdLf}*z5>eynjde76an6jmzQ~}=e)$u@rc)*bhocdmx^)%;kgYSIeQc%zj zkK#q<5~JSI)=En-GI?&M!z`UQ)24E8SdpnoxT1fRk088B|A>iBPKwE9bHt!AZO@WS z-h_BJc@WZ=L?k9o`~>x0Fli(_nZbC^4SS`;PN6uF_PNcao_W4H??xuUOpH4j6KpyA zILy$RV!+gc9e2+u=crM)y~LII%((*Qw!N`!Fk%90LB=4{n$#G%1KWgRKjGOOIC&J> zfbx+|Fc8ldV;n;fGhI1VI9-TbgXS#0EDTzuS-QgiqVo;Cf3P;P^@%`XDmNKnz{Ftw z1L=FwH9i86QS)`N++XZgMyS^}(m7cPUs)Hp4(%S7j;YKNZBpepsZXyN(qsL9O6(B;#8@qYnDZPf`?0*RlLEQUeG#u<7Ui2*{$ zWLZ4j+a&CZI0)o32#dUEZPEP@omP!%6*~Ac>P;r(Z$NTPO)E`&rK(a&9L(`@3bXj> zkk`?)UEYO&bLr2+JpYd;)NZU)t^@9_ou|yAvFm_c+*o_=uHC2HUC3BMZZL^Z;Fe}I zX`qtnz>egO3!X2Qe89E|2(wS-H#}@paTGA_lHjA4I|ld zB$3V|*$P3%l0a8oB{Sg+Mz%F^At1g_Z9#Z8%Pvl-qZAO%&MLj%6yABrx;3f_=VH4M zfFdyKK=_l5u3Vx*7Cj9cp*Sq^bJJf!b4X`$P7PlofOmpPv@Q!!FS<~!gVD))>gFc3 zGQ%8mR$Oo2By0U+YtA}^A3cQKx;d4WCUq=TR{|zI@VxdwfwDD9Qj1(mrB~ zaqA%!27(9y&fo$4=SX<;!Dtdp4tVF>fzZmnA z|Gth*Z6dam(k4+(DF#%PPAHvJlCN1#6FrJhQ>AB(cOY{8Y97x>H65RqTTPLFk08di zVubtSw*FA>99x~KplqDnmjE9o>a75WGVZTZ$cmBr+*r3a%;0M$^_sJh(F`w)e=IDA z95=pU#I;fN=SXEut(_U}fsYA*+KHI5Eo`qS9q%)rnsbsJkjU7%3NRdc0KCW?&YEK_ zJMy4~_t;MR9I<k?nWd#%Wj>i=1CHhsOELHm8wzCLhOF&ZfwW`cMyAp3%OT+DtEh?z;+i4$lB*W*k#(W@=aeW zu6Emjq0pJXA>*nX=7mgD3uudHI28KR2kjkJ+N(9mLORao*@vWR={NrS!R3cS*W1CO z?N?EI{IFE!@-WVLq97U{c*4%^9#e}AV;g*)9)A@c!$g~8_4+iEW0Er}&s;QhZ2|(n zm^}zhPk6}hX5#A8dDwdW-^}0%1ra5g_VLHlKK@Fvy}4(vOFx<57V_^B6^{Ro4Q&4p zCQ#K;nf-?L-tz6IT=3SqhX}I~wZS*ErcYtVi#9)KV(uq-HI7`8d?wS;E-DdDf?K+~wNC>-Fud`4>`;F=n7+)-J-JUaa@0YqC}>cdTdZ zD;^>jffJnr9YsQ)6!}Y{WPt{#NE`rc!m60JMhHj^pL4WW56Ms6=RgtamaRgPBMiwR z4Ofg5R$+{U$}?a{&0N_>j<1n0>xfS(tvkgWddp)+RAr1K!eeKoDM5hgx~7~L$JB?! z`G+d`D=^T-e>+qYIhD49ObI&JQX1+`>a@1+ zvz0c~LcjjeN|VQQSAtu_4vuAQwe&?RQuxQ7bX6o}u+FMWZV%I`q#l=w6|xSv?z z=-8^NLqWI7@26aetESU(GC1}iN6Bt9noywB(lOyQ^L-)bJh`ayGX@kV{wrmFOoyk` z9Qe2~Q&LUp%XgU0Y@vGMEn{pZl1H{_lcFcjNaP>{c>Qmc3S3m%b?U_8lSIi%!*P93Asg_QA8akeiq?;o`g+`_=0g*n z!bQODzmo%K$#z|WY7)tUY7aT3$(yG=ZMJxoR}K2*&R!F-m%eG3Ie@Nb$l-mrMlonu ziL(CvseeF1t$yDSL;SpQo)CX`_&eKlBBL2 zmh@(ULk0ShcO*oBBSvVPx!7Y@{`n?FKaMR@l1Et*btKQk22hR~CA$;zNs47zDx!)2 z{d6sLU@6&s)`v3{$S60DB+15#tg#782q)LLAxB-(Z^Z)k2*-|2>Bu^@#oqZA>H!Hu zSB;lO&2~g_v0*OP@-ovQ&Ek*RdZF>VL^;h&BoSkA@J4V$tmdQ$vr4)=W7L*Bttknq zI*V>Hm8Zz`rL034Z|(&X*0Z$)yn@+Z!+r^u2Q2)R98F;NDRF)#N05K3RsH`tn}5v~ z$9yVPpYk?;Qk-4Gvfq+;Ov?kX_MGDD&uNJ$A|qm?%0!Yo#|C`U%;n|!4EmG0hAC}T z#XPh(rJWqPZpctKh>P_M?};4N#>2(SN&T-bQ!e!vTeZP*kX^yv2rZRV#tQ&XOgLu- z>DB`y2xoS*A6tgVv;;Wi-b>xKh^v}6NLxI>=)ZKT`}}3z14=$s-QUo8h_PZO1k7l` z2eg2i{MEA<(pnEJa%m+g3Z7)gY-|BVlCvz;tMska9&`h})rJOK>WUIiSsb@A!jiJ# z1Oo#-z;o`Lkzf=g34gsVV4N1(;Z-VC+(1>bt?{0Vh41n)c?+NLRMQ7 z&8hrp!7gL>BRFTJ^$NvWGc(lxR%O_~?3cut5o2H~F&OuNUCzX1oNbyPN zMh@?zg45vi7KPA8AoF`}$#)|eL z`=%Hwto|F_d#oYP!dg6GPL1aGGhywZXHDlB+iPmyS-Xv&LyL2=!HFhSRxv_W11$)h zLYw7ZnsKxj<$x?}hj4nQ?=nornBbqQ0P6e9N}6c1gT_Q=?L?3(q?!V4lSeLogrvMJ zjQ(tcPa$rw)xgs;f!CtZK0N4@h2!tg<%pYCt!yPz*EnkQJ1%B+fysz1Sk#%Hb8?c> z*IgyGn`F}44mS$KrEq-^x*}0)=rjLtV2Wmo@RdVWww`#Bl%}YAJ9Hsy&~>*BqfyY^ zWC5<+$B)pju2gBBm~LAoBG`L}1rje@TwynzPQN%YSF5%oXZXJ>3^kvwy2ODWB?fhb z?$wCc6NeNSCA*al;})w4NW%xx6jJ7*4)6hd4aQ*0>T!g5)$aVS6Zt7t$wfc~nFGZ5 zeu*~n)F7=dN`4fs;fVXZ6zKCkp(lvna3)B}KJAg{)d*>ap_XA8loPimrB@W1v+9c+ zPMqnYcfvyl^}lnbe~Q-M9%ylu%;X)T3Vy6$_rT`nI_sUm=$R**of!G-3Si5tPUk!U zuB1>Wm_)uqn*DRZWG-16RnaHtwf{DT(fvm;OvK*K`CrXaM3;{ zSY-ePmZHo0KL{ygA!lddiPC^FWN(2^kk3jb48vMOGR1cr5(uB12eYoIAAt|se`nCb|U z7FpFFR=oqiB5<52PKIEs)howQpTBeP9sjoOu*l=eF;FlF+gFnCYM4IuCa|{Kv~TU) z;EK1Mv}_70Aobo8qn$6Z^dYU+(Ng_fP&oLmy0e{I$awC~x9A=`2y*?0X&T{0Pl9OP z#rN>Jb+ z;(qtpVAqrz|C6|2mXgZqQgY~*{k+~a4bJIGTIMqKD8Ys-xroYy#^<`>#My)ftr-cv zk#Ofh%a+S$9gXce(m{>w+R;aO5)#3K(i+U(!13HNhHxK#OI@tg>-A)rL=~QJR6(-E zV63awgy)ITK+K4FWAtF&H*rwzseDc=MO%zheDWK4Rvxnn)@ExQ4)+%xxp4I=rlg;6 zkKg(myG$bo`;NXL)SIXa$a-<}x zg7rVfn1<6onnq6lf|}B(eD7z+NCY-TJCvqN1IH*od_E-#hm))t2@@-3v_QVNI45!D zMK9ArlIlT_f4hOr9<*Ct5?hN2ZMLCqOTFD7#o}!*7=(Kux)6;eG5Q@t?zc95g@H65_xG8POG4*=vIsi@Bw`f=7LrISqKJlp21;B^fiyaIIzE>f^BH7y@h8(upr z&vs9UAG`sbG=wh4NRE|c7(2)Y*g4~adxhmJ5W{d9iBUwrKKUTs5cgq1LKdNTI98;| z0l!s9i7S!l&zXIcc9W>%lygg z*~oyCZvRvd$J|ZnKAR~-4embq^!1)|u`v9A@!#u&14Vpt zNwQIfjOf^$zAgd)5_V9C-$c&zYV~<{w^!9gqGU$Cu$FJL5F)4kHHnbpB_xOxa^C6e z`}~f=g3gkVwzJG2aH9uD7A8eO;Hni3l8xXa2adIdam?OovIN`Mot)h;rom&$46qr4 zp3Ioj`v|zk7i$7hPjx)l!ehdqDtB zk^s#)^V$O?m&W7PXsJ^5+h2a@TfY8Dou9yf|Jz$D@gD)>pP7-ctEG*Jsnh@N);XCv ze{N^`ceRs}Fe(koj0X5{S&`Q%)4qIsrK$-MSwt5CUq_2lT`MzAmZTufh|X6q)hL8@ zJhy%#Fe+79izcFQU>J6Pca{|B`o8&v?T3-^z(92jH|{M5h7H|{W@WdyDHwM&S9!sS z0X9O9dgpYk4^8=Dm@t$6t)dqnl#!fwkf&Y^7Q8l?dZ(jQy`KH%jQ0sNjeUpw<-%2s z_(OPp(01|Bi9kLYr-8-xeHM9`7DGoBB^B!7fwtu-`#yD z(0~NLVHB+_NU$_CD`Qw8qeJm7w}Oh8XNGf58!$q2>M!s$@R9O!0VCDHP9${@&|kfk zA*G-rwxmiE4fdP^T3rPmU1l8g0RMrcro2wUh+C0OR4S|@^7*;&+1QA~pdgxARgTZD zf#yaM$t!Mx55BHy>oT9d>{wWr%gEooGhKDa!dcbSP<6JY`!zkN> zt0L71z?)W1vz<$$>OE1ufYNQwm5}T>9T)il2)B#8C`w>B6vu$v85zD0XUlviaJU{U!V4sV z0eUz)BS%YhV?yxObM^4;y9Sc|(;>wy>yB)@0W6)4qEjVBgkdS%rDN3ihYsq4Ut&+O z`jqf79mNNc9b!Cj`l<{9%tb6 z*z|^Bb<&>`ZD+I9oMfvs@A#Q5Wt_*HOQ=xn{IO~QBiiWp-Hd3(GD8O@kD>;bEO-hF zmu(=E0rtx(Uxx?kyCVYXadNJ6h#nNN^uzrqwGeUL`;Cv)(U`3H6OaANUuW)#A4^E} zXzdYTD3P)pW%^w#3K*rxKy0-pft!kfAQ6ZO)d1K%JkjiPtuHlCvch)ms{R7 z?Q3pm0aANrl1((Tn(?@IFeyuTYc;WXdQr|S>H4r}Qade!yL~+Fc52Szd-7vad^j0d zQ%M%8T7$o$QX}s-xRhd%+!-k(kywtBh+*1n!$+~m@x=!%w5dUtLu+BwY7~HLr0N+t znD+nV2XwmbFi4pztxM$aoo{M|{o0ZGxqt-Mj&m;yD(RY5tJaJRya0fka;>x;0UV^| zd)%W#E3tjyhUfigAsU&RPB7lp0k9v~*BXZR*3^MjLsciKsnzbwjbtLj`WPvYLa?}= zI;!O|(fe^s*Dfy#!dYq0dgbGMhRbj@-zzEsb`e-P2Pc+RzXPk7jztwft7B@v$~Ovn zZ5aOXw#f}k&FTku_6^&f#XU$k{h?nLz3ax~!9J*Ogw{v$2E;|Aa@&}D0G6z)b9z=i zoAy!Y8zuL|H}e0m7CJ8tTw#N)R`S8VIaIbt{+ML`^a5i^erc>1DJZg^?gD!`wYDN( z$R$+r=IE8_lVEuDXIu85)CU$iUkWN-V=yBVs;uK@f^ zGdamEMe=?AdzcLT)>8b*Q>yErS0Zv@g1%Fvc-V|YFp-v#hkvY&G}u`E zECH@SsSkh#ZEYXBWv};xZ7XsDqEvsKT9k03gC#i54zo{v5>ra)Yg3C2|C3qUA zvl8l*l5lB%Q1|S6O;?i3uCq0ymx`xxC{9uwMJ{0$8avL)hjHi*y321)zjqI;DkE#% z;a~pIJX@{^&H3wnS~n>92HGbjW_^DC=hsbK{{Ut7 z8Jw&j#>06coAog3mUHu`KB2%@EFdXpOQ1Kz0NfhqRd;ttpcJSIzQtgurtWaSwh;t< zYpM{fSA%2$XB3BSWdA?*5Kj0BDD+(q;A_>(W7jcEUDKvjWlRjb&>Y#qiW3)8HYRPlpX4u<;!fxRp_5fF;OUT410pIaFeF z6x|WIp}nBctRCduH|bW07w%2Z9F~a6Oh3I&WfWiTnN>~Rpi(UJ?0!TQhquKCL{M&b zIWf{CvM==rn84@=YYVp7p3*EhwF59RjoAD9Q*!-?G09|GQb@!A#G=S5VmlK1KJO5) zgw_rDfq8evogMlZBH}*_I}#qcDezwh?>Q)2|I@&1G{Ozk1`u_;a#-COF zzjJz}zZPow%IIj*1Q9+tUgs0Tq-*l0c@S@_-Ul#Ww^rk-G}uBnQM}V~vnTWsZpE=o zEDV5RE&<27hO<0xum9XWgKt3$a%giP*x;>jIrVI{GATM6_Er>q)yU!ro<0^}UULnC z_TSCjxvqL-%9Tub>CtuQ?=-}SVw2sm{;eY}T9`~{^c_n~Yh+N`tH_1IbB=)Qdd-|` zvz!2$*SDaTOZFc+^^Gw?BIfWET(9Pa`LyN)K{Hq|uXIC3JWw(CC2vPfDWzv;w!+JN zNo(GeOK+8fy<^z|4)Zi3DBPAOP)cVpZXwq82$v1|JWa_Pnl-GG>RU{&4*7O83hmxH zdGz6iY3vCkab~oQ8f6xJL(E4I<({ilRT@ycS`pX89a^pa@vrBS0e`4}(R`8#?%(EE zl>f;g{F6VLKkY1aG=AhJkn8LGax_w+bnGxuL6K;ny>!fGv)oyVz+IKfVyjj8r7NS+ zf^sZ9omg^a8`z@wP(n?vSJ4jU0J|g)S^};uO~}_GJnEgiFSJQ}rMc0oqlsg7(V1v_ zESb6QXXsFzdf$qagvWLt;aCo9B0?Ft`dgKAq2WrJ>;m)s{Ud6mUkZ2o{{4G9&F!hQQ#ZR?2PU`7eSrg)CS}%@=KJ{D=zgzs4IB#$p3`kx*^QpPUD*o*r_A9TS%IEC28uirC&HxH z44=qhtfa7XzOg-Iia3^MwxmfGZ1Wqgq)FxsgU3qX`qq1GSds-Cw z<~Q&ZldR8>6q`*zhzDEHj=W*W^tYlMBd;V->1`2&dY}Z}RzMv0H-J620dltK4K^el zz&xGrFo68Z5HcBPjNs7gNuj{I1<$k}5Vci=P##cb@4dcgYW+*Olt_x>ne$q*LsLtLEjO?m*?WrS;lF!)LYJ8lctgm1)% z_=z>?H%dkJOpkF$erxAQ3BT0oa_7gUpBx6C0X`qL?4SEN;aAZ(g^m-KA4=qfSVd^?WC563U; z3zS=$pvT&NR0s)x|@GG99_j`V-C)lsKDFjclVuA(9m8>T1>Vd{X5{fLAmUyRfKm z3^kCL{qj|mO#ISX?D91zv$8K5IyLIc0BzdQd{4TQZcM-DOV^fC^Oq%o@xgisWE;`> zInAlVbz8o6lI`+B>>paRy^z3dlc1DDM90|s%r`1v0=H1L`w0!-bhawxh~DtV?#H~H0&R_ur0a39?Y7= zV;u$MS$^xeaddD2&?Vh&G&5{e^_Ci8gPy=HS)%Vy5*DzgGU{kB(v4AIL1AEL6uwE- zsP$sZy7axKE`juI%t6Y_sBlW2mvI)6f=8&_bjb{T+ojnEgbR%VNi`bPT{eDptL!zah9T2$(6N(zR( z&KdzAo#=LBApA&|8>0v0e1nDdC{&}3Ypn7W8+?g7zCU^M`Z{QWEvifioI0XY zuxUGe350hvBlsrg6gBNa8k5)h~ABkHw(neooW{ zm!5hk(Pc>heT0Nq^Y`glUio1@OYMGtg825>3F^$ z5J6{+X!$G)b@ge1l0%zi5GZtn5!3}pzhe~Ve2Sn<&FI;ZqPE^b>wcUqD5(*vx%pw4 zb0oqIw8P(K_dG?{G#Q7Gh|dV!+5qawSVbg>OQ`wJ-Xy8%5GqzAgXpwO;O^}CjoG}F zG=B>Cur=qp$?-GWmSRlHitv#9K(0d}cUQH)dZM;xOU5ghzfH_+#kmD!b+*q1B*KwD zNc6U=%0sK&=A002FNs+~f-5{^P-q%kCA-Klr>yVvaY}Sr*3s-pYo3?7DmRO+&Cji2 zSlHiin&0Sv;%D)KWZ|$r+v0a&G}nYKyeRV<>dkt0r})k~wCxO2nX1N8W171J(#fk} zgcUDfD54@;-E4RxtM7GD5zig`K995+aw9PGtmTF8t!$H@aQ+5IxrpxKfbqEdMJxxV0j1IxWep2^(W?n~c7o%=W!>^V119Db zvwN=Y?n4UWncL>K@kj-E;+Jmj7ggtOEXK`_O=BB z5^pZyQK-mWjI+$d$OW*BuEJiK>;qUQ;|+8b@DlPf1!O4D6)+!*3nq6~Yk>;Im`lHK zQiH%SsDjDAOd;-jfXIAxr1h;N?+VTBx0^(R%PT?NA%DY;KSB*zhyOj?t1j1*>-hBn zhYsVIpK8WiGJ2h{mq0uU$~$xjfb22dw}2Ga#_@=zOCChgXQ(Jjffx8WYv!OC(g{kA z+Kv)=g!s+LyRxjC5@Zve(R}$8Eji-M_sgjHw`GVdO2|95x89IiqE8Xo4*5d<$#6y0 z*lpMsf$CPQbYOchXzDxTOgrW~70kIQN%QMeE`IF`H5O7}(Hknjrhn1Y7`aY?mveSQ zgybm2$onxuc8OFz^gD73;`?G>EeP$OugQuubFw`fgJ4G+P3W{^;4PYB+Q6$ikilAH z`aW0TqZE>(A9oYOd8``5kw&gY zSPSF0i|R@oS=GMN2J}8|DfG@=Adhdx)fBcqM6r}0zDsBty@+mk|IjPeM902HdW-6A z%ZGX)dQ1AkLlU%s_(2f>AvzVdod$A*jC{xP)COI{QLr#*XiRYW`6Hy7j?Z)(^y`)N*M5*SJR(;TG*?k+D(P2?Uvsh-q1Xmh5_ z;Md$4IYHgvNMX&zyO&B^&B@t#_}#G_A8_XV2W(wwN;MICz-UiUia|emJ(RT}vw9$E z)$oqQR?1lM*jn;Yfvoq}QCZz+zQ*asvRvKPuiJPX{U8d!mD#EEoB4~3^IfO)!SS4h zuWk(mGb7(mB7S-E0VnuPG#IN3mh6kJ-K6{Myi#|S=$H>b!{VdacwTJt9arlh|G?Sd zR(hyx`Lx^;1=IG4ENW~LG^GJ~ZU{cxMg(U!!RMdmsZr~L9Bg`jHpr%DoA!E^<2oq_ z7tfwlH2D#fXE5ZnP^UnH&eloZ$S-XgR4(aR=J=V?ondyf;`(l9_+$<;@NvP)o5}T* zUIT6w<+iu5bXmTBCxwG71x_JESv+xtZabp^pqIjvnqX9e;^zgva&CLJBIboazp?O% zw3=VjUdef--cI6~bkIuauqJ(_9Bbs6q^)y#y{*3nOWPv((xVvD18Wv8-u@Z`V@p{utUw zY)ZmQU;dQdm|6z@xEdm?B-O>uIIH5;O`40BADH_B4TZ=veO^FzL3d7foXiu&Yn#%4 zplh0j4tM*B$}c~Xwh*1KLoj#ESi&3G&j-!YN{-4`8vJ1u#7Mr+ z;Ty_h$(f$9x0hhq;d<6vM;(Y;x)n2KC!8%YtMW5}Blp*UF98RfT(1&k zKj~f*QpT5YOIM6>Wn(OtR5+d@jcwKEBHfU{N>-xDm2X7 z#I-D|k_nAmr14_rsb~2rOSH#L%mEKg`&#@S!}~7NPZ?Vv*XmXb!dHTr2!3DQ4T2Uk z)w;J!rQ4qHvg@J1F9P#mng}sJOM44x`6~q&$VVRuzmJIjPs+F zJK;|XX>BWcP=ZPV4(C7k@Z#)H#$^O z+W0$C^j_3A$P=;A*0m_|wOo*oLo^zDnW68ODXsf*?wYo0fB)4u4L9Awi$whLh4`~^ z{Xgr4LjOs5FZDT~Vd!H2e{{ykEbS~^$o}0Ki&0va{*-Lr8|+Tic{vZ?+L45!Y*4G) zH;_cJphOZP9xcbo5=)C@zLz$41$*L1~{)90dv}>nnO*>VqTFsrrIct+$bL*~FOI1o!}aoBEc}U@sLv?V#Muu`V2yykTNWr;(*h&F|z~(lgmP9J7!Z5 z<2sQ#-aa@|kWoz^tMVbUITta@aG}3(|Ga!V^()X6R?9}ZOFQr)NcoXnJaaqt!B;0y zpH`KmN<+Z>L6=?;0dJ3Us@^tvKz2WR!@B!E?Gb#O6P(_oLu5;>sY zsQpp9jkhvx@~z?(FjKXiUu(R~R)dLet*NE>*z7H2UgmvQ`%FkR`LR`ZTp4h{_>cCN zB))#V+@~}{`M1){e>7MSUgXPE^6BI=bhl(b>e?Z<@keh$g%WI|2qQ3~0SaL(%BVMt5 zU6G$l2xADtmrQ^dBstq+@Lh&hJU9~^VZpbDe6bx2^gd6-N|4VOtVoVw*$}NMgXafZ zP_sW9`A|#m|HIll1y=&>{i3lZnApk0wr$&)*tR{fZQHiJV%xTD&B?}htM0?u``%OM zVdZ7LtV(y+zxzk=*UCPc!Kisn9zub19o-17{ZDb!3NS-^0Gd)Jj1PXy?=O}^;aTir z&gP8+HHiATXF{;U?o`l8l*K&4l~=ZA^+LwP<2Y(bmx@d5WP|GP!Xx1Or#0MfQWHDw z!g-AqznlVH^srGO;lZUJ#ynAuFaSqX+GIi)Al^qi*Tm`?Ns$mPb+-i(E|uJG)b<}MeD}Vv>HNLI;s0@k zh5pY@r<3i!rGEcor}|#yZ=EjBy5?G{PUT}bp?KgBq+w2#KNNT#x#F!ivf66>YT~8n zOijI9crN~qSXc(>3-N+>Y8sQ{(M0Y0-G9>ZpHUs!P5Uv?92aiBBk{`+=Yy-tp3fuC~8Y# zEBxe17_j19ig`h_u29V|OHk2zWKqmh6o68#h2q_^qO0G%vQc*}ddhonePAPRocYxx zFM=QVRXW$EsoBSyn!@2~GM&ZY`t?Vj&wU}XTPi#f<0s8J-L6cP~kh*`DN0eBb z#ZOV)=4acHjV_j7RM>?br06ywwS49o__zi*s1gq*gy|omab|i#-g6Lg9jR{X#_sxM z#OmJ?&#xKSSA0Hxxnenb6J;Yz{^^<`e=1?B&lV~qX4Bpt^;*)Is73q`CM$-E>_EeI zCtT7lo!YHA>QcENyYoiEPd5^dZmp)H9nuZzC=GMaPTKKEbu-8Vrl#0vF&)j%px=@Nf&-c!gwy)!U2dp^Oj+i2^3^L?x1c`AeL^*x7*4JdJs z4ztAQgLuC)@&_aB*vs!B63i51*inpR#4%Bbvw8+h&C6JM!;*0bF72gSZo`mB4X%3i z`nJjFByjB-w>H;c!0B|O5ZPAv%>)!tyQ!*e<=BBr|DnC0 z#Xl*~ax@=48WtlDctB@+=Xs;uoUgtnCe#&I_uy}s0D7#*|C$80=0^Z)lFivVay<{E zLDLttwe1;{uN#NDqedqwS#u6fjs*P;tbI;m;}_a-9*!0@+JIf8v zu#SlTW`d_5T}rf}L~{}rS=YUdkC#^|n>t$*{SpY{*r@}fy z$11+v`8gb(G-i`J;u8gd=DnM!8U?|y^%I;8D>ewQwS0f&Y{igIjT%B}QPv>~YEt&p z%9BOaoYEl;UBRI{h)GVMbXUtX;r-|vEvE`Z*{0N9!T!RKPVSQ)u^!{8Int%>d z?yJ{^<4K`QI)izDc;x*G{xXntEOS+L&mz=HUf_DH~BF~Q2qmghIToqjchHPLg zO=e8(+?c4Nm{QH1&jsF+tIP!CTJl9S6%?<^swfw)E$@@nQkE^Wc9Pwn?edacRg=w) zQLk2>%H!|ivE)QJHgy(wlWRwlM{Sk3yCp(kDBI2C^Z5X%6mYqTTYIOFT{uz?-O_Z~ zK+4j`%Ze7Snbi(u+WFTKQ}=~P-M@^nSE7-{uGJjjW?YQ3dP4em}{W!BI(WJiUp*yy>VdSmu`F6Ozd|M1E zM%7T=e$&Fy5yA;Ca5e(Os-ZI(^k63rh#uJ5AdTvQ*#f`1t-_N-ZfaAvC^Xg-8K@|; zNC=(yc%yP7VkPi#5c`D)ze4KXek0lQ47x)r1=D+iB_SD{pAe>H2B9&W2IJ_M zb~%DM!LNptq36NspFx>9^5Wml?_vS-Bxz)->0kdEJtM~%3IL=o&U#=fT}c?zMggyr zOw_{>Y_q~HqV-FzL!FgcbN&Uzz;uSQ*J1;Y1J+WQUd08Zjz(*a-12B1gGR1b6O6lL z;cWjhZP~$%m^&%IoFb6M7eJllG8ttnvCbz8;8krb=#-YK4h@M>B7M#$qtyhs{p#)} z`9f#)S5+c+S8xxMji41jlAjE%KB)%fT8_8`W3(bGJH*~{sJ+(x$6e_b{1;{Jw_bk- z?LWOdss8KmGq*97Hn%kX_w`6&@>@rZk%{1^@*#xc?PoDI7@PoUlb#wItJjZAeF;fz z)6&!!&YKR2-y6c{g8~Q1ohsuJKk9WqdEw*J^#|x=PjFW~ugI?4u6Ty@;w6i>@183_ zq_hgYe?SA?F`@MrtpiUp&Ia0$Xyzl7O3!v1QR8Qy7T}o&b;5*6Goa< zo4O;?Z?U%n)aQ3EzmyLa4je61`k_&NxrJ^bWt6741$H4#&;Bf08PV?R`)3x&ie$PO*^nT9ZY#b~ml#%FBdln8)B~qf zeJ}kG$jdfJ^Tm^dReH$gYFpZ?vL1ypH4ohh%rf)E&KaySN*yvdnT_TiE&t53&N)qD z{zpY+0COn#pUaY%zg1iBrdt!8?)O_DG2FMkLO?&!AgOj9ed-qIl3xc!d}fc(Gd}V+ z=~&-IR_HiC2=~yllH`G>VfdjtS%He|h=F)p{eQ{a_ya}j$i|YoG4zdt$V1D)^8;lI z+okR>`j`CVPTG<5FG1wa+Mxsq(u@2h`IATgfoZxL+yCoxn!7^k0rU1TqD zEtHl2J{TF4ERvSg4XnQ@5_iUqhyU?U?vxz}|6@e%oSh&rI>Oet9TG4)5;u-MDkvR^ z8%lpQ36+3OzM1gXcF#?&Jbjllm8LButu zwA{}R$f=*wNFNF?z}s>8VBNwuR1{t}TohwF9g2y7mZBQ~dQoYhoFt-ejidrNU%Via zGC;0S9Y{CXfBWyK zMzPYDPP{Jufu+wD%7;;h`5^C08%n^< z4~i!MTM;`@izEZsb@F$rH8l_zm_Ng%8@&ZFY*u$6lKD6=aM2_-BsjIr7Q0iEgO<4* z`J3DO$jYL{!dCjW_X;bEH%^CT?Rq#Rbdfi)v1M?ywoDssCb8e#$|i5=@Ir#cMXd)c zCY5?{Rr;8^APuJtHfc27{t#1nz{U)s5=V79nmPVlwF1wK=mYuek^?hMn)JG`1^yg0 z%w+mrC4e$+X;QFWMn!_KriuO9K}n*BK6>?~A=+q^YhHt}SaCoVwZNDVol?^Nh^q!c z>5vgp%$x{$IYIJ*=4ml9JcPZhs)<K!{{RLb5W9S8-jtSXCL&D$H*Wy|i zE|au}hw;o6Shl=Y1`C=O!@K+U>g7PT$Lq5}yWMqKPOPs2T@C7} z@Vk2ENC57tPY0nBQwJ=1N@`&}tkKq{Jwm%3`CgIbnv2&pE{#X6WUG+8*;P9=+5Ecs z-g)Z2g2^>hj%zQL$B|+CNG1J`2u?SUz0Id8)7zce@sL60U)u{uI^tq1GMjgP!MaL) zk>$N|<9kH9?c&q)E+Jpr-q11-G@FkenuMn8b)T4cGKQLJ>aC$X`$ z!x6fC*}%rKIk54ILsqLQzlidzE4~yJbS5)rUg~Ev(4PhIViaU&<#stSSOcu~DzkQy zTuO_=Y951!jADPvGp22q;(hx?*x+Hpt}N}kx{oisBB~|a7$^3*;xY{ip_shta12!y zI4L+zY^JD0UkeL=?JNZ&F|JwKq>F}=U1En*f-9VGGu@-zTmjW#tRaxKOw< z`;64!GGZiViyaL>Yg4a*&uG!Ev)IOHAi$e9A4;LuLmJhud$r5^0MunUNaZ;^MN5r(g1`e5s*XXu+ z=JALYUt^}tmfO^4<##YJ)5Bd^w3&-XHWLsHQ)&fIyOIll_Rd1QUgBAtFWeTT#!oiU z;56lnuqQ<1cV(?93n|2J1;rSB7!3^BN>)|D2m>k_vBe_UbOVyynzD-)Nh%%_ z<7Y9_yA^pWv=!uZmRKg~-nmoUzU&~_$Pfo#$WG+K++nP;lW?Fj|J z>K2e<)_yVe_A67E9T-;z3(bxTSZ=9G*qi`6^!^ZzD^Xcn-(v`lE09*c6t1+Un-C2R zvb?kiAzbWw)#&!9q9@0NLu!@@g%$u0cs7f4KhSX1NEu1BU2C_5I>3JU{zqq2ggSJl zb6Z7k2)=K)ywrZUgnD1qI*c2FsXbXq+YesFR^F8B>#4Rf z)<(Mp$*ra)99OahvXNGBs(UrbhthdtxnZ`vEL5pV%6Dxoqj}W zeL`9&OZHl$j?0c4A~&L>1{v%Ypi)tG3tbn{E8`094#zjjXM;G-7`y|o&c6Fg$Fhls z)fz7DgBDK*r-zKY#hB$^j5c42FHEME$ZA)TJG{+-RR<&QJ5`Z=@9fx5z1FbK2x7Q_ zEes0+(Q9D%zW$0^Ypl>b+!__jn)#) z;^PWV8>fl)-0hd>BX0Y9Kqi{(pJtk2?@{p*@`W_Q+3yG!1?+08Qw&A-b} zXk;U4;@t=$=8G+pZGOK&zTwYpamCXImk7i{o0RV{370;X8ckFA$wx-3f=t)*o4J5!7ygO7{I!4$p_k$ z9a%XShJNCZu!96-$ySP>1Mq^M88?P*c!Sq5Juo~l;psbb@?!FG^kelycR^uY8N6ck z19ypF@r_$Ub`8JF*_DHT3560&cNLN4e31ce2_ilGSK2!SK)fdMGMWA-Ec8(L#_ zAz`L=$zZa3Q&2x3Bp{UfY#EC5u(svx@N`vfh`Wn-D4HGohD~mq!O?H+!BcMB!6`6c zOm0cQr*~TYzXhuPwhSMkyTdTm23mbAUb=lanm+I`6mED3RBnE|BHZOWoOlX1YOFUf z-DNu}ULt*#7mMeux_#R>Jm7zOTTnawuF2hyw^eS?x=VMgy#)N{QJ3V}1;fk;Y4dtF zXXx|;I`nr^Jru3O%Wvz7^L7|$j9lpO@L1t^ctrtC%$cxd3Qb{=!eQlPdorK5wJwrB z5G=_;6=;l7)AQ1y0}g28yawduEJPLIM4R&5#EI765oKZqNo7b`;7%l-96ER`)eYyS zjKz!d${6;nk#B8M_XZV4;D5WR3wCwvhGuQ@vT*R0$~qknOLu7OU$w;ORCWBp9ICo1 zTBq39jUpthvPsbE%mL330g;*`h&9UOrpIj2dWgts9bK1BhDN@I2WH4Y%&X&kCcM^Y zZxFyu-{G})IXnsl?Gebe6X%v*oj;t?k3`DXOr*URgqCS@T1DLTw{2M-Id)oDNw3vJ zL{64i+iY$hCx5?8bS%2S(=0M>DEOmtX7Ja#MJZK@YJq)%#qKqKyhXs1ms=tDa#Cvc z>+D23D~hB=aJr)@fp+L!)%55Ho2aID9>V#kZ(JcoOsyzg*ZWE{&AKBWTRW*tW%=dv zRob+}9cR7adMf;IqFaIoYbjENj#lm-Dfr%XX@+pZYzz;;?AULFc_R@sJ4zPCe$85rEOaLc~!%&OGzo+wc{j3S_(!&E)hfWgcIVCq4PT3 z>K+vrX40bR<2SX`WN&rr6te!&A_n54VX-1EEl$3WuR%?bw_u@NRps+1Wlh9GWR0S{ zh1g#B!A;#CC#;;b>sFFHnmB(Kn8pB!BA$N1EU^gMBLOOEem>RYDV_%UFs-DJY6cMo zOoKVxkGq-_!J0S__{v~k39;?}HTGEv?Bsyh2# z^6Z`)VRqe`dC;sgQDpTic5joNs}^k%=t_g$si$f&r&W30KJ6dknWeSGRq5+w_ll4v z=mf!~)Rul>!d0OVJrb{RF6c@3b7Hc9jl9+pWP5oQq5ZDlK23!zCN{{2{lw=a4!KaU z>${#}t+Zwm?1978i;kqE@w5`n?IiPi`W)j1caLIScUG7m`(%A74XL=f$VQk=`FML{ zB4wEivhrV~kDe4hL8GO3`AhocII^n~*;fusp8lE{pu5#iXqUxx;<-dKMR(?G$ZR_8 zT7G&Nx{ocvXfZ8pkm+22D&TSrX5MS}xU?NtY6pO{dIW9p(pylhcVWX(Dd-<4LL?*Q z8!{x{x9NTT3UBzB-(qtI7WvAXDYv+#Lv=56K{U>6IvR9 z-kRtexh*c-ndOlMWgUYplpL3-V4kIeAYQXbo4BzsKVl`jHy6&3r++~+w1^IPqmnUg zXrRpC|MG0MlIbHO)EzQS#&{~Ws|bBBOw}aYo7!n0tUmAb^#smAws#HnXQeq>3U58W zHAi&K7};)UWdm$NKz;%b;$1JDp)`kbf2n2vzDGmyuY{&9l61pv_g$l`e}fqUQ?4Ng zTMjfoM^qqs8HjJZeN!w&X}eK|zjHx+U3TMdL|Dup$sRl{3Z`&x2_LE1Hzfpg2+MZ* zrT#*qAw1)=m*%%dQ}`PV4csIl?2T*>yhxX?(^ryAT2R9f1i|p0kWbz^sHKp+7*QIt zUxIn&QeGdlz_}VGDF&k=^#UK(Bfu2GCuI6?Rs3n_n$G?GK8u~sUGRjv6S3-qFkj;e ziGL6u6|vfjGnJ=5>k5QZJC3rgFRmKqY#lA~kPL&MTzfpwvf!=ZNpn_qqs`qTxw{2f z;`0?8ZX;oN#+&O1zirH35SkiY*y{IKu8Yd}?IeyN&etT>n$u4^ncL5GnOEohpu2R% z&fYpZ>o$n!1Le(&KY0i>t=Bc3z5oC%`nbsH;hO!uBvP^FsXSpM=Pr7< zsdR)m#BmSWFg8zX!e)aJfV2IzN|R@(7|v-$+G0@`fHVM(PZA!e>u>w6`}t4C`150S zh7jtz-T~`Bp>h9(0#yAjHuxXi&Ht!vx>8>MK>-f;5c!cypNFgdv)kPeNC*?@xL)g8T54~0v9GYUN3dAO_murxn!UvZCf^E;IDNXCc};)q zz2)tC)zxNul=H{-^Sj63kK1hlI-k4!Y=mxS>%m}+&Ic;{vo4CgpB^fh~ zD^XihYpOTbH#S%qu1?)K0Xxj}*Gq^%ZYkG+=9IAqrYL(w%cECAu9WPt?@`M3D%i(YW$x9@i7c5)#ZiWC*&(2i!2U8i322yf|kaNJvHRInG;fS_vYTwn!&>^(fVK=o!dYptO-fl7z6%Wzq zbsT+x5^ibe${SO9QqC^k`*5r9O6CISyK-J+dV$T#$yOKt!NoMR5~>boOmvyl-6aW{ zl+Wo@**e-Q$3Yt#669xEn{ww>JH;RtlO}pcNSo};DEmq0{eU%0iyoN-CP&--Ny?kV zT$bN<<{szO-??km!#3@F>uFde9280kSM%8QtF(r9?9J%-6XEC5O8xWz534zZlp=R< z+*K~@c_L|LEBYokuLu)N#4I1q)96)Lz9dI4QMZUhs9uWd7x*K0mB_jA~B zJ3O_WRlN{raG0ivQ|sU&TN&YwhZXoO@jm~D&DCMs(BA_}ey&pQLs{07ydKb3wa9g` zTL!h(&QR)y>GJe4KSDzAqy?&%mQAnJi8!|*)!Gv6;BgKW{-K#i*FE1ELi}YF&7Yw$XeKMC}IGGM)gj`HA?ztuz8Mc_rWO$#Nm9~&dJ7!p`qtqOI z#a?B%z?ei%f?t}SskR7l9SUpP=6eS3@6>I0muNI6tvUIud6AP#=1@_dLNgWVy#_{3 zm&DO^Q-V{?sha)>a!x?IwwD?~-%~6Ui#Yc@b>ABT9`mx0eU23IuLyX=GTo>Xi_z4rt!UP$sD_Tht*L-jt%hC(E4woDB9?ZKo-Cni z+g)+uR&+Z(>&AxvD^*v5aKimYO*$-5a)VCjtvtO)C)ZBfC;bImxEga(J#y$v;Rmb; zo)df2Muhl1MGsNb1l8~+;fU*Y;(fp9{n3hMC^(Q@!L=!>7o4MyKn5jV3NK^EtnvnQ zM9Bw}XAO^M7O}~7Vh50*c_X&$gU1ud<5rLv-$Mtohw#E^x~XeR^dzm~P#O&xxzr3| z0I@6X@jYU>B`ZNUGx+o@^4+<_VZY;tN2x20Sap7uiwa-wU;@*m%m81Y%nT|*(S{m# zhzoLw4=2mKh7+5J(`HJ8PN(eD-1;Ska3~tZ{NmoCN4>U!?hX%#-z|zq4seqV4b&C1 z-TMvftt?oxF9NJhn%8pE=}Vh3<9LFp8%d=~S|?xJQAD0NTB&SYgyeyNUpzB7QpEQw zCoA<@!k~EI51XQelw$D@kmcAvSZ&!-`)|4kH0&``3;yseJfMd{Kf*!19s^ygI%GX{ z0LuP zNB7nV-HhL)TrcYXt#S14pur&dE?EFQMrRF;3^vH2oPh|rAPp!QJz5>g7?NJ@%k}N#MATB2>0L zsdTc00%gH(%BDuiTCOs(WV#}n7w!Z@T1i7Sp=%3WimEY7K}(}-lDrgh8vJrE+RFh; z=-a>QNyjRue^#a6O5MbvuJCP(N3Erya-@xeaHy`Mzfeq_A$A8=5^urLYvRie!&QpL zRz^5p#$F7`i?aQ`K#@84tp-^{1dy6pP5dJmhdZomCVwhbJF zVduaP%0r;ESIE4K5WJicI84m2c~LSNZV0$f4Yet$Nc0TyMxnGpb|()6Q!_p5H-3(z z35%!a^D&*Tin4N|iaI5l3U!&fsy=$`U@oi>CnLQxImS#`AvKv0Ua=o^FO0|L>UHD+PScey&}l;*}(!;7;wDW#fG7_Us#xMj6kHPvQ>bOuJye;G0*U z67LB>GBH7z6A_w3V$Px3=KI}k?(Ha{)cYeuz!ofL-neU!GNVUrQebFb;eKd!CEK;? zc6$w5H{!~1%4ar4u&09zr_eC+GEdDRnOw){vixk!=8U~!mK%MWvz&aCLg`nc zi6cX#1^QTAJ~_j~_KXc4xpcdBykG(Eq_Ao98LZ^rZ*~?E3XBOW=^vdV`TtQO_FwGR z|Jo@3+9!jW&|XR^OMGN9Z*vd8ML%;Jjp0^;ljoArO7rU_o4^xABCyTx<{;(@NhuVE z1R&Xj2fVSL^YcStOZlmYTCevR&muW(_ODLZY1uiao+l?buDc4YJ=%S9?nmkVzF+bD zeeXHma=qqx=Dk_{!_$fY6n>jS0d&j{V+2&FUP^pu2EwIdJFL5Y3jzL2zEZB2;g3Ms zs@Atn`I6{QNAXhc-=lVe3jU1?v4H>0-N8ro((YqJ@lxvJqj*X7x21ad)^<_8=wQKy zcw+hhY7=&Sf;VBUVS9{Mrf=8*4Y1oq0<0ScKwMhqzRazC!0^)c{G6uqyW8;c`VF9O zb$NV*0fPd#uT24GI@5Mk09;s23_L@(s9n%tI*e{&er%!g0F91SU=F(iP={Sk$jHD7 z%xeACpuC`6mtZc;D}&YyeJTJCYz#ej-wiQ93097Y8*qaIxQFdA{Kle4f>SVkrf(<# z_?TOIFUSB**lYUE@f%)12rNE>*TfAppak}h{!8X=4eTGom+)O6z&56j-U}pv5A!Qz z_YdZmh;Of+ObD=@CZ|#V@+mz=x9JN5ZsrXhAZK15ihJya6kvKv56deaLj6LDE85E% zd1Ec&OJVD`SAhq@1hWy0xLtR&+TvdoI%!!>aI} zJ{(ln9VgUlz`k-PzMSC=oE+wyD zgo+mu3u?~MQg&Z~vKPEq=?nMsok(WI-h1lkj`FQgvDXN^_rT^GMvTfW(?!YNv0Ht( z?hY(!&wv+dPd_DU&k(Zm2LyWA3(b`BEl@F&eAIO<|6tF)KBZ3#k;(_~am5RQR@q)m zYkYCjIJ)}bU2wqePgAS_83o**ig$NZh!DqzYE?ItcPQXIdF}b&O;pUb7dHocB6Z@w zl*I-ym2nk$$zf*Yk6X`$Fx6Sjdd1U_-NwXiGYE30bcp@4vTp&)0JWF~*cw zE{D}dx~5#c1QS0N7Mg~0&qsKc5qE7JI%FM)-zo*$2hRw<-?6oCtlMyD&Ye8qy`hkf zc_y6N#HBoGD5Q6X$Xou#e_Lz$&7lx$j&HlmgZd~hkZm{opuC@(9Qa@mkHE55O+u4c zjhX;&Cx$Cb!T}`3Bd2<@-tB&tNAc)x3Wpp1CHMR4{B3Jzy5;zRF~`Zcp1 zcaD&kJnQAP1%a{Ar})Wld6J>@D7O6RGLY$dr_qx$8w^3>ELKja;cZSUMj&F?#+N6?W{1sr$A*FfxF;zo34Al08ViplFA>N z*Lo3U2_C|qFay!TwNo(f%wUBT!@lMG;u+fu@)aUzjwy&*T|K7gVHAr$Oz6te_XWk% zbbkl!sH50~yRmS-#s*v5ke*2{`~)8s$d@w5%bCV70?R0=V_1otk(1Mg+ZvNpYmDQF z3oTj2wjs{gJcpj-C>TX9G_d_hz(5ZXVvzfVvWSnJQNjEt@@+ik$H#)iejm#qQ&sE4 z_2X$0c7`0(!Xh$FRt|cIsU4M56^jE?#)l8h9%VI67H8XpT80+ypwbc5b4v0_6A>cK z8V3ApsdI*$W#nQ)cXsyG+|*7zKk-oaJBY%XS_awd%K$!{=!W8k4aJoeg?US&Q*&ao zrX-gpX^SSQYja|g=GYod9S|;&nZpaAKGvg8X6qQKXIY9)yo0=aD2rU>GIzIdFzFxa zxV+nv7K3F$-cKC~K+SDL_#MDP@ffnlhqic^0q-cJtbMOUUFkB9)cUJ7rRV4R=Wgz_ zSJ0Q;05U8imGrV09h~(aCcCtXcxh)1D_MC<{*lTb!~OS7i*;#zwXTQxsbqpkfDW{5 z4CsN#(dt@z{ZuSmJ>I7EV3H(hhor=$SE=IJ6Szx*@~TI<#gk8C;@_p>WzWKG-e(v* zi`bUa7VyMmLrI|%o!qbNGR`2g9Ngs#CH##-X4wj24Vh_2G*J4|A-3s_dgt7Im~rjJ z5Hu(m4Qmx?V}@KlFNG-$xfIR4d`ug{j?JK!dMVQd3pocO>t$vNj?6=#f7Cr^KJgs%opff;tRPN^;wQ#$!>j0Ae!WHYtns&0=}xa1OpQ1dguGMY8ksl*ag?p!k%s5 zFaxmiAtLLreph8(JQ-H*qy&}wigSlwUK-g7VObfEYVfKF+;WBZBL_A&ORUe zot=bxlhyt|wlMssQ6_5!#oreYp01e*+g_$er-C#a#`*Dy2Nqh%gXv`)T-zA!u zMK`MBE9R7y1YRXibBfpNB+w!;=M6z7byXc}znTXk;`mTkF7#OaA)0*UyXc$7=zEDY&!bs+HC{gclhb6uqKNPt~C{|dRR3F(mv=a>9+Y$<+(B`>kUpI(PD z-3sEX>H#CUP6qpK?zkOit8SfqN_Gh^-G-tkHdi4X>bPm4o`pI}bMLH(AcIssS;9Zj zkMVZu4tth3)RDr%`91+cU>3^xyx~RKY%%`*syE{o2BW$%N0P|HWD07ASrWLQtZ$$@ zdSvyLRrG3me~}>5K5&%QJn*H(O{R#BzJ$#%g|70eKY$Kqr!S?lzn`yvcZDY~%w6Q| zuY-x|J7QCQzPg5~FsJJJCzSHQvYP9j2=!#Vp+O^oTNo|CLd6siMK8Rg^Jzv|ILor)+J<+KS0AY{0@>|}+ zD1aOPTyWqlN^#Jw8gE;sNp7+say4?c8S=CouWAEVwtB3x3?dr0Zc{Cp)ah9}YpB%J z%x*^yUOwPd@}9^YFeh07{dP)73SeuQ`%wo`7g&zrcIY*JWnMR8wb~%yWEZhpd+K>4 z_ahyeqq>-C43(mdOw_mZxjQMzSq2p}@1RdL`l*@$yXLbS9%$4Xt+wrW(bncH!pyJj zfc&uP3&}K3TLEeCFgO)g5*q>=&bBakm(*fq4J6}B3T_wbY06$LZffve!9M?IarS#hg>J2>S&)a2?4@(+zmE=>0 zk_#a5faQi5*1tr2FE3Cyh=L)%LAac@?|khH*-3{mD@9^BS}cQ;){#tY6M0RK1!ORc zW0dN{Tf_UA*hbB9a?%N`s%3V8R=0j_2ZrjxX})UL;RlVN2HPcperPv7POj#VEB(*z z5d9la>kP==zQ2T93z=pfacPV^WF0}wj&`%|Xzf}!E30jgFYNe@rB^Xw?II?d0;}_m z8}FQ|U#x9+^RZ!a3Wp8I&x=S7(Oe0qThBiWr(MxiOHc}DmXx(tc%eRjD8~Le;@2K2;v0zP>>_2OG08|vj6;~D*?)c?+&9cjUV98}gZC>vwX z{)_)}7vz&FkXB!V?D?AORmzDBo>26l>E1%q5OMWSpP#=|1NCRxiP-lopgVz2!iuGc zSzz_l%2h_CWKwi@(!}2xL0KGRnFLHU^_p>U6Zd)k<+bk)H>o_a`?7kf8ye?mN^*8N ztE-bA;UNYcqs`&mt7P$PU(a?w0mAgbEN6@({4~tB#;$jn0tV6(1lteq_A*FxpauO5 zkuQsgm+zR%8sZFb`%3yS)OMLFg^O}7GdfZsgW{kBb9Q7FM$ESEfCHCVj14d#%;3)E zQ02_=yJ4<17BndU@TJUj@#omdXwQVb`WQ zppA5B=IPUv7Dy8nIr7rlsp)zw$4+^K<E4=&h6sm-GpguRp^ zel<3VKRGwtt6%{|jnsQae{uRzdl@$rS(!5V;9LkVh25 zhOh?`AwfYsWC+U5Bm6yTeB=+B(#DREV=QXIJ2tjufv-^!<6WytyfUWMq>7;SNY=FW z?qKh~I#8?X{d(y*{gt)7WxDM+#q)fti+~4G+GmQ_dEN(!=XKI2hUazBM~3H>4vFsX zZdVZe4OF=VdjywYVqv&2UKl{R=jD$5K+Wi4ZeEk}PSSC!4a-Z;i_>QV=mdMizGJ3i zYGAHmWb3;n=Vk5k^E%W93E{x0qVBL?gz*493M3w`b`Yd+T1PGNAz4C=v~=!9~=X{Mq){i@g9#hqFp3iYE} zBf?q9V{X|ngnyQIfGGQFt{$i}S$}&OBp}&bQFkF6yZ`_wE8H^0>MK|<9K^PYD}YIU zrHYoKwD}XdIrt?n9vKgSC#3#m$;SF7l%|g`{5UVl-RHMnAS4Qk-15%!7#Il|nTi+_ z98hI{4jWUF8nqgg$+29lyby`OeQ3Q3{Vml+*f`1Yv~fYz_?xwY`2+j6tc$$-Snj7< zUwNP&)41DA_)xquT=vnC`^{zhMcOow`ylu+nSemWF-t$*98V0vJ=JR6Uy)VWc1ygu zeiMXIGX9KQPDfLtw!?N2}{I>?QxRBj7~IaF z%h}Y~IIJtCz3)aH@a{HU-iIP5F#gTvB6kJgJQfjpAy5(Nr^_T9$bH?M%=(wJvbp_E~7b{fFG{c15^>Gf7*{TUD zBS&D}e$P@C|t5e0*?3qEd1&&Y^MxMF~5~qldEt?S3B#tbb z64b!j3&@$cvqcqfCsf}3DHrPADO{J7a#PR)!A9l&z&U(K zR8wB+ppqY}nwy1w(S}NlCZOBF${@Up9jgYycD0owdM>fliPnO7VnQqL$yNVEJzw13 zQNu5g{KS1c!#7j?OU)VhY(FAVm@#D~Yvh_+k|n4bZ)Q7Py?)~pwP9QLu;nW6sZ~_{ zu0A)fz~Sc$^9|Fr_3#TMRV?79@dy1-!KBObciJN%8}TS%e)Sv62Hhw30c&B-1)Vmv z8GKhKb<`b+mST^j4Z$WP&A~Ybd9GuZSAOD2{9mlSRd8Hem!)elGqc6a%oZ~KUKG*tE*2{MRY{Qe#qCk_J}p+ockMa0%c@-`2)~w zbyczz-wj!@A}zMhP-xUiR8)e*qh2t4QXa@;-;w^(e#O`#1k_ z{TIO-Qw!_2m1hdl#FwG&9tee>O<*_3rOFJYBC5a=fM^k&yr&g#z?}MAk)=fzt97K! zG{E?KdK;Z?wW^%CP1@?WfI@2tadG?8*P_<@1zo+`eD)3Vob!zts`tBt17Y^Zx5xhD ze80=QZr-ors{<>o$}>zi4p*`W!4h2}MyRyg+zB8X^vx z|JR}{5E) z=>8sGYjrm~hjo)tL4HyeZ-^vGG;OCD8A~74|h-LgW z>o9%7xLmkq>#GN05gJF@d1GpV`GG_2r0T+#D8=j*F?H+m)aDDa;)i;WpEW zRQ7(&*{idR3OC!N^l30R-$~nTM5ilA&S!n^@7##WPSUi^mz-%VbUd}7FhyHwA-9N2 zN?foB%Z_6l&Lm?aB>%?mnfgYV9HgQC-B#SpH^!nS5)!F6k9J|YtqnOs#zMg|wj?(x zYsH@Bv<6ICae00#Qj6hNvZ?^D?1nAQ8ZGY60-vbV&Z^&|(|FhD!RoGWadADL4%THw zm-MBmD78XLT_?Iw4&JIn>&$w7I19Q#DVfew5}|pGPd=OaTfM5$i!)?5q9+0RzF5zd zOAwlO^&2f?-TEcB7w3JCVy70AQoP130@`+F(G^b@h{z+p^h^sjRc%(`QPOaNzMmqW&?(Rb7-2Ty8TYuK8u(Y2<<@B2x*D%ZKAd* zTlf4#b}M%N`Jmi>_VbhDyDUjX3(*hySohDCiG?ClFe|`|CQDQU=!~YAKh%?s^QsD^ zKR@fU6C8FQyNk5`S>4Ery8W|*i}iaj$UV{~n^tA1l*20+k2Npc-J!l+ z3;-V9HyW;BIjp_Auxcu<^=tSfLZf*H4GCXPK5(Hc!zrGh`%I)DX`$G2_a)BPzH{Mp zzTQ_5^Gp#Xs#V+5x%m~Y{8y;wYa5mce#>i-d7Yt_3I;L&6VrK$&QFEon^TFN=i}CX zyz_cwx_XQH%*Pr|>zz|U^;(!MP$TW`fYiK&pTNd^t&W+sP5BEc`>NyV)}#%(U_yW( zmW+~wk&?xt+Qyn%+MW9Sy*jnvN`N4zjFLk87(w*rg*XdYk+`kvN#jI0CSIQVU?du) zy(A5DUqc!Hlsi>~1O2jj80}!E3I8`eY(IxCJ{X&$K24xaLo63miLS%gQMmSWmXqfl z&3$I(W(Som-wwj1*|BU6v>^Gf%inVDFapJ!D$`GUG9})tV*^*a6o}$&(iq!w_(KSM zCMAb$P7v`gTXlkMRj>sea#@uKejc?tT+&K9mGYNI63K|G;+{6tSWbCjONid9>)rW{ zD^#M4M!L1Cd3brlmG*F5NSzEWUwl|p` zm9>=evQ8l;6JeJSmmjwI5CyQ;w=FeKN%9HuS9SH!scY$(+}R*#&D4Hd@>%2cyg@cl zT~Sr5($a)^ZC^{*N}EH_Sc0CYc-VzI=HoKTqNNxs=V!JksCf;oKTb-A#u^WYp>y2} zm3&|!h8*upZ&0E*KMf0jMCp8J9DY?1INvmb^Aj-n_;(^q@Ng3fS4L169!j{loz@(y zi8uJ2rXz+J^DUDlgJ303{2i-KR=FZMY_e~llnHa2(K*4i;&b%{MrHpfiki7xccDzv znD_=L`P}K_Mwxu>qT(4w@v5iu@$?f-WlS{>qMpK*ZK9UgAd`7`Y_dWlGhK_kX_ROu8}DRgO7Z#2Cc9b^2)V%BYj;n^^x#yzY6B}_pQ6{i;dZ8c)ARR2a+l6dLX6DbFmPM9+@$ zu*UFIyYgkk@=23|g8j-oGRank(DdnvNmI%!#z#;nKt8IA9OtNPtVYF02d=@s@!~VU z3Mkt=aiVx7_+=>7q1}PYWz$?v{&@JTcL`dALpH5%O>9vwG?#M9R`WYd5%xkbtb+t& zkWqiEik2VDwEw7fr&s%9tlUx=yOJ0om_?F5s+izMHcf&Fo-i0Th2{oCM<3vJdz#=#!U&YMWuVJ? zYM=JK?iujQ@_zmG_&BN)1HwFZd|Lj=qT5S6au!FU;WXrbZ@FCEZd~R|&!qY|pzZ|3 zzDKs#cq~@iF>oghkQZ9e>)D~bUI^Q*{>*6vPkizY>m;M@E4KG(6p9KfZ+Es)i7vY> zk6IA}Z^emBATChn+UId-N=^1MC5i(VY4BGAn zoG^OMWDZi%COAWH6Im~9XiI)MuhmH58HAv(!js;7go~JdMZG8NR{~aSR^{*auVYG| z7<=kK8c6vArNq4;E920;^@u^0JYMiAc3g0Vi50%o<=LqO+2|VCcBam_|CHqG(b{le7*^2;rWtACr?1Vr z&+x<@_ARO!Uv?EKtg+?saRIA=YF&VQ!cF#In=xhuA6n(Bss&{!Exnd=OKpzY?Uv~B zyd2L3`(w*)Yfsl^HIBS(_d11I<_j1`81R??vl*%9nZ6~ezLQOwR`8!eN`bOzuM``) z$x@aS4U;7V?JM?Pd=(c4oAoYo3o1Y9R6K<_TWk&<1QDjG&1$T1b65H=7Qdt zPd24EYFl2Is=+Z($MajG2f(So_Q}c#Gn8F=B6P+qX6bXU+zbzUMt$;bA!bo{h48wdvI6brwVqK+$)x%y9M>s%^q zjh3c6!+7uE?UD^cMj8DpLNiB zSqS=YDm5|eO@9R%re)-gh0sSd+bnst+tPiQGRa|n%d;~s^vl9;)~|(a47ya-j5Fy~ zcRw6xrjmC^BJ}Svjp{JYXCdB{q5a3t$*l;VmXS7#az|EHzM0xmLZY>DLCBaVsTvr1 z;E5NZWLeJ`8DpJ}s7Vw7zHPj7CobXOa>J-)D&dbts}jMsq#pI{d#bNE+B-!L{~&28 zw1pf|`pZrI-q?^-{ivbXKk^Fy3pe$@*YNMRIqu&wTl9qBp`eD9mi5fwskppZs8Er! zos&+$NC1*|U5C`~tz|hx4Nqu(s$t z)tA-JZ9mgkD^rwGl&UNziWEh{Yf`4dx|i&Bw=<5hIjp%=|??WC&-5Y1ckBl9yIFnJK(J7Lo;K82tkD4zfi1cGE!}0w& zI>YvQHW;|C*Yr&p&~8f!S5`&&UCN|O0q!C%L8#P+c|&8S;cYimO{Kyaf-agjrbw)( zEdx8YKOWuC3Y#SY%P1O0Sam=uJr)2JD|(V;DMO<#cY5T3RmPcH8HJJgrX z*+3hL;OeiU)jeAN(*7u#$3GVBzfj=+t!RJO>;nqpO|XKNZ#9)pE(}9G+<8txr4q}y zm2}7t0~en#JQxqb3da@Jl&<*@(n9a4MOxI-a##Eq;MUgCGOt>QN@z{nYhCmh@crY* zXkqEGo-TE>`Mf#z{jq<=@6U8j7Pr%`-h`6S{dLJFRP>CDK?TFc5&Ad7#ufTDpAqEx z^;4|wo_&G(^$RS&o_+uNlV8!jh6~+@>4RlcUxwuTl_8yeiLk~#4?xU)?ug&R5~2y_ zeobUk{2G9Dtxv)bQ39EPsxRrxI|wO~`B}6OT^x=It{j#lgdVPrED~)P&K*4TvxqZi zu#7Rl+_V&L%F*yoe1lh|JP5xj zBq-mcqp_|bXf=n{O0}U1(u(2xC`W8Rcr13Xt3%9X+A!Y}O$CwEKfo-N!Tr*WU_#_0 zl27P^US;c$!fD0WePkn+Pa&8d9Lg}l>HGTll@WSn)*rMPw9f%?*DxW%vI~%p(hG?F zX=JbkDaBBZDP-bJwbvH3n;?Ew_kqWwaOR1uSg zQD?a11NC8p()P)oiZP}3tW*iZjpSV5-Xx=0pYkyeoVzhIRGpbZU(0x)`%yoE`~Qyg z+$P~N1t^lBH2 zw1Y`uwRn7<4wIfB3f9!o%e_Kk6{X4~;MvOf`T(2#iaM34?V!sRi;F)acfRVyF+ft` zu)|ijG(fsr)x)UgZ(aeIZWQZHE~|_{Qj(PqTy~;kCZ8y>qYzXg~5h_J!|msSSPRw>bCbN%^=Htj^l&IG<-xd&b^Z8cOZ-W7vsh z2npQ@^hU8v+PQb&oaJ##ToJ<~bxtO<+&Jk_D;>8c1SOW15bQ@V2(Z_b&PJqB5?H5- zl4_STX`H@|AsY~x)z7z3Zgr1-*rdZ5luYKG_}d0x9kP3(DL>^x`Y_+ETwQne0*C(W z{Ms;Dhn4Ejc_ehiV3t)nq8tmy-xl6s^7gshGm}I2@X9_n%DoiDIa*IvXM7HvH4w#~ zZQf}A9DQM6RX)N>bsOEKeysoP7=!dUASnH!#8v=J?K~u75N~H2By@ZtCU=ALY~Wb- z%-s)SM?H2uAe1z^ocyjGdHcs;?K~#BN-~PzJdQSFL>^xxXl2gm-#yU=}74SN(id!}BlKsiqI>G&r;VBHdFBcXG^>09N)5k&x`AM`AM0RwfD=NM!HLG z=@Qv2sn>=TNhxdTKqi18*^x`KnBrR-LUI0%e_&|>URamNw&~tEgLmGcdMFyVFyA}R zf3b1GNwhTHg05SEmedDW~#C z6OoxYOQ5!QcQ3P?*<2M&!9`46UDU$XM?2DE0!!y}~L ztB_SmKQc&CBv{2{V?QDvnRogLOZyAT#VCfjq zPhV5H)lJRk{Cz#kRwpa)CSsGn%`2Rm1*EmUv0vpGA&OyZ?P=uKQd{lN9Rj7XIeNbF z&E9PFn`c`~tGm?ZRs*hakf&PuCiG78g4WhR44Wp5ect2owAr_>6!&lk!!&fIvcCFJ zlq+bl=~wtO`Ley2UtlRP#n4hkd5xz}kEuW524mgH5j?!X2s;e^BD0@@r~rw|Kd3dF z34Rf#77W*pX8ghk&oOji6RfbVTF*`C%q;dWXl8RW+VZgY<$%L}ACu%OiraRe;uYzW zR(3a--hMh{xk+M4I?z443r|crww8IGhsz&79uv$;l6spL9*USziBdjM6`Q$23sU2~}uxqao+Oah#8P3xFNp743DQAFnrAaV{Cp4}E1J&odrA zn8RrzSc)7;hEmZ4eQBZ&&6~!!AwiP~<&5B;ZxD>HX!9xm1AWGGvnlXHGQJ02XqHhF zV$1;Own6b7(#ec$byQ`}qH2fm9=6+sd9N~b17Y0WmPASrI>9Y4&UxFdN$T{aMOVly zdxQK@007T@%zOE*^Em@3%%j)t?9m4<#AC#|dO1V*OtU?LD=@s_45#_+dD$d>6P6_0 z2S!cpmg9VX<9x@FdhC8cnAFXLeo**$Xmz;Q*QWNhte-vyW_!Fy zIP7a$m^8slHo;vj1Fq3`*-I?CVO^T8DIdc+KK2xK9Q1XRibe3X18|?lJVe5pKDeF{ zuaJtaB{j9fP6$m6pKK+d1h^5{4_a+6esuwGSNk$=*wtOVrC33)uL<~=Ri_L^jSK>UV+8qZyF(d`h@X;$^5@>F8rqq zO$QHm6H|K&W)W9c6CY6zOUsW)^MChRb05|iH=yL>_~ev7Oeso8o{^sBLhAT5@o9^U zc@S9`ZjCE(pc=y;;~RsPS_lK1 z5(O_0H^)TGZUY<`zmTeKs3egTODAoM=*EN;?EbB1)19bp9aBmm%^nxsLAg1L!{D#pdIzP!q2@ zmkGB}Ld#!Fl6{N9i5uD>3e;1}STs^GO2)BC#3KsG)!MmWZKmL#1x0Q=5r2zwX34Q) z74$C!9OXFjr-M(Q;Z~ioV@vk$IbmwiP$#~F|K+IcQWfciJ{%S9KRT-a6tVfAl;wZ9 zDa^67KqY*e;mS|JXxgxN_RtKHL!pYpCWNTqVU%G>4UF~>wOPaq-sOIo5Rg%M!!GF5 zvjH!jEIgk6TTyYCPJ~w6V~$*FN3(`eUd_AbNxe-u=Xl4xm#Zh4+$HI`HFp6)URrPA8BQz-TxV00R)2r$Z&K%Ri+K5I`uy z8zFr~5yJb*#xrxB_9+#(1+f<)4F3hHhy4>+hwzt`Cd3L{MfO~{0f|Hto(YyAX9S=g z?(X0<6Tpjod*b>pQc+LA2b74!{0S8pfhRO{tpZ43_0I({0lM)nO}xK?L;y-Gxfb4$ zAORo{F@eoL8pH}1!^=1JP6p9_ti}Ab>H^{gr~p_2b8J21*M5ivW={gZDa8J{YZyc$ z^Cxs*2Hx*PPy&QAs{r~}{ElAqqrn_{6QOMtI0O(O(2A}I|6}YZO34tehqpU<&GjMB z+f&Z*ARhoiS_7g`?VjCw<=)ThdicKJYAk<@HP#lIZSZ=Qw^&d=*5$wje7AGxkio~t zVsluRlIk5Wzq3Di(VdSfz&{QN)Ais7mNlT*VO}10+wEmQt`|0t0O4VrJBLEO8;9&% ziiUo6#)&X!dBft+_kd~9_9TI*dgMVS`lZ*f{qqevAk=i)SYt}D)*+fz+GUU)q<7j7 z?py7UNpAi8fDb(5k&bpX%R~>xv>XUL+(vVD;5+T~kP=q@@MoYefF3h{A0Ww6dX4yk zwL3)DUab1DURQiw+aV7!@J<@){y;fhnJoA@7=KKDtS-m6!cmN?hhQ3IK|0txQ2hXFayyc;7P)bXx=dERGxSYi?88&^g$`w_1Coe->z+*xWMX^pAfyyo3FXy z3xINH;DePeY`Bddq0Y)^@}teCg@GYc7kbcP&lo5VQ;RPiJFs$RYxjg}ingE*OXP@G zK>W|7>Gac)_pDSCWHlEotO{~neS}9!c7TQI!lXW0D@nzsI^iW($x9^h#hMaoiK4mr zT`CvmT1?-ZHmz^a#|AZXifn&mz5f#nWF?OZI2L$5f&s)C?}_)vGAT}7I- zZTZ2AZIlxQ`UYYQz%Q1=>(F9E=bbNQb^6O>sXL>}c#6wvd<|N{*|tRnPKg3uuEt0v zaya;*E&&Ru`dWRrNHmU70JqeVYL~q4j)Uc$$yVW66|?Jox?AT`Af26MY#6bSv3R_S zgjT99Q}q=wPKLzls@EJLRE@epfl*%Cn(U#foB~;+_ftgAipw}bZ<3oxR+D09vvqTO z_JLTpVA`VM)%d9C!*$DC-HmvmL$<*PEfIPAC@Jyrn=3zRx(xFv1THy+7vNts{j-jf+(j$dv+Ahp=B8w5d-njIFZ9g^1k5MfK zKM6d}h#qIlhP-|~e^K!HFkIneS)Tvev#ciUTy^$drj^zyp`KHbm}fZ3@P~``5zG*& zVsiLXJcZAqelD4m?-F8KDM*lREvqlPH{Wraii4HajVL3gZhh;Rg2iKHLtj0#odoV6 zmN-rD2 z0K)=Vl>({ErwFYR0X3fBR&*z& z`sC&_Rz;q7UA6=zZ)44Up|O)jX7AqA!S6e=ykdJB zFQ$7k^yYyh`!F$hAti|bXU~)D9MxSFEm_^>MHghC-!T;5VIPdyjd@PLH^p3WmjcT;+{CjAY@i0 zUR#%ok&crB5smQt>1R#nXP+etNnb@Exmd4{u1)GN!OwDlJ=ELDnUs0PFj*+Apg#wo z`R8e>OMTgNVLoS-4>00lEvABlR>-c%K-=m(G9A^kSM zqZA{VSNDZxDS6__ChYlvGc(fN3uZ}%=@>e5YK{Cs%kgk~%)^bx&HhbeSQfpOtT@Yn zx=ATDuF*O$smUfLRf~R6s$lmG5h%(M zaRT?pdo1-A_y}p>et3Mtj+V)D|i0yU(U3atP~ zCze3w0{rr57*rE2`S8@)K>qv8^f;>ry{J52RqFPc@zY(CCRs`wZ~}6{I^Mq2e2NR# zXw+i0_Hy@288-VOzqP!d<7X}D^WFQtqTKAK#%gYQHNCW(0=+$*Rjnl!Y7}g}J+v`$cvR%Ww$Q+Cm!HJX8x>U>e%+@pT|P8^zs z6aRgaRw2PPyaR247xRLn>3jY)#W5s}!TJGImYHN1`!Uo|Qx();9Gb%SW!7a$UQ+hH z2ZqTXt3_LU5iq zuF5t2)~0jwM$>ad=aHnyYp@o9|8Sq)_F9Q8;p3;%THkd}TB&nYss5Th_Juk4r{g1t zyuO#t#}(;qG3~r4_-?6{bGf0@zPa8Jb9FT}PSjoE4l43G)Ctkrq%7jWwU7FJF}n$C z<*=kdQngQLt)Q!V`)7v&mJuHc|2F^k@wUybThz*u94W(Bjh*8{d$-Qpy7KMQQct#u zmQgKTg`*nB8d{Q2aX4v(-2{3!;Qn2U@N$DGCN#wj#5Ib~kIhjP9-1VlP~nM~5*6=I z4rwQxsx`Ug_NZm1C|~~cBbav@!bEUK+~2Z^JESTJpQW4hC@)E~x>fdx0e_}R;Lf%g zlbuk8z8@z*B5k#z^O8MmXrSNMNcSoh>ymj?hzHPc=TRRmJiFH3=NQYz1N zbi}~Ek>bzOs*nU1z%CO|obB+Qxq6MuilSc;yTIY5XM7cU^0Kk#mfjBCM{SYiLM&9u zwO~xXT)K7;(MoZj8~7u8beUov5r*Z7*(S3r(uuQ4dFjYI1Vn$NeL@%vD@nEzu05 zp+8Dyb<6Bag7`%f95O0plDg!Ig_z1ib12T-uI(n)Nu&H2+B2EFEDj^_&~SDPgW-+n z8+f>+FGvPFCsr+e*dIwuzd0q3qnJfd_)D|x!#%MNb^_7wS)b~@^r{pK!38lXCrB@+ zTnjUKU_G7S6oz!!ztiY5Hvf@xb*P_CTQqGKzZi;Rx@UV`hd?zZx3!k&7v2B44=T}& z&JVb*VDbo;yBCD;gjBXo}z3XnaHDHP>vSZFg~0 zb#tK}`PXc~Bm^PZ*tFD?K8L-B}e$qK>QUit>gM!ic`$lQLu5-#qrDkc0=#N_1oNV_uj!_mqBm zEwCIELJ`J9WHlF?jnc%S_+!lVa^Q&*Fu>A+142q$QV>ziDd9q zPAoLE*UWf9o-kY^72*7OSKD?j_RZsKtKmePcoaXnh6c`Ojy!)#K@V50{j|TsB(2)3 zstb`nee&V>r*;Ixe>TGZ55CvN-u(aXN2IA6I4!YY3htgv%TiB{er25;)0SFvKsq?E z!9b_~7Pbsl9O}jG!G*FW=?G5OA1xS%0{3e=L3FzmD;p4&EHK>pz zJ&1M8Af7YckB~TGZedURcztjEhqWst;UjMw53#er4{vp7pedeOhpHr8436^13cRiM zH)E(Z`pN_R(5vS>6PgxS(dt%2GYbV4Y14%zHt(}s+zTq%$FeQU%!jvjL_5P{X@AsU z?4!@#8~kLmZGWz?#kO*2`0>gnMdV^Y&Pupg6DE@rTaRywm1!!!FaTM36Tmkcd3H5Z zp0%cc3FpR=SpOtM-^GgxU&O;Y1$~+=I{M|kUd^KD`^X~SRh}xu;coSj@q~&TmgzX8 ze%B`E5{(;{y6#2VqLm7lYTvr{V%DP}m5k3M^OD`GwzP;qNykx|bf~>rF_F<6AJCkA zvVu7~b5U(=;dA}*h%mPc*dvi0GJIrzx(e9O9kHL$nLc7htiYR~8<8kxm9MlOslRQe z_o34(yChb?Yfne|OWZ@k4{9<;=+ddb`bt6`;Ty61!C0y9n7+>9!gX8qC z^wbC?Fn^bOSuz7rnMi4vY(=?TvUV&GKcmCa;B@!;!zR<2_f##4*IVym&vX}e){#YXtAqEK zfT%95r9lBmKxwT#LK3OdE!?EM7F z7MPuK5*ZXGxf2bz~0lQjo8(QC( zUx(OYareyS%;lTcGX|Gh5?!2Y`x)C~iU{Qh4S(o2tX~H0II@2QxCvv@9SUaO3q<5F zg-cv*)EVTY&--DsEr&^;1lklQ1<(4FUzC}(&r|^kK@~BTxp{mo-~?+ z#5~A1VuPIG;y&LA2xW4d@T`^`xvSpho&=5x1eveqZB`ii~r4+eKJNw?47h+OO0BbDn=_2TaBMPe3L!&0jx3_e50& zLEKQmk&x!d|7OCuv$B3AHZWh7<|6CC(4Qb)3-{P2s6ts}12olw2(HD%1NruWHn|5bE zBK0BLktRQqnv{6p-$s`2X}*O+FJ`{Ns)%G;V7?N3K4$LyL%DCbDX@;nx6S~)PUvb+ z4E({aQU?@A0QEt;7e&0SxD+NhCli3QUCv+~UaAy#_F>oA)ICWA?V;9m^10s7Y%rbc zRH0p@eap4I!45w`Y#b|VLg~c!k<8PoDr>#tMZd{~1s->BFU&HOZeT-KeGdoUMd(a1 zr*r}9Dg(N~r}GH|1*(WXQH!p?+?nQw@eQmXX33KW<;MDEq9NxU`s5@2Dm4xhus~;( zd~g$N)*Z*9{(%!oVqC5i_>oMPJcBM(a6?}QcleTOHBZJ-$`vt!#6N;LR!m{>z>>o+ zf?~5scduYRw9GxkNO3m}x0$f8fuG;Yk*oXShYW%a4o@a}VGTg*nk=^A?-d$!8=rT> z6%BzC93{B2zx!A4)Ew&C65)sRsD5Bq|L;w6w*QIz__+UD{#32TjvB$oCx46+R5=#G z*9&yetz|YyIrFXRdf|)k5<(ns9$)g-aWW3M80xeRtTbZ=Rz!(+@`U}6Y^ zB&NQk;vlXhmOfAe>cCrN&|`M@>o;TYV=Q^HdgJJ3i8{7~kWaLF?f8c2J6Q8*jye7e z+#J+}%-rhwD{CGzix=Ox`dcOugU&0x2fY-!`LNe}$oEtoT`N!Uy2|VTW;*63d~KR` zPcO+MEpTfq+|&f+t<&2zZdR^KR_1tfGcIaL;}k%oa8OvSK8_TEk{L8n{p=DXI^mXw;2aYbHRDG7wJ(bXiwL@(1Rzpwnm}u zV~y3?5?X}Y9KDoF*6MRQC{_Qg8`H1lDpqLZ_XXZK@wU=xT0O8e$IYm&P;+GXkP10S zi+x2uqW(Gy&l6M}h^_p@9Po$Y-bU`x%EO6LT7R{{ea>%@ao6MCH;+*&%Uj?a=sTk~ z3|y!D2(`~C&hV%9Uk2YKct~z;JaN{*w<4RNod0Tja1ie4;J*jif~y?v$zk(_IlhB+ zF#&DHJTOJ}Ot2*=)RD}!2K8VYj1QsPUB0!Xm0j6mA~+)O2~vEC4jjLnJ#e%3TUNlG zx~yN~$~3X6F!#*!IV9J7yn=hlGDq(h*T6*?Vzn#>X~Lq%c_tCer$~244hKh!u^j_s z`#y3b@&3{9MCsEzWL1BX@0GC_=C+^v8gCy9&IZT_Drmj;%-lg3i2G{MAof_)@1w__ zKdkwp?UWCnBm4VFjw0BzMx-_IzKeDHW>(}ELKdhE7!{%z?IZRTe>DolHc6j=SW~`D zTZD+z!)4fx&^|H--UY;Pc!}seOGW-FFumgs>5W77Paq5|^mD}U#A6m;$14^vXe2gy zl!o?NbOL!ydRFq_TLdWAV$!PUoy=fN!2G5pyK&5CSDz~3u?tHnQ$%pFO&z9KXkYQ~ z*PDN3- zQ%K4K-k=~(RJ!UJ!u_0lNB4a{YQ?7P&#ANlxdqdO>f>pQ3jJ-_dqcp`af2jOV^WCN zqE@Ka!;SD?^Px*P7&WpF1=9O}ra)rWCjYkg{BiqnBG5qn9pC$N$~Wa`Rn++_my4d? zg}V(rBZY!ob|S*MjEZXFDMHRnRkKO&&$^4mYo#FwaN-jLl&a-KSkmRb8!ftENa+EH z4BqaTB??C@3iFGssjTOt+$`P$lHV^6exGn$*n_nWGGop7;Ds8E_iljL`M-! z@ldC-D9}%|PzMV`XcplqKG61~w2eJ9=@}!gx*~{kwB~Qg(WF1r%~^`~(VOT-S-ySH zl`3(^=;`X9emQ z?QrEr_cip4&Wx^`+?xWMb0;&eZi#^-=jL_V+(=vV3Ttf>Kv$yPO?%uat~F|bx%Hlf zmUFC2i*5dtYJgO}s;6Sdq`0FJTkbS*TsHZ8n&(SNBi%MA-{Oot|FscT7L18x?)JMH z0EWQi+R3rWxu5Urj zW_Y13q99C*&}R+TMFS9GZ1%DEg6oribJuh)W$<_*LzlwbkEL`}vf{-iQkF^#g%Zn5 z%~0(LcRO)&$6MPx{|+@LU;l-dFn`lG$E5}tg62*&n;N#^Y7zxV)cS5i2^^Yg{q|LE z58K1&!{z&I&`M{8med_>;F$GhT#lnh^jb&r5RqH6x(#af*iSGaR`bC^=;vQW?1)wg zD|3k9e-Mr%}z?lCf09ZC#?R%v%1V|C-|!?L{j&Qm;V)uab^FhZtvk#~d^ zoI|B%5^edWE=QB;jx+!L5lG9e@+2Z+9F0A&|Xg3qMS4Q3z%uJ7nEF> zK1bEn`ANeYZqg)Rn7e-2?09^1+8hAeQ*-xW@r8~B@Wq&B%HWdnk{x;CTJ!a+6;j=h z26Pbokb*tcE2%C-$21Y) z#shRnmM0}-m$7|s#cu-NAki%NnIXR&k`f9~(Lpytyp{-BjMeMls6Y z$)P0SrXfHLon-V6$|Q9LK7%dORHdfty07;sJiErK>Z~&%UKJ&{E;8Mr-s_^i$a{yE zrhgY3W9r(>G2&_Ct-TW_%KmB2&_dl}@KU@yX)1Hku5?XEtR@mDjxL<^fb=J?_wTIo zbsTH}<$ozcA5Q-N%z^w*&AOANxRb}nM!~;p(Mwb<^Iqx zR=Lx=ZgZW-Z?nDqewPym{e3~7_AIt#_tJPAM_KX4)CredriXuN5;{?%jkuYfU;)EF zWJu+6k&*}`Efib6hBYV`Hy;NWW6D$i8DNF!8Ym(m+(fsCtH~M?8_gQ~tt0wFfe{J{CV32d!c4Y78lisCuO zI_G8~&vgN#M}`VE?BSj{mSkGgifhdaPg295T|oXWf!G?D z-zOO_pCyB0PDv>DE^A)@HWh;(yCG=eG`EgG_UBScOqp$uWmH`CxN;SC`D#VA4R z#z_cRYzXdcfJVrbayf0F*FS9uC;}y#2XYEb@!!V+`vYDRfw$CQnQeropH;koV+!v@ zAcihG1jiHkI7V3__~maD3)a?DQL7l^rk^#kRp!8Y1r|rgg+@2b zo+IZyo$2~zrrbk0^3m(gt;WxB?ixOX=tUF5I&l?JxuU$RZ9#*{L1aNyPdc!hfT@dS z{mR-bs1T!UXI{=c15&_3zPwLV^mFb;OiU8wYl?r})#x^rLMZ{9Xm%YfTtS(rcwUktZ&HduK^Eha%xcZh)Jh{##^ z29cM(FA>rrYKL~>hfZX7{9!v0o>e4xv~dhG{BSOXQF&w&;$ZHLGogsG5JXpd3a0$- zgbq!+6=^Zz%h0nwq<@6G!J$uDD$=-TL^!Ww49TdKh4W~Tu`1pOGIHzTGbBtVB(4vL zC%mCXEm#XF;R-HXIcu`4Vf3PKic(puG0@r1RrvEtsvmLRhVq|KDe9J8R_G$A8Np5x zx-WGSwK$cOw7$+q?bwtCotJkj&W21ZZE^*m(OIyzT%;*I5$4akJ)y}wVCWd3>~2*5 z-4z<0et}~8&?Ad~ycDtj5A;aV-pR!MKgf~4mCmG;4OZx;sBVhFqQNkhm+cgKd zfOWSd6Sse3S-xph-cf@90rA%+e~Hj=Oq-ep)Fil8Tp3mxpwm{nPIrwkgWuLcndpGK zrF^5qc5T!Az?6gg;0l=}WE_gSRd>IZmU1P&`BUpNC^s;+G%_!Q-zc8g zG)4SNii9LZKNIf6#h}5w0{-II>r*R~^8Xz17w?ShPgoL?bNAd8RUJ<{oP-sjdL^4` z|NSC%>IeD6V?b+hR0UPnv>UGC>XW(eQtq-}u}&UI`6S@c%al;# z{PD~C1=srv^WLKoUy*c@yJ3H2ed3qMjRHHdwDY5T@;m125nXv8iU|4kNS}s|Ws4={ zHtcHn8S>DLtJxqP=rfABYsl2S-})Q{gnH$GeQx!S4;)p=)0*> zUxe@b`GIUG{(Ry=O{Om)3`RuF|5=7HloGbuoU0F{`gINQm$oS-sh#=&3KXSrfCd_E zI5}g1J#5?l;iEy&gsn%;(2YMyQ0MD6xAi4@$5mVN=baHww)9=GaVeQed-Gj^u|pWu zo5G%28?46M^hztGfPGH)j(KJk%St)-tQwa2+S9M+`)gt(Vp|d@g0YOD!Ts*EJ{igx z2cldrcBNQ<=%I{5ZzA^4t>}2-x!{KkI3R zCyI-j4Y1>s)en)RW-AVr8#tkhbP;U0Fy}dpEd_$&^{*4%C2HD6qubCG&rlCZp!oi^ zdyU-jlNy4dp@O49-8sC>kwJPIGAoirtM~*C>5E=r+fkd>k8yW21H_NJNqX@tdJe@4 zS_a|^U0E;$)~z}+=;Rh!O{|TM=6h-TkM4b!p6ADRh+aW_j2g~2-!-$hVb9C$ht^8x zjz}S+UjU)dPk#Z}S!Di04BG6{bWEs^+!M?&GWW?9QHPDoMZfla>D9|OAFh7%tZCEB zd2&QDyd9aY#o(CU^x}Y&rT~(8$3AeGVH*yr^MhKcEaqWDEQy?D6y*3oX9flfAa%F` zNiHnLoJG+U_NO5UW5&tEuzMJs&HUg1B)Y-C#%QJioM-94{JRKSBchlk^5MpXc(;Qh9DwsM_S~$7pBd;Cly_K-xm#oS}J2z69tHcu0368*ttrD*|nw>S_5A zFM&L*@r1aEr{!S7>U$H@#%*p(k}IyS%f+C1nALF##2kM%yBXH?F$Mnk#4DE!o-T}@ zE|?@^5HkKP;5G$g8FAXuv8w#HD{DYM5F<|=_^Fb)BogeLQmmr@q~h0mo*TVB1gcU5 z?E}NptA!7yKwKeT(eBqCX?aDm2l|;+xC?KJhTOF56-ND;MAk>FT6JnzdT*H$%xqIA z1P>4l#Yr>s)2(vE^=Ld4KL*s|a7Kx!Pnyp&l9*$gKodiK_6ZB9RYHK8zb2RcZfieh zMj)FRmqPrMs-ji`^L3~|Rou#;R@XgrE=7wPNwn*gY9e&(ZgetdaJb-0ibycdvvL&~ zdE!9k&J^!6IoVkWokCUM9C|)gK|?Pu=fl+chS%fVdHqb+(NAqMlh|Qet7vaKbE6u^ zFO2{h1al;;A3)3eI8zwKxONr7a4P)Dlxyn#`9SWQul}n5aY;Dt54{U$us$C`AL^fYnhA z*sFF(nNfFpsY7Bo_AvF2)8;Q{UTKBIavD!cP z_dLznsDIKeSYSjNnjpTJr_YvxDW1j^aBTgy)<7NP8)u`A)m$V=_=!QKh2@52$^xUy z>_n#DE^y}9mx?r!^0Kp6u^y_mgG%)UGVMcPg+PR=FLXDXRxB>J1r_Z>ISp5GO?FRO z(#BB$j~?lC?w+@np?xsKJ!B4uh4emDYksoKvYzC_@I>p(4s4}iwBp@7`#QcfWf%cC z$c+=aXxs#Qp5&YsN*K(j$E?|iR}YGaxK)T11k6c)xdhXwPPSCp*&MKZ=&DqVW9=S` zR+pZyAOoUnm7e-ql-gSL7ZuF(uNAdVSEaiuj^As0ogBQBid8C88jBZU7I!OkaAt;{ zaPsx$4>NzUSJ`=_uI`WP7s{H_J^*4=cf&k=%pA!4zyJ&lwIwUBFhDNF;_M~Q?PqThfvHG%?|yNy)3~*g7p|)R&x|4k7P~|%gAqI=0gYHz8$d>?iw=;~C_e?RQywl7pSr^;Ec8rzpeX6d$wF2A!4V z$6N8j2gW4@C->6Okzw{XU#~EMd(5DpaG@>h)XV~oP=eshJ;Ar{I^EIqvXg9Hh11%^ z^}t!JB496Hpk9DFPNg#Rbf>@T256TW|v*r&w2JBduDFkCpw%Lo_?~K5{X6VIFg&I;qa|>`| zug?rfBeJp>05@j006`{`iMFL2+D|^*)Ual0SItb#BWww6;57r>AGGh8MgP2~YpY1V zNVXY_MVdTg9}dSALZKaskoc7Oy2a}r8t+^`xWiSbTN4ANeDn)reo;r7jb2vRa^rX0I^l* zW#&z=g~XFk^CjFiSWAczQt71`lTbT2AQP8T>xm2aN=#g?Cn|_b0qOE6imCqj-F5b; ze&@8tS6-E;Ry)*Cr`wcJeM!Y-E+RLrVeI*Ub#`T0$EC&U*qFxRkQ>34HufTYkCe|U zI@Wc`#)rUeCY`#Td0w7LqmTaQ0=99{ptSL^hVi^Pw*niiXlhHH(E^uMR?M(^((_$K zl(ljys*UBkfo1^1hckw(dy;OG+zvX>9`Cy+Ub4)H`HtZaSgCbAmGERuHWp`5+Qt|B zOo*4RFquwD;~E;=Bz4v~m9mnwXEX~$SVyJ{r}ep2HV|Q*Vz0rXjzu#Ag?wtZQfXiQ zH#X%d8I@miY^e=B7J#XhxyWbzmgySi#=M)Y*v`lU1viCX-ZECDX&a>T6)b7Q++*MF z(7w~x&DW@@e+2QjpW2DBSYAZw81>6~CbfexS)7RhdEQmo!(|O;v6n9DS6DMr;N;Wr z|H56}h=xw#RC0lm-^dFGE!e>5&@VY-X}J4o%us1JBg@r)n6zS9Tk4R`hPQLqtQs7W zb(e%!iO=TWh|HfeyS~X3C?yy53CJdjz&&)6@Uv#?CaaIX ziJ{i9o|t(0Oqjf>-%9n`*Efl<)%s#)6p9awd|o{H{v?(42!Dw&{fW>##2Z9kSSt-` zERLt!hbBXiYNA7*@v!f0|1)6*o;A7AkgEEMm^kkNg+8BT7Of|E{v%2}`Bb0{vNL*} zFsf@nxpElY_Ob}I6NFr-f8p^BPmmj=qCa2H?)$Qc56@CMgn$sxS7HhHU0y?QF3Gsr z{V)g*9+-uoiy6H**{!@jcj-!$kP__(a$=OW&TL^@xoxlxBf2O-_T;0dHg+e%X|e0h z@`SMCk-Qg@*S4RLZSdQ=w*`s;4Cha%1{p1OlMXPgKT)~u0h6()A7dV&BXc^GZ$O73 z@!o~Sv38QWwv}6niNFCbLpr>g)D_SvF8WpMHTku*@DjW^54H+qHzba0JnJ_8f*(3# zF%y%Heeb>6FX+DoP||z9{O{rX(%%Z8|4j;{WNKsom-+e!`54|9sN>6PL<(5V)(6SV z{4t%E41b*>o`YQYFU*%vW*C%T6sL$+&9kbq$t$v_(&YLnF-^el9O%5|aP7kLFzaHY zW9{Q*74K8VKm&vh#I8Ap65#3u1M;9HMpfXII|^aeP9FlEtT3X0mLPf@5|Fq!#EwKL zTGZ9H887!}@Gg>|t03Ve6QiuYuqc#?QHGGlAcm-sRzE1_uDp@f&bblHXi7H)rfSa5 zls)S#p@SG?1c~{vpmhBvkXz`v=+qj#VHB?dD)cVV%%@)7p14yweX# z);L-o(Kg7T=>^ZvO84V4ac7qfaN5k&`QD*$uX^2>c=+G8VylN_4}Io!41qvedA;2F z^an(pzH{8PM}7KX$o3^u26OFr=Rs(W9ef=S1DUld6r0I6Sr`f%*Op3gjM-zuQDI&v z$%d?(o05tMk=9Y(k$m3%)rKq+mTXy^XP-n;ghgdRseKTp*J3W?pmk6(kLVP)K8I!e zmZqI)$YM;xD5XoKToNDCw?@h*13|!V9k#Df43fc_;0$E{Az|=&8Td-6NF{rEjYO^` z!P>m?rP-=|-?RZs^R|%P6&rqcWfg+8E!vjJKn*2y?(85~Mv=K3)Wd1xzWTN(^p^d* z&JWu6!Kw1_F3zlRz-4Ys9=060ly-(*@Smhen68h2dbn9oHfT2!vus3t?0#$yQh213T zlw!zu`azhm&rCqaf_9RP?W;_{Uc0t9=u8YLho6G8uQSK0owDY6;%d=EBO5uAH@gG! zfrMPKkZ6UQZrbNdqPWLU_eV^kJ(sJYRXXPVheYLO+#Z+4^k-W#yY=Ybadbt!6V_$a z7P|Q(FW41T)GnL(BWlJKN4WBLq6F!dYG^wgKsO~sL^dgZ6EK%YVou}@#CWCV_wi`P z%K`5qisTcI0q@uDci?sLqfVxx$G1FHxSU+Vc1@hK1`_j{kC)_-6Rg-350V}%!y_QF zGHezyX-PhWyDGSBpHw12>@(a{W*iW#+Tsxw9k5Jd-`QDYA4v=kPmcjc ziPO;Y?psN@enOB)UH^IDOfR11Na=-y*>wxq6<89tEHKHcpSis()CWkS7kITu=%eo` zR&SxFz8(my#yC4o6qIfO8Oz6D+K|xokOXI(%^M9ZI{BJziS8KDs80gQj z*x5K~I(0ZgP6a|KNl}Vr+@QC3Vl<`lchj8RsbBq_%>h)}OQ$pI1&St~m%9VeKA-S7 zHMsVFtDqS>(csQ!_@|<*wl-u92$%`>)Q-blfC=;Pc{m-;_W8I6_q{3Ux6|oK@GFDb zP`qsPGXrIUGDeP>rb|jtn;_2Fazljy%x~EX<1PZ!zy_}U)O3^)2P6`AWGyfRC4{gJ zncMyJV5_!kgBI;1`pBWM1_L;vl3BERq_8&lRr=CtqEM=iNGtCq1vCUIBoS~R_AFAGZ81A`qUa3jABvA>Nc#xf@Y;=8d7V8 z{m5rXdF#Wd$GSx8zBz3*Hz&T$5?7v|}Tt z-}U^WqiwlHydM^@QT#9o`hw)1b@jY7hpL)MLMxD0^s6+5HQQzZy~RUYIW~uuU8ZTU zSVj-#%RGyM&^M*Sg$R~>V^;A*iwUey@+Q}HB5G?7nPj?@Y$TP4O=~PPHv_b$Wb7tl z^1J!rPlN~dbP$AvX4Byo0@^-&MBAq|rm=w5&n*rruN7vbdSBaqUEB}#phKKHG`!}) z6)at-Xb1MwS-XlRb|;9Tev@uVtq}EDQn~hRkWF?OhE!vuBR&24n8E()E2HIPEHh>! z{B1(@UFH(u*KfaE`VVi#j+y~0^jH)_AWj%Kp*8F2w!dm*BiD?HB)DOGgB8qEKqW*q zlfx%*;%V>)-0i?F%~}!NNmgl1N&R+m^vxx{cxr|7yzCpIdBP{2AYjCc^9^Caqic63KYV2W>*oVLpJV^YPuT^t{($NDJ?PA- z6w0z{Ge0Y`5EnHwP8L-KH1A%t^Ayb^qNq2)?N_qK>D-H}IiaRc_bkfyu+K|P?0ud2 zz;gI_`V&f^VZK>a6FlGBzdP!oU$=jvnz<2@_fx(j$h)3Id_L>zjF!Deu08#1UqE(6 zI}x-*5f^vli)&_q=byfQp@)=8Z_8}RQ=v))7;(AZ5MO)0u|qmh(ZRHizOk19Qe|j( z+h4+Yg4aP=+1XSoV*6zBtn|xt7lN3PXix#ucyFj)el71uM5c)LKWLhcsykACiAR)B zTcUdpK8o_0S)Gyb^v1elBwd*leftx4Py(c~k$7YWzRURD!k~vH)PmVQzOy zkw!PXa11<++R6+nYI_MoP09>uBr#e?wso-cNp=3q_wTxes;oMXz^@8}Osi=rj)7MC ztPaM;ha49i9?tgO@6YeR-LzQ_mUi4bKOHO`P}sx1kVN4`F$ND@r5GgdLJlcKnV8Y9 z$r6QY4z4j6Gx)cujei4{!aUXE@%Jic(372f`$_+5G zC@jEXUulTqu_NWttxvS%#4|L4&>0sA3r_YItCaEE6<2MYaj-m@n$3n))jJv@kJS61 zQsW_AGQ&twD#PL|CF+puRV0a(R2y4Hk2(RN!$bpgecAEcl8H>7gTY&gFl%Lo5^Z{C z8*if`xrAB^g(j0&hAa=Bccj%Mo3&3$){9LGSR*#_2qE^hU@}=hQg1hFFw2FtWV4kU zR1}+G3j1*nzh`{nJAxc3&Oq_0=sOZ}iQ}A0KvS;JSM1ydLpI!;vvvWxYPvS`o73bu zlZhMSYBh#AQC(jhMX`_saoT>!<4UPZyz~mP=yV`BiK39cpR6qvY9ivA#THjUPR~08 zW9i1!xY)*B4zAbCV-WH8Vgci*tz!lbPa(533aE$`4l~vvuY{>a0KYJBgVCXdwRQMn zJjZtty){qHz)6Jyp7MdzrlZ>Sk6Uk(Qhy z9+Pw>)syXYfV@FMBc>ii7~sJ~y!~k^swEmuuiywdOG6YS3rZOjC_$OhQ{YDeL!s7V z07^$cI8{)@irG4+{4CG6z@pT2-ynS+bGU94L|~tM-TK4AZIyZ0WGSi94E0e=XKCjm z6a#`->->l#Ua6gqTt`9%B(M?ZErp9DHj{PZ3h-pqQXyD%Sf+rUBP3Y{Xj5{`hj^YG z4G_|mb3}R9o;n@E3N^K2E@hcDYG+NoO=XD3Pro(Q2 zM-xzj&!Y-5YA%DZ(jiu&mT^OZ8OL`1&QxCv8{S2Ux8Gbx6M(Tt({{S|Bqd}?*rtv| zWOfrkpreY8^UdC3lP8Bc+~;yLu^&gL!02?vzQeq<87)j|$z$F?49jGS=Z66sV?)?5 z!Ud7{YNYko7YM+g(fNg`16+*4NXp46kLB2u$rDdI0=A)?W@j35oD<_zlY7%^VY7@F zf8I=e$0=s>Vg;caf9~CSshrTx&m$cAT)Vmi*?GH6XFv1|^L0+y3>hq6BM|wB4vavy zd=Q3|Zl53Vamd;g2)wV3pSYt6rlyfTA%dM_g8()>c-Kr`3XL}(2F-=2QIJbJr4`ljXx z=Jl0%C;!&diCp~|xXj}?)W%TO?m_C)N5BGv;>RC5Vu#8f>%Ifx441Ks%C(e&B`4PhRZMR<0gA>Zd_s6EhAcTH5g{W#h0~(kauS};+ zdkpl<4Fw~FylTK3akW-Wj!ab0IbBTj#?@9YTD}+I=_aG^W zg*`LcHFHrgUq{R%No89_LfGcy;?ZfzY#B|QQE4HPy9pw^p)XA_s)=Y*L!;*B4IQ;q zStZxHTOWOnG1hAQj}^ii0;kbBGm3_J4<&5mcYX8AQog0SN6b6?-LBW1oFAWa>#cv? zg9$Y`)G7g5CP*-u>4DrPN6QN`@1>q;`wLkJ`Y|rhMpQ$(zx0vWf5u=)V1J!4JtzKw zy~bS|O)`t_Z7X4=0>x#Fjthnak z?3dZzFG$DLA$PMnI3^f2uDnr>$f`=iWDJzHDF{i_aq^o?AuJYxG`zhW-L4c=W^Z@X z#;I<6IX`Cw$0IQ;k*PQUr0najJ&n*NC8o(7!0RR zF~S1C+Cd~RW@a3OR#b&r+e;N3W|lBX zD3_j~41>>WCfGn^*pg!JJ#bT+a?9R)YBB4*Fr7UJ?yVF*VcD%{hTlI<7n(fp-(JmDNCf7T zuiOB=6G*_8dq&Mcw0M9hlq|<_45g&jHi3(&vD2}rX>SwGNMoQ1b~>okM+RgUIuq5N zqF2BnYsPrA$jIWd`&k3O>(uyZjx9%>_fCt(m(^sl#L$9X=t4w9zq5N_N62{zCSa?3dxk9jjwaqlFBdeIfHtid>$`N;{5Yv zvJ(3VS2Ut5k+Fu$9=?3w=&6%!f5*%@(ZZX$s^lfQvnu*Pg=icSR^c+r<@aUewbs)Y zqN`bq%SXLQmlKL!&7rB+BUH&NzHzOKisQ|1WFarJ@Ycf7fC|f?S9H>qlvTjU;z2O& zwvKH*AGy^E8O3XxOJsw*jgIS&gByZ z_#U-Mtb&ux0HI3bhLP-5s=U_P2B+IE)0+M~m5oHr2&EJ4)jnP$C`8)CXZoo2vE4 z1bxXebB#oVq;WU?D%g~zzG@OYR-MveQI3A^Hmw;)FMKW;t3F1~nIytHTNC-Ut-tGf z2JAz+pPzi&E*X_t_QXr?#3c7eb)if$j(rnh-r|_tE9q&@FUWpZO_mBI7f2}paa(cB zM!X)?+NUOOG}CfvT$kMlD3fifWwr0qD`et&vDBN-K094lIgN>ibNHj@_2e!`pR7I| zJq>}%pvE{9S-FoQ#l4TXw=f<{iJNNMNooncq?z5g%=-hmum#3|IQ9K#70F^7AO>6~ zV2Eps3h7XkjB9G{I5|^@8Seryk({BWo27T&HGb>J*hP+QPN+lnbS&VIz^F z;hzF>$)*@{{wqIv|Y@x5eSjR?G-&!9YJu5jZqV3!p14VX*)&az8)H@==3>=ZK~T=so;UWL?X zGN0-_`cn9qo#c7fc(obx%M`vidm|LL4Y(tK>@&?hUArBnE+dR~|L&2gkK?U^qa!^f zr+=pdGg^+L=c0V=?P{y@%W+d~7NoBBpD{Z+|XY%mTM(ZYWyg}2QGWax8 z{5XW{H~B%PQAN$xg#BC-PC)(NXhM48{psZdg&(R6>42D+ggfq1tv4@#3@M42T1p%o zv6}{y7iRcKR4qt1s|xp;ATDq8g^JBJ8K4!v67oFQLEw*EE4+{`nJ6uizpXUr`8(qeZ$wP?;QrNc1$! zg3wu7KiX$-axw_(0Uv%|!A-Umr5a-3#xsE2{`MuBVu;Apqj@VEaZm_`>yf<>dBqpP z`}q#&rSJmvfZ?oqw*T2Q5tNJ5CjuMYz=xPOkRdqRQU4($$KwPd3_G)sC~I~sY9sy; z);S`E%j-hpc8ui+=A5&>MA!6fu2YB25!9tUKYwF-bz|DD>Ig^_t7KB(_<2P>?snR| z?YLhKNv!#4JTz?Dh|I|nRt6B9IW&hJhM#C*$DGLBHl9+yw z+GdQ9pv%(VYWwW8l>%>AJnzqp#G8D#MOzz;D!+ZHrS0&2qU$hYHRCt5fx2M;35moM zF?ZsnbZ}{ zAt;@13Aqw7R`iZ#KW=k%{*jQ}#~iNxPDs!l00{}lKN1qX{U@@l%^kduKNAx5_*pmI zDK`>kM92EGBcpQ^5yutR$*R66$x8nEWRkuyhu~^MqEp3@AEJ9)Vst33-s5(+yGB)9 zHkj^nk=^%>KhhiC>#fe>T|_HPY|M9En^U0LG2q~+y#?3TVRXHfr{;W@!t^lDDTT^h z;wVsxc_4!hoVX@2S7w3+bd#fM*Ot))VXl+3?d@L^30Py-Wq#))Da^Dk5py!uXKmmp z23OJVk#V(s(Df18>1D~1sM#jM;VTURsQymTkC)-&GU9$qlI0ohwh z3_X#aN-q;6G$0@8C*e-M6zq))NWP1iBFP7HF`V0Jguz=edYmSm6>P$AH~e`NV~3mx zr5@BzPaT8^suV(=B3k7~rk^qt;AaGg0|g>-Sy2GD69amOt(;UNk4tMX!%s@BCh6;w zgjJPRxzFUAlY5;t=I7N`=A6RJiV&qRFy9kR79LFq-MJxyHO(at>o<-YbP~Wik``#3 zk~JIU$r|JUTuycYLEMd8E6N0oT53I=V(yejNRxb9Ju7x9 zP%%qQlbfnMe0s4o*QkYtI6pUBRCS(RF|!ClP8A$50H#xB@K6X_o*J@vN}jJ~bCo%=%5$5@Do$pks$W5cebuaijU6O4aKDCL?4q04JZ|6{p)Z#AP z%QL5sKTw6l%<$P$Ri18@%`*h-#_HIe1VYnDG8w$mL_YExv2&~?Wnu`Uk0`{g5~s_* z2yBGB2H5*1UEGdcril9Jjpx8g;mm%$Z3}z$>jh?;4k&Iu+0jQ+=%VL)R~cOCmnA%{ z?MTpo9gq9gHaHTQIoOoamjgXS4*%IjI8N-+N?4*cI>>8{Q~2aF(7t|VKb7lN*o!IJ zPU{g~wGgw%?6k!+e3bxM$)!l^P(^gK6;!R&Klsc;r`o^%zvuj|E*^7*S`at)e&FSKy}?&Z(wGkQt0UuASw@|W1fx>7xX*}X3Sb;F|agB zK<8J~w7IjA%ff3*`4;5X|frTCi4 zGA)&@=zUjT)e`qjV6#w(?U;Kcu1*K&_=V_DtI@DMC*#vqBPhlt@CRv$6@64JV_KPo z;lj{jPdht@$+shAm#lknmwm|tExPSpt;fvs#{7kAmsJHSmn?y7?S)co8IA5s5i!pn zCAEqBf}*~b=@mJy1;1Dt8^qp7rDX!X+`?Q33Gk3h!Una1xfz?GiNSY#zNR4Ls^cS= zpZ>@^aBTP)l-V#D;~H*9R0IeYk+;A5^nAkATN)TTc38tc*fH5$S*m)gVvvi2bxvFQ z6p3Ua#zfXUUHKOJ2pZAyT2Qv`5w<0ZQ;VKC@J33*{;R{m2R{S-hgv>iny~@E5jSEFH@d$pmm6xj70}V<5wPfsu#-(IP(B6oRhP zDr#6^rgrdkrf06Ge{8_xip@9Xt8|RXv$SC$)sgVbw%-ob z6T%RL#-jc0NM*LnW954i6SD(Zlg`>M3E3pTqMA!E)oTqi!dxFGrBbCBy+Zy1sALc_E7I(+}8hf>B+wIkVf@OD!SgHDE~sKhI(a<@s2;z z0aP69hrF2vy5n}^8expo5X`Hse>){^AtpSAcU^EyA>toCUHXNnGxG`N2?7W2D0Ks+w&Hw)?x((* z8zPa8aBt*gk54}G!}RZrG2^lk>MH)>aET9RGKOfF(<6NWqGDRE*;(g~ELKU}eXqJ0 zi%)d)9ubAl9nl5LQJXF64elQG?EUsA@n{b92=u|&5A6xsCISkOFCH);0H#H_XYkQ* zK_bP|i~K~mk}{pXafKgz@HiyOb5awAT`?TJchSE?M#PdoUmKN)LI92g06pk$H9gsX zVp#tis&}YEx*`vuz7HkQ5W#;#hUq^L^{x3#G>R_v$$-{B60)ekE}hiE%*ckYqTgJ^ zcjGv5!zG^CDy_(BrNAt`o`S(FeWp$3;-&NAMLfEm!tC&Dq&7BdKk<*tRg>qdA01vp zW9jNo+e3Js5d7j_?!yDUYktzv-i1ecPw$|ozY^}DS9l}dhqHE>K7_+{9o>f0b}gNv zYXTuOv-&b}v-sM7A)*Jm)FbzWB8F7_th`I!gA7rL-yF9k0U}4*9JWOPqKd2~L@$j% z5Q20B!r+JKr{H%2u>%4L6u-qTkk(E5^Up(7VOwS(a%8Sxf847A9IA4F$dbC`_B%vw z_uUfpLq}>4*%|<$BXPy-p@PtnxTN$`MQ#t;Y5=K%{zfYcF7HpKl8>aqQflkPV!XXP zpAwmj(B6*akR!1cuqnP4kuAyX*G7yxfJgiu>Oq3r=R*7*$q;(;*wF8D=|SOpAxUs) zMKN+ILgD#XfP(7Ufs*)GisB8pS4HWk{uqk#*;75t+#UZ4mBJX=2ChfTQ!ot6GieK3 zac&1(ajefvQOnOof&ni(S^u*&*<%FCU4PloLxCy&hW;Ai0FtLbSmAAykz?DJizIze zZ_!IlzfFl6q^>x9R9+GhgeJ+FwDR(v!vfR(#X_}U8;KfJ-FSVjPT~PLPu;Lg;bjTB zUqwsK+h>ZWgLs8Y{=tPy!7qhN(b-aVpZF4X@VyB{@VqHR_&O;D2pH*o@t{Hc6n2H_ z<08mpS|tXU%)miLuov&3v}T$sFEq{q7LXRlVw19(lVojBQ+&QVetXp5(MJyfw;H7v zOK27iokgUnY=9}SBr>C%uFVkd;H<1qY%W}k_(gVil;f?%kW>w$*;K#XtaICvSwD9p zvgWYp7~|nMHzRwbl2-nJxp<6g3Tzx;TE?ChD{g2>*l>$!COJN`Q!bDxURF%7oK#7I zVnZ{8ZK54Bus@JOD>DC-;x7c$IjBSFo{81`yb!;}Vqzq{Dt<&G7V*=xN&=n=%L8X0 zl-Yj8DKbz|QLS&71DGI5%@d2oZPso$y(4y6x^5a)ffH)KBjGA=4fqi9(BVRPPc|sS z$<1ca@r(8Ttee0_U3j8)7dS>9I;4fAw?UQ*feBK%aev0FCWnqdps1r#Q-t`a4pAki zpwG-)o~U71GT!chkgUYbzy!<%Rf61^V6^f12*%Zs4pr)w&`2P!gh#e@)B|TRb}SdR zE3QmeuOIyMN_N;qnsRJCTJa?lCvzl{>V}1D=Bu?kgW1|dyS{#4D*^gCL#v75R+V_l*RVaJA=bM z!G+nr$8^t3eX$vX-oy$hF|A8m@#3|8Y3f2m69y}zG3f%`uD28-XeaPFRgr>X^!YdH z{cK_NVyUlVXI)yObjo8~l^5g!p%;=`BiA&pT%T<-yZuQfO+Xw-@9B(pTYFxhKU~@l znQw+`CMU?O7pax0y~Q_~n6N?d)V07Vh9@3PQoxzaubkbsWb7Wh>_tmA8f)JQmua2V zq9=dOMRdV715e$?dtO6E*n2z-sM}@Osf1gPv$IDZfP!^!SsXLyH}m!0S`;D9&MJ9Q zRDCNyC_Wz4&-k!Gve!i+2~cYpr-$`u>&~zqosoqtn8C)u2I|Z*R7Ou(HA#8SmDX2l ziki^`1@a$`K%M^Dr^;4WZ#s7;#BXl6^Q+l9YAGM+E8_T?19$$wF{9Z^nqE=jvN)rY zWKLR8zQkeSFXZWt)x^*NM=A^~IG2V7vEZBiObw5EA~b8KdKxLY;y7dF)bg;>PNR~6 z@^Y~XL&NS;CQhFj^)Wnb%VSV3y3+wLy1GDY55#wrS4vJnI7wDuDquwC?m)jp$^z0$PD`$&ztBH-;a*LQd?k&} z2DZT|kWGy6>H5A>T+If)jMB#5*xFXV5G0kz6R?)MeBY9;v!?8Y*L%QC~maRG{^Lk{6A%IxK^ zXxpYCa_>4+-r%sVcmWj{bT?(Fu1URyxl#TDj`D2JaMsD$o{*yF($=GFVFhhC*~Jpd z=2uu_;C!j-^%`eIeY~Tfvfi?UQ@>J~PUM8i;bY+}G|(dUeaQ>QmgeVYRc*}I6xbZ_ zhCIk7!7ZC#)}dH|SX60)Q=V0=2#;;Ol48xG3T)ML)=?{CifndscaLgGt;!rKqLXQ9 zL)B-vL0ot)OvY@s%AxQM#@H7I&GYO-c8m7TRBq>zQP|zf)Gn!j*#g#^%=pPrbfEbiiZmkEVn9lft9@F6k*4l)86N)0}8&#dJJ6v4;dEZ-n35vQ1 zlT<~a&om>PXjTh~d@v;L^wa}jJ$P5~juh2e5QY$890~zri zp1;x7+^NSKX>oOrKQgD!OSLG|42Y@Qrw~o1`L(AJy<8E{YL>BEqfWxwuW7W$4xhS5 zt#m62eYyM8_VeC;4(C*Otw#a;5&?ZtAWAcN^~rwUs7RnfZTLJUbFH?+ENtBjkZ{7mk!Q(=pJ<-k;FWFybZBYKfc8<3_GWYzjgzEA;wG)TuFZdEg zvnBcK?B z>4V;>xBxrFs``pwf9ZayxLN{54&)Wv_6n8B)-50rSao>;RqBmWVpsNMt+Dd0QIo!$>T8Otl z29?*Z4a#@{xVgaA3-Z-p1N8k__qS2I*X(eyfC6;)021}T@9zDrQTyjBLyIl%e(HfF5}-^B6FfYk0M~U z`~g>U_gz^HKdq8u;@Laby?7nAdfXpYUGRO{tD)3a>32Y+6Gjmk3s*y^!4Sac&{~TS z?7kx(Z_P)r^AG2x1#8CG(`}j<({(AVrX%ysCA#>eX0?aQD$QO zL%A)KCG)n0(+v93xBL+pV>5H>4Msj&q5cfG!8JScjF@yM=8oQSLq!H@qEs?ap?1>Dy##$j+%|bfeYbWRhNSqJ#9) zenI2>maL-Ky=6oTEe_hj22|@4h~`w-$gj|HELGa7uT($II?G0(rTNm2#@q)xoIa5j zrJ#qpQl47e^c~A015H>%d*U-yuA{{NOYc^pE9W{e>_-!`oh528reR2grE;9D@CY6(C1J1BIN4Tt<{DnpOnM;Kk2)~EA8uB z+NKd+nc}#9!iCxvHp&$;9R84a|9Fu-yvRO7_oW~u2qF+A7$lH|d^?g+HR`_zkjOBk zYKaw;szZSfM?E}An2J0cX4p#82if-0CeD{XPn+djKLr2*c6ZMJKL0&kk^kMVDr9M6ZS?#0+@HTI*PYEGFBlk@ z5SX(w7@9MfiZd9iDA;y+f35IaMt{4b*I~wM`&@rPhO#Kw=YSF)MHO>GHO${K$$SH8*Ym`|ta=Tkb?H z^&G|Z988sef9F4M%KwvOJ5ruhkyTNKx`@74OPB4a2=tJwV9b5e`)Yw25N0%;*JzF& zY)F_d1Qs7E(sr@Zv7+&yQP$9)YzMz$;B%YtzQk8@)*-|nCJo(rdd8y%Am|) zd(b|~KIxMbs1%75suZjg%_vk-B6XQsmY#ZFZBk)WVU`h&6w#=A5^2(*dYZb1nuhv{ z8f&ekz8e8Nrs+2~T6p!PoIIS1lBI|$er<7 zHt|TlY7L`WD$9dU3oC1%X7Mc9;oMNRLw@;6Y|Aggw;^u_j)>|qU5LMk%y{qSmt-b_ zB7LuBry1=Iv|XEKUQwzc5ac2e?{~aGGTzqUm9{xY*!F6L({`8BVgG2`hQmVGX4;$* zWFRm;_iDI6JXcNlUL-DlF)eiVtV4JZ269%utdelRGxK^-(kK@_SP!RU6At3kizb;e zJ&)RidCi8+aBnYQUaDtmMfKwX7o6M3u6wT`Lr(728DN1QOOE zV}QWaK?KWoHBnapX;P$-B5^*A5zWq(P=&(OvBbqVt$)r2@r{!xujXk(ZzHkdON4|b z+a_|Ir=w`ps-s?yidzcO(L)hEDQ5@iU6^B7Ie8)Ebtx+U+Lvs`OnvPLhXH465`za?>(Z~swu*rjG8THj;MV9Fu!+)6T8A2#ro2x` zjSr>=(^a-81X#>$^KTcdkTueDqP71IU*{BFSJ-atHny>1+qRpev2ELSS8Ut1?PSGn zoE0^0*x2gdz4w2z5B7I9=h-|M*Y%9~KJU1Pi120=egz+y8s_EF!>N4+yAR=}T!Pi8 zOF6S+CYccb-f)?Xp_t$6oYVs==BKY(%&2Z_B>L*9k~AYw7Ian!8&_`Gux6T<^D;L) z&<+mAa}(zg8SMsL6U&dj4}ci%;zc-hYGNZz8Z>vSJH)$#Z4ySzy3CY-;sGA6E$9n+ z2L?pG%UNoyR`)oUdl4JYA@zgXi|vkbZXprB3ODP#IV*Yado|*55ReJA#0=-pehsYa z62#fQooYDm)-w~MED?C>NEenDvoEvx_v+RlX@o%@xz8ytpJ^smd3kKM3d&s-#S@g= zG@`>Ve44n{Nm7st*k=`43fW^~wHIgG+}287VSp50SE@#JABeNnk-9ldks%*%OR z-JThi^KU^5A%h)b3T`D9-)I$)g~Yaf01TPzf*kk7n@7A0ZC2Od`p6ao&yi@#r);5r zK}f9Fe0<+7ISw}BIPSaD1)c+Mjw)lDY`I|l68Ejl{){;Q2a0k#hgmuLVU3WoHM8EP z1a5~==6%(pu_=QrWYI=7`$tlfb0NQSjSD(>HDtVnW?n_7K1~ZMB0(z+M%GRo`)f5H zJ_&+Jy2H&<=62(Wi)>Lv%^fq}bIPr_(XXs~Aq7~{ype+Rd#?PS1-0%5kyY>LGA#v* z2EgI3DgFVa2>qn9u2Q~H0x@|qj?4x=oJ$#^CJjG6c|&n17i91-ys(L6>L*;{fBT2Q zGp4qS(6=m6Jt73lv}eZWiD`8-I4eRRQa%fMCa~%!4-!TRk+rl?K5{;Ow+Cqeg`m|; zt>nZ6s9QKFA9<0Y^5|atpnQ|_U~xYnn**raep9n*+&+6|Yq-HSz-hF66TjM*{EA&? zIEM2zk0orkSiAj>vXy8L-9oLI6F8ZSNFxm_NZxh2X-bW-Q8iB>UNcIt;R8Wx*u zly)Tt5{Xmhr_bwo%ZTF2ognwp7sgPwlCU6FYKgNHx|y>v8>EXi$zLTGD^s?prp>0! z4?j~zF&7eN%dF{pnzl0l=`eyUE0>29JD~9cc&ulU5AV&gg8hygolnB8rmiS<#zpi6 zI_kQkwqpK%3i*qb*E|)izLct=YE@P|6#+jH%5Q^UsGqhnw_29H5S56>dn1rVnrj%; ze|aIVcU*rHk;S@4ZV4nJmaQgPtFSM&8h$L&C4@`gTuUfJWx*Hh50-eQh1a5KQPy@P z@$o9M?}_Ub383OhI_N=O$q*wJqW!H8`)3qsLjlCIC3C(0-?vqoDxAf1V7`3e#`#a9 z>p!mb|M%$npLrXnqTsY7iWY!5?qVvxNKMyD2Q49&<7~bA1r~$sD>*qDs7RxXnCCIM zbHlr)4SAptBM8WV%dGEfLrD(GGa5qOjn(cc@`s~*ydHD%Jf6LqId&ouhUg5dBd^DD znGLqvs}4g(TvIR~e>NQ+42~0|=bphrsA$X-UjZSU(doN8(hCspc$v;Q9)`Re&08hC%`|pULMZYf9e$*YT35dAnhK#r z6V}qTKXp3N?FT=6LxZ^y5-$??J0X%POtb78Jer`x`AY%2qeU}xiWKgp#hm?+ej4+F zD2q1XDL54iyhnx_Af6_Pu2n_z1ihs7X^_$QzBK$nvX+pV9Nl8Fn{gj z`4b1=jRWhk!nxW#_--XP95c0oHefL=R1SZ-o2@bdK}a$r-+DHU1JjPboO^~q4Loi| z6~-w;6la%x)c8vDUsi`lshxJ^pDmmCXL|o{tHb@Dtl9t8%YT_34R;%RxBn52N7AMv zCL3D#xWi(rf*OL5C?B>gLJomEqPlpOcx2>vpsdi3Z!Gj{=hXGRkDh0x7JLVgVn-*rziq)88>+MH zCO6viN(a_nmESh?s;DDCi?Xh9PArU`SZ*YlL>Db)RQa$RP?k?Kjl0DZX{5>`s^Mis zV|jFNTb;>7T%&kEoAmOlHikKs5zl@amJ#W6zru+=vr}bz=Mo}`@g{FY+N2ky0S-FN zRr7RQ%TN`KGj)H8M*22)qtaoBKcSxOU2Q(iAHN?S86XgY{vyvU@dv&-WEW>lyK9-7 zT)wAaGBlPLLQ%c?sQiNBk=+yQMb1Xf+d=p+lU5)h;;mxzZRce%Gj)x6vge*}t;)(C$mNbTdd$uYIeu7*2GO@{u-T5U2$Ww&ViLx??Yz-wG z<;!uVWcJDUJE0Kc8Son-V}Hvc6S)`r1($-DYbIa>b$s{glg{$t|(e=En#-o)+ytENu~-dKI5?U;E|jv5mV_=ODq9L|KA z%-jfUkz|aKND9~jB+CF4*iG0?+#p8+cB(sL5N%bGHbi549Jaf`-7Cz@8a-fJJv)Qy zw4k{Fr}SPQj;z8D-Je@tSA56JR$a&aeul66JPH2_mq%}Zd;z0Cuz!IX$7d@TOPU17agG+DyQr;)P6nbk0Pn(c18ST+fvfMe?#P94L^rV0s4%y3D5x;Eb_k(BTsIk(tzSAhnyp_vX~x;5p3;`B zUp{HZrBgBKtH5pT+C{Bpeaov*zieJYtz~oTtx&&uh_8@eI7zKgzj7FrEhv*hV|R8app-K>@(tl(+mT0;GieX(jT znj)0FRXRDFy;VF}m%UX!$-(JUPW^N7a6Ma4I%SiqOCm*p%g2!(t7%-VC{!p3;i#7i zJH3TQZO{8roj!LWUolMaQJ&u3{Aam1&zlOpy{U*|m>XD0br0fEpI(0?Unxuud|c&B z_ePFgZ_WZX=2W6GrZA>SR5Xr79&6UKPn>-O8xwkMl1334Na~(D{pkHG_;6W)i zgS;P6AcT`enSp{3({Rg})})Q$gT4gv#sh7jZ>AzqN*y39&m3gJk##W-X_pG*@}|ES z&oEy_!Un6jC#+C62PZFEK}eoG$dzt@MNeLea|M$d1O_N>^Xc{mMrnAU5IA;8jQn5B69DA z9F*V=%%6)N$YD=*5dX#r?i&!9Dl*}#@EnZL1qy+^2@iT_*kBzucnW1>7wNJYq$3zJ z=na7hH`7`)ljJGTll4W_{#bzT!MY8*!w$|=M)@#HKDzjw;H#y= zAAQL%VHa_nv;cs;ZOj8~K)dF% zaYpC?4qCzSQVhaVqEAq=ri-Eg{UI!%;;9;wMJc`#+939%53<0$0|)(}en}5P11bM~ z1fircJy0Ct4xW+HSrE7awOS5>dDO+v9s8viva}v&GY?uT16e*Y14YoEIe-Ib&xwOZ z@b7Vh(on+G*Ip2RNw1Y43ecW$flBDtDo~cCAZ||~2t!3LWKMp0%(Nsj11hte5L^gVLoF3zmq4* z6{d<}1H+)w(RkAbU7+G=8nXv=d$h-YwjlBV9T#K^;|5_M3?VAedGkaCfG7}oQ2tQe zaQ2kD9WliF%~69m{oBlQ``TXIvrmLgGtBLG0`P+7xB|}>P>y8I1VACEOKRr_L_r;SnQcj2DS!~z>!T=c2YcZ{CS)B+3Kj62wieOZ@c1O3@6vc@&$dh|9= z7oxve2L1`)9Z?Ff0!3$%kv%TYg1=MwB9nS|nrw&4sVDumSecM|cZ#f+%Bds$pCc{s zXkYI*^Cjjil%VHLKw!JU#X-)W{jdF_%*4e-e~x*;qr>miJ-maEo;Q%6nFfMGZx{O! zP_N1wBPHr&3$54S{ zyB7G<4c26a=q-QtHC$p1ddC`CW7gnbkRb-Buc>!zaY-<5ci>+NzP+)0szEfYk5;^^ z^gQznY%mL#g#Uy;a`Sx;7WuyW!@yrA_%*t~4%g_8;LrZRJ9^LaKTavB+GGG5YF~{8qtK9pg2|V>^iG=ZBwn3Q6aGt{>i^|?9;)7VzsPDmxm@ zpK7+{QSoR)t^B^gAV?6^M~`95%KpAf>)>$H%H1;!yQ*2wlBc87)E_41`;Cds^abhL z=e;g;2ARg^efQE<&X(k#%enUAus@fVmpVvN$dv~DpPWi=q}APQyHB3&CLSo7UOYXU z+mgH>&uou*krw$Hc6Cj);LAo^qa!b;+vlF`^GzX-UrR#pg-loW z)UBtQ)(-iK2|Jtmt@GPH?g<@z7EwEEz|OE6=Y)kI+*khYtvv9L=QQ|T=~(Nuijtab zqCypgrs8`~_j@R~rpmcZA^;&`#u89F1$I=tv;;;B;aTW1Ub_Z=MAhzy3{N38V)bM_ zlJ-EHHc*M72y5k_yUllbMj2C!p)(ygQxLc>US>n1dt$LVc$R27^{%UWy6}v-pudTC z-CBiiiEIzIkaZ@3pQrSoqE>z9>AMw}JAYgMqhq*dcB$ABNBrT*-L$X5x~#(o^3fp{ zu_8Uyh=8!*fp-SE2q>?Ne1l|e8~A4zMm!|(W%^`1i=?ZbU@OpzgFOdZh(392=AD)- z^Qh96B9xCSd&GBci5#84mw$DfP*SsBGN7)i#<)*ID}*dO-LILdSl9%koBe)>_q!(Z zTo$0;krq3OOqyWtq6qy=4JAdd{^ktuU?*c%a-LS7&}6I3^rFeM+NaBu1zkPYF$M9W zM;qQ`Dd)?-5 zQvz$IhcATWctfok(c9g*|7Sb7S4NLdNX0~t4a3~f72Vl&+x_l!ACu%mb?aiw6kEJ9rh}QiZNJrk8{d0#Rr?AhHt8PW9rOOdvA18s_s?K!JzKy`)}Je(3<2L4Zulvu9zI%^LO2k za05UJE1={gp!U91_aI|r-0KK+3(KsIDIT#x1#QG|Pg+dxN)EkvB368yn1h_Y!(OAC zby6MMOd?^V>DRQlESK{W%A9CIAsWjXenLDS=SUP=jt2_wVe&KW>TnHlDDCgeRVlbt z!c6;CaR2I9F62HccLZtl17S2QS#$FPU|K;8o5`#9w>Jkol5oo4d>Q(`Zp!kP)z;r` zw!Ox#2BwVtacfz23z802F;+1ZRHd*k$lf4yHh{NrEqzkRx3m#<7+%-?wy2m< z{(N3f>*bIOqcY7c7*d_O?P5?BhEIk%H-Nxywi*!2m@bRVuOp{J@^xhcDxH??1#8q) z!f}kfw`oI}oE9T#`ni))6Ez&oOd5#{>GpAIv0c111WnK3CTXOmuHzX^mT{%YJ)Epq z`#5_3t!RUSyd&RKKdo_jmrQjev0$`DQSPpFV~)`}jiV*GjjCQlS`LGPSf3w$Zq>C6 zBNy1lV#H0kFQzY92Hi3*Mo}-NBkAQrTVlki%Onngbl=F#LVR;3wqf>d-bGPKGuzyP z^aj;fv)hV4V6^RcUIUyD!b5(Qy86B|M>UZz);CHj<1Ovm!!?|3bn#G1&MkwA<^)#wqp1DeW4NKem z4vNZ9>0cF&lh6VQuzzKFNBr%{VAZ^$ERiqsoVVbTpAv7WGhC!Jj@er}t`%!z!AY2J z-+OZ4HbiF;W>-{^!O)~>BD+~2iqoh~|B^$R0{xFSf4(48S2xw9mj@d@7;A~M?{VL8 zP!_{Pwyi5riL2^O-&!a8g>B5kWeGh=>S|DvyVc#g_rSosodmsxqMxVDmxZs>D=L+} z?$5_g{PbTZ@OtL6ah;kHhUQnbmKQuK-sH4P`u4sm{W;M@6sVhcOb93{|MIePFX$QXa*kDtNeMvU5~m2g8L zNH$x-QAU|z9AkdcpR%2{~gs#gJ%9ft^yiw!EUy5`DMLLI2Xy zcg#3-WkebtFh0xGnWo>>{C9;E5(k<3e+ff;#(*1lhGjrtj)K)7A~pN>e1)Ct84J>R zsB0A-C>YnGhS3o3lalZyu8@3_I(_sId4}}UF>?a{JmS|>&4tnrgYKJOoA+FJ|QVA%;Zm~B2dJAc7G>QH-3 zaq~^>5{0Ao)u(3x-}jO1?AF(#nD_?03sD(vLL;pRY~=ny7U^L&0Q(MqM($L7b zr4g=c8;^7^u32JmLJZGiBDY!7^!5cZsj4nV-Svp2m)DmSkv6_(c$E4?`Zpnv?toQV z!uNo@uN@sN1QlZ2j83ELXVB7RJ>8wXZCzoVrnB?~>{c*3uPL7!qbj}qt<|-?7JXg) ziw3_`7i4$5$l7+UPA_jg+KqYevtxQHh(?o4QVFpgy?jEBw%5P3z{T&&-y1zg5fL+;E zZ3-g`FPV2imUO^wc>?_^C8?#KnqwH&&!}DE^L}|rob1zVg&3b&x>ao`bbxUV-AZup zUDRCCJY`^DV5k#E;q;KKYF7uSETR~mY=7O*I}@z?y1#$dGdaV_=)E;d*{rE9XR*Tz zdoW^aKW`o6U>-yVha8JUUyNVO^9QMRV0YupaOJkQrSe68n1&sY_N_F4B@qaEENx^fvLdl0=tNKcOT{;dgo231(#{k9Y~ zfe>e47wBM&gy7?ZhvNGgRf8f-rg%=ev*1!B|w; zXm_=RZx2O0G~&kmRFua_$xPDJgHM2D+cd6|%_E=$U~}_sps)~_Q0jkvbbGihR;IgpO8 zjNKZ64 zxMCAX5%AzHm;S=8-G$9RvLmZ=S$M(aI)Bp=ZYEf^Mol6Kd9_kuHa_ffBT=0S0n%>i zq=LIrncElPOvlx^L1qmRG2~6$x2FS}rhDq@21`IWLlRa;XozXzYf(vOYi;p2E?SK< zUZKuNy1e|^v5^Sa7_!X{6NM8pWD;9}9>~i%%L^BPki!OkyRm>~?Z%IsQ5bhF{+0=4 zjEc3YScbNxH682DWh_PldXZktr_V7@BV)0D7|Avpl&3143@Ep6*Yeq5t$(S+K03kH z=I0&X%z?%EfsesL0bw0!nZ^q2{&-2^XbQo+S-tbk#&Mk+nGW|>+`Rb($IHxIbdL-9 zcLu<}W7znu1E-2B%^}X=72YADlKbv?gkHTfq>sgJWdVpkrTqr4;|L~P3KBEBFA!zX zAI28!2!@g{**g+}G5l<@CR4SJ_t)WJ?gG(rQ+f>1da^SQRHGIeWh=9@zh9eItIw)e zr?P&(-d6usRq~;81aoLioWPB@p~U2>MwH}u;znGKMSLsXJPs~O9Lc;%>|Pm;cv1Pc zpwkSw=ckGeO9G*lsu>Q~M_k6lSzC0TBJfv-euROZnZA5apQT0DjWckztA53INgX*hW>YN=g;RR_L_(R_xzmvuaP(Gg2HD%YOHER zN6DGyrpj~;{Q3Or7=-FHx$V1uTd*>L{InVF2oA?qZM~YP*$l@9pIr|OR>Oy^rx{Xd_kp;KMRiNvd>7Jw zt34kM+vu?Im2a8zFhl`rv&N|_E9RLQFPiLFP??|9>fZHI0Te=Y7pMARsC$@sr^1mS z>vKPHehlA@lTb8&3bG!dKj5B|^30`p*8RSo8-V{gvyGEUqPU1$A7}d>sMuFR=EfR- zWmmX*2b$8O!_THi{*RQ3-zIifUCu9huZElIo;=4gDu!h_jN-BJ`du}IslrXn!VZrr zbFC`P5pkbK$3zI#}&%2-rs+?PZrSE(lyZ4QPo|+(9z;Ph?UrcGjsY&7vGBR31b1V=<+5uT8J%)d%KF^3 zs6+wDkir2mvO|~=-8OLfg29pjY>;|Af}p2)cZG$eu3sG%X+M=M%x%?a4G3jaA3L1N ztf_mwH>kb*5~Taow_{ZeOt;FxJS8R#oWE??jP)o(T1ZjnZN3a@7?+5sz{}T;SI7)D z5tFWDB`u6IA2p(qWLk@5*5XH#r8%L=A>VtDsyn2)LHU&@7z}|<1)+?DP<#R|5d6^s zE?le6&2Tvi(YSD1htC+OX~Uo%taH{tbZ3(=Ysi<%3Dvxkgpd{4}dIlxY5@A%&% z>|7M;LsXxMXCltYH2mprW9Kp`P&_W9FM6wh<&gn=Z$({`8s2E1i{qMZtX_##-~WaZ zz2i0Svh4mqO?oLpy9g?tnY8uM7g?pA+%DE|t@wgV7`X3Qd80@Z!`CfEV$0HiT!ywa!P zk@A_?kn58a?G*r@FF{;_5ER9FAmPY388KpPlG0g(T*ycB41eypZ}xb>ifSh@a8n); z^lA+7!*X3Wo46#2nys32Rs6Z1dx>L)43p9n4*;Y6Rfp-ju7WQp2!4KwT7davBox_G z0-+adx+hYRZOeQgPv%fVyKhdF*R!-Gxsz?-^d?~MW*@QcGvUSV_IwzE>~pqKGZ>3C zoH+hU zdmsefksznxR0-H~_(M>u|+(Qa#}aZb~OrQXa%-NEWV>xqy#75zqR<4KUjOgYJUhFdz@$ zc>OCZck)l2YxM#T=?9eH7g=|XmD25(^+nn^mO*~m$fUE1gd=!fsGnQj&&W+mCMR_d|c^h#ifOPsB zn9Td%Yg~>}1}(=40`iN+9)I3kA<2e}+TUN}x+>enJ@VKYQTQS52pAYP`1S5YQoDVP zJH2(QMeznymUvLo1W+jwPze{|=tU7Eg=Lp(1vAX=mJ>idXEGR#%5@>{4+n`=^O!GKdDp_z9FooT!xCl8Mk zYOD*N+Z!08c~*bx*2|^+={mxsY|-m5ig#BA;3hv7L_@kH{Q^s7)es{9TW1fSb#516 z-|Wf=2K`Ad$&N+}G@dkOWAo+;205f7l~aTWf98V<(e2c%ap4dKHIz$MD77dxCc+uB z(%c0ot!S=TW|CbE+SBu5q0PFj^CGDU9*IBeszUf^8>XLoq+qoUy36*j&In!LE^~5f zo)*=UT;VRAsuO*f)D4o%pBJ+aYfFVazee;TFv(3P`;iI=tY?+s<>gD;)$ZZ{WfNFG zjLfwLeW}<3C`PEv$1@i}j&8wo6e12!#wkMwY9nl)TsY&R|EqYyKghB1-e}^5!1cjV z-GMliHw=(bhl0?f`vIo{3mJKnv@H3J=^^T|DT_RMJPS)S)LR{2kop9x<4iG#j!ONM zVz6R}b{zG1SJZL$>YKe|DO{CelG*yP5BT^O+N(NbfcBP)VWffijR+oRkSM-tnxDSH zgVON(wzC9Cjdg&GR=I9|e#7mXq^qWDdONIcuFj+_LfDv-f~s2FjAAz|XBX~o^XX$z zOur&VJz?ao9x@6&Vd$=PnTXxfK2{)oOY9V5wZAP3xH+jC_wi5ovcY5}43COP2ZN*P z#p+6)ASiZ%Nog%sk2Uxoq7~$Dg5)Tj(hEuSo46C|as=+()4mJ#QYu&7h3jt!+s>B_ z97hILBlQXWvLfC-iEC9u7J@L5dI?5`4(MrKy{xoe*s-qWK<5x@;XLs-mS;5Eg}^OX;&2~lk@#KAPtOrP8QkhKfr?T4=?G=fo@Ww? z_S&m9NikRrvKC7ID!~j6^Y)W6XSK426yElyW^+dk8WiMv9?p(gEj-gD0cV{qD^5mt^yO!Hlp{Kn!^j-}shT-twiASv;WW~E3 zEpSJfbtDLvsFSJq&oy>Set%faW7Y@0Uz-ndvmh4Q}ZUVM;#t59C7U!PJe`+QB@7q?_$77^%{^M?b9F+3U`qR^)| zFu&M}CHJNc3uYFMpN?N`9}=XnPm?y`UR&ky!fTZ$lme!V+ZvsWuzmIgIo6_S?gjaw zOO!SiKlh2CD*{%+!W?016vQa3!%*^qt$8a9l|qf@0Bt+quM`4-aC4?Bb@F@efOTRg z5`2Q-mkx>*N*LJP4Z~*zmT7J!e|8Mv5hHfE4_b588~mdodM^q1?#3w?tG(^r!VrHH zXZs;lrhgCeLSPoY4m$9;=g_9))FxBirA$H~2t|e#&L*xYR0?^D#kGo-u1O z+rK;PIrvJ7W1%f_kH_(efCF=Z6=yfqc9kPF)HBU6Xy}8Y^Q$_t7~rHVY;vz%;F(c*IDl?VIG#uyMjlBU{ErUL#(WyldE#O z_~$qX?2_uAHW|g9>I7XHBkLS#>l}@*O=<~(w`Qy;gz_5^?8FM#$=kQ?RALORV9oY{ zEGbSH{l}e4Z{}2)rjD_w)g_$PjO1UXK3mr*j?H6AFF_H+T?9%a5ou@txJTUoDVGT) zLi2eR*?CXw1u@82b{}VV;HCc=s@@W90O@-#Vh<8?3G)%`~Pkw}g9>PjS>NZeZ%} zofv%dn|sBi$RZW^L_MdVmuNhbO~ruGK}{MTzy4B%AP;7azwH!yp&e?$eUG5C`{aXP zQWa{A1N#+^f341?D|?)+Fyt%0MdKp&34NZ4>$eIk1{|x?y!<5V3P`Ek?|&h&)=p%! zrapXwbVoAV8QPCe%=pjY#yE`~rTKfmwwoe-(|R*H8J39x+$yfDpae`D`}dHcYKL99 z-b2Q&0IXWGwiBfN&^G61=6=w-P%~m#f$&{Wav9qXU^*c^WAdCccyUpp4XkyKhZ{zq zSxbE4%v_~iansIAe^bRg=cp}-c`fViJTz2_1V~o> z_b;xa6yAntC!fO^4kJ#b3>~GQv}ydjuqhts&X>H76G%GZFsy|HBxc+eWD!%2_X%nV zY~AeNmEXOqiBZ%DXV@++aQA~+ZeAok5+NT+M+dt=0PuBLwb5`Ff84yOPMPlz5t5HTkqh5%fks9X^Z{)|QHMR{ONK`DRA(Bz2Yt|b*OUwbYgV8IU zDDN01^s7e9k)8|A8`xOSUuIe{s9=Nr1nUVAOP1(@#+|5R@c6!uplkmi$7qaywRhB7 zWWg=W4M#t5LW>aCtJ<5*8uwlH<1B{dEa?sX24+SlOT1>s zN45&5vZyUvXk=n!4G@d-jg~R}{w82fT*oVM%%;4uUo2(krOkr8_4jAlHX**^eUgrU zF#bEX?e*F`^(W%yC{HIgQ8;8WD1kC!NA7EZ%w+hqIk${$tyFoVl#Lm2R=DmPU61N} z&Nhv(+0V$I>rSJ%7iu?DN*SmA!G$~k7L)siR<%#v9jTF>+mi{wjem#}dPe=kL{xb4 zqwI<$1#c&HLJXQ*}CU!0^U5F=0e9FheLKs3-Tlq1rU zbjPLD!ab2-+PXglI?)fDdu9K%SZL_4@Rxu5AA;%Aca}94`sWX1^+Hqt^dviC^ARAE zc3eDaCVbSDFqhn|Nb)y`nva+{6?2!G51lwUW!Hxf?^hbibBZ8Bf2x{JcJA1%xo{3$ zMT_L(q+%j-y9!QYj0IVb8cx&Oq5LXK%=%Arct5@L=AbEdW(z(N1_p0n?ctpTwo~%T z7K`08l`GDG+}z&Nbk{o@U&3Ggx#Oz|=yyz?)Ks5c}e5TE4Vj|`ipyb<@MS+c_i zru7w&m4E7l^(Z z($XsP^hle$aa9u)xtMIB;&Kz2+XP$yzWVKP_0=V$EyTSkk-w}^u1r8Wd$9Dl@HRV}o9=sHpQ6O99j?2?sHMWZAhjsJwH8Vks zTX@FS`?y)I$poGNY#xmv#O zQpmpvEOh-3I{C}D-kJ#u)YM3LW=v6QjCjMR;ZLWjOZmRL?1myXpkqvswA*NnL`8Kv zNuKr5STmWmG+tlc6e~EPT9*v{Nul{|XTY)SZH;C21effUxoJyu$C47O-V@(cRW7GY z#A1WPA?wtayGZ!bu+xtR$_!V^6c3+vsEVjnZ|nT1x73!2D^FHw}rSCnwxiik8V#{aQyu{eh06Od%cu7khYO-k9eQ@t z9{v4c%$*a6F2pn;sARQ{>S4a;w7T{nks$l_{`3jyTykm=*$Z7>xS=?@qwYiU*N>J; zRH-Z*Ly=OmSB;(HNnG`}GrRdL>mhK8jE?DzTt6^x!CnH-?QJia+9ffraAcFao8@m# zpwI{0mMsQZs~ElgAP1BVA?^!~a3NDT>JQJY)s4_;_L$-QsTXn&1)^`L{mI$#HbeCx zj4Ac~8fTVvYG3JxjaDKNOYR0H^@@(CBz~sR*JE>t^E7vxAiJIV-#fv}k3l)5+``d7 zwFExcFYPI6)#s-#k7t*?;NyP&kaJ7Pv_a#-yDQbZJZJ1Itb&WI0*hK{t0P$a8?@d0<$BRsB$g zj>o?2a&W8%CE7PQhZ-WEne)LKd$Ahx($QiByzy{6db(IP46YsIR7`er5J=PoHD zHG|6Orh=7mLh3&~tt^`vIM(SO^2_L>qsa1*1C=FC2#N=CzDwM@eMs0&p{c^!hFhS&m5y){&n4vxNu4@ zFQrw_SrIKwXI8g?A{;HFiua+RoOk=wBtu^;gRfqHT)?y_F(nhw=8rABe*`Z!psZ2OuStc=hzrGA zh;##$qKKfG^v7i(ESDAL@}OlVs-6kFdu1X8Zy}C0e~_L%2m}GTN6K-4X322s$W?o zGZp)!suCKS9@9e`q?V{xd8<5*UCF6lJxQ-FQ%MIM=B?ayb4}dz^CVbU{(JO?3B>fE zvjdcQqr7utQe#qFup>7ImTnbO8^S$a!Mk@IytQ4E>@(Ty~l5ZAlkJcy)qJr6X|z z8~3ilp;_^shg>+rFy~3~a&SX3D3((gLx#IJNy#hk+~AOBNLn|`E%4(ELvhYSaGjEb zzUnwEBt%}qzQtdh#sQge&XVLHA6qcqbmGtZ#ly;X00FIl7*2t$h*w3%>I`(Ol(V$^ zBW?2pmP}v$1R-v-1P4~a7hIsUMOBc+J=k%^wteY@rH+KKvq1Q>w55u9go_uDBZn(5 zW?-Ihm4oyS0r-kreBJ}M{cE(6F++k(KUl~pT31+?wd9zk(P}CuP`>PBWC&!H@c576}#Mv0x4>c%}Ad9x%?$6ry52Mn*Fx#gl6XG>FGrUv@OH{G@17F#` zBU&D`r`I5%YAN7<9QIW5iZ(Sn8@F`^zIBaouZZc60=}-Kn z5|tPNcVv52*n3_=r=^#<4Q5Zf&b3Eh8khGjH*kxXM@se>lW`P|G9!D%YE27^sRL4*(d4vWC7cH@jORKluO7CWWxHD*oYN=97y9tN);))7?E=qgTNjcHr^`(uT z&DKAK8;V`Q(x7dJIHqcuqH67(zRTVxYn9y2o9t15N_c)8SVKP7`5d8;zCOICE6djm zWKEJSA@{rB0cqGP0_g?dlmu-|ubJ6Tu&dB!j8;+cU&cD>=^gqoP=)TZj7#`gJ`Dq? zwF~>hYB_^$bx|l-HFGDbl^-tAFybj+@@K9V{r8sJmC*Xnzi*!rUo`h~5Gen3?E;cq zq{iOZ=cb-hVo}ZgX%8syNT#1#Vh1LkYhnj9OmQ|OXeI36j;s+)_+bFCXqLu7aO~V1 z_GVzv{OmlYICOica+Cs+VZyKQ#-u&XaFlSx@H!*|q)j|e_N}pNvv2{xr8#)GF+PC68ccl`@Yw$mnzep; z;;rkwjsw54XF&wHQCNPK-YvuPttE7$cVd1fu#JJ6bl)+G_iUqkeMg@aNT=^{9FKxA zI~TK9?^s!vJ>9Yzi|J`gm&}zo)BL8vR5tnquR%hndXUbv+{t6#WYDJ+(__&7t0AYf z{|w?xh9QYNG#gEEYgMh@A|X3j#oUxasL0c|?{Q>QD3<;TEu_0duiUEj9YU)8yH0ye zyDqH4@FIqamfMnI3+}T4@uMs)YR8?iOlRM4lwG(~dbrq4dDn`g9syQ+$tcplwo2UD zT5aNALkbGs@5N2_4xR2R>1lHtEb=DjE*>ooM2A&T^V!DfldaP*XRob}l%WdYJPLD( z2C9p(>qdK4J9?BqE%1Z(Z=J8*O+KW8KHzV|>f#kQsYwYH|n>BjVfHR}012 ziUk%lbYIjaeq)MtwMD10Vh+Wkw#6gR40EvZ8A)SEeHwkMz?j!us3=2zf>>4x8mM}6 z6(&w~dwlO6;ZxcFHk1SpzX-}!a^+WLk%SxfyN>^NMLhVgN@)P%8!5)43vXwF9A;B+ z4jWFa9B7m`EayH;Mu4uKqa5er23lSvYlxv~s37w%@=q8V8qO1}`t*5yrh=pwcu((! zfz1`9-^p@`F$`T_*i!t>zSO-mEP`=sJ@)wjr=qPbpe) zo7MeM@;1(E;R3;fpYb)B5!W+MB#u&Fm>*vNfpUWK=x1?5iD7!OC6@fqMSGc9IPn@U zv%m58Or@be_W`~I%ov9otGfF9*rqmGyz6`=uiNCC=ftv6jN^IjVhf9Tp54&~+eEi8 zDn;w9Pm!`7E;@436Nk1!@M}ud>3A)6FRNT=+wm#cCC}X%XehpzOJryl2-&N->OCaLN9TaOaB5{ZX!OK=UL#*US-TEg|xX=i}J z!5TX2X4Ui2WvRl=-?4bL+7&Z>?)K_YyB)RK)EslcUPXMhq#mFBicslTW;G`lu~FsY z(Un|sO4ZQ75&s`%f@hjMitf9R*T&=#O zq(pq)5Pgi$h@8*tRR&S8;S)fAk#f!wcR8Xel;EEHk^0p_5q9EgK4-L?u7~v*Y*+G4 zjYM;=)CU1EiE`QjQ>ZYvA;iLEY%%UTIU|X(KwxsIwCngIM^)DOC*D0V(ot3;5aedI zgh$1muF-{;+cTA}D@P|&+9)-8T4cSgNR?<*b-(x~#i-Y3;z?iHL0XCv0ar+_{DEIF zOpJWJF(Jf7CyMnUp4zCzDV=E>v4D~A>{d4L>$1$?8v{d0W18znw88VyAM_LPPOsR< zBYmZ+jzHJx9Y^^GA?&LPkGZ}XHov6S+=|$&UU=lGtrTWg^A~t5-Lo#9$m<-)gf~f0 zqr^}S@76#4nY=|fzL<@}ywfC`J>1s|fz-BnGtILPb_2?`_e;FlCO0sHVasbuJ^Qr6 zeDQEK#SU$Ln6Td5R^O%s?eg4}An(@nQ-y+8L$GLfTIhoy?Nd$b0#^sIXb;@2AO&nO z3!7nJfa(P^Vofb3`~{l9_VXqb*!8fJOLC?T{ndy099^y1?`h{ITn7^7$+`x`^|H)ME?%fzx;Oahflrf(ihr#9)*&go7A z`v$oq$8tt&tuQsbU{iY8Oi&E=f8*py-Omk3C3GKW^%{^}2@;2Y5{#K;wG6;Aos+XP-aK|Mr`KM@ zDtvLXE!Whj&o@>P6AA*=lYq;J@gzjGgq&rB+l@EjtWwnx>9w^pn;lZf7oLZ=~V7Akz^0H<4W=Gi?f`7-<$W1gDaQe_c@mIrx>sp zX`+?TkA~zsas#6UoQWOxjvyNEcecoiTE9G>wz%n=&+wd&pfp%P3|d(a5~MyL9KRSp z9L5u21h|!%+3VCIiJeVPHsW5j( zXJ{0-IgjDSP)DTN4AH} zr=o8NCd~jmjGs>LchpXe-V4fBz1|CI&)h%~7(atv0?J1b2>+rjeuxK}M-vGDf~`cD zch%lfR3Gi$T&hPG2><*o2DI1Yz+dW@95CFn){! z{?gs8bYNVYaD?sm&~-$8?S-daI|E47MFTtR)8G^sc@F^nKsyfE06qIOnCVs{Y~Dji z23vO#EL*eIM4&$41+#njvtG<){4Ba~0GrIOF~C?je z?8GtebAwxC@yP~40vwoaEglnLK6)Wa|AL zYQT?(AkJtbZV@^W5|P}96DPWUZZv^d2smgohM_iU{lH4KPE-q~nH{sGnr)0a*}-wF z8bQQU)Kew)^Hvo~Uop`C0t;yBx zK|pz1^(%T_t-+Uil|j5$cZ!@o!Dsv)7R)K5$E*lM24Hisg+b(65kG%N3jsX!2hMI$^bxA;BGy`)N1M}hT zzGH*Iu!!1vtJ+V|ctN#6j`VACl?~&u)w1$Oj#I@e0?(3&i!B(m&B5uiNBsM;M}B(s zE3i}LD+AA*2!idSuk;3lO@9tz0fk7x(A|opNtaLiR@x#uCSbw=4uq&!5hSv#G~Gk{ z-d)Y*aw3!sq|Z-ZpSpV)8_6-vV9IAu8Hdf(<+hCq5xa*B1reW{&4ag5sW?U}v*rRE zJ@>Mv*jBWw>8r$^kJWE$e!qo3nGCLnYf{ciY$GENziDe$>M*5!6(b>~ZWd#mU~DFL zT9@)J(y`#%n_Z2O6f~%XYQMV)S0ZOd_Le>vqo~cWg+vkG&H2_l|JDXak}q|I;EXcYaAjXGj3;( zvm%xH)0eNvnP+h;lh7g?`HiPp;~93?YcUqrv1=Y9CT9nQ8_7&W34HA)i^G3%2F>@r$xQ7f{7>}Sf6l3N_S#7-4(3p*nkp@GBNwA$UN zduoEP^eLNoB>GDhbr<7?V;E%j|<;pZWKIHgzeyz9zVk91`s?(^U z=i?@!$<5lukU#cig?I%FsNsuZO(1;o#$EPa4erm1OS%?~)WcED`uHb4;i*i*seryF z?T!K@x^LGw9g5y3EN1m>lEV?vyT8-vr)~R)>5N5RW;I;}@e}xN!BS0~$C*`-(pmKO zolI{|BBE;L;PHksM#bYrooiYSo@xz3OC4+tnxxBGgxmT;!xuWw`jce0 zA!si9>=lEbR4vqJO*cu#L(2o@{;VrkHquD;x^Q0+Dz(r%_88n>Et4%rwNHE$1J=JYZdT$e?3z?ZxG1sakxZCfsNW{4(7+|`p!v}u zhm%vk{+1Mm6{98=0Xl}@|AD%dB<9D+Uuv&Svw;H#IU=z&wxBG?Ocfa-MHB&shPHV(rJh7|%wf zaDcto8lp{DN1703CrDXP!9}wxXy?c%+hAOH3;)Q&?QprYe7+VBmjhKI-GVi>uOBZ+-jS3r|xBYs?g# z!@ED%mv5*T`*i#r=YWk3%6oWBr+HscChlS&J3upOf_A@eac!3({};5-o|dsg21}*_ zSD0L;VN&-W!@BP7=AR+Lf)+QU708W!8b3;CtU61=_L-@uW)|nl1DB=@DP@D`zhXY+C)b;9>Qi`^Ak;&}Ayb zYos(KKcH9Bl`fWiz=9r}A@rg`F%Vk}88@LD@N6if<06C7xFa*cq<}D}GCL8-@630H z))4dlHF5S2N=S+$RRp7n>WWbQYH0|@z%#&;iB{`kk&Z=W-YrhecdgF!wD}06pvXh+ z_qV-9ZW7F;=_F*%`!>mEB8Q&KvmUR}88@+8w-yvNZ_QHN(e{*vIH6LUxo{oNU~6ngsJ!>csExdcL+- zhOXK8I!L;0^wLQ9EZmLT>sWJ8qcfll@fiu>OxY9?2CYQ-b8&o5c7g`Rdzc2kA})2s z{4x$RVh&5o9Z z>8-jcjNF2VDlA!0-=rILxyjDP-+M*!v#!QK@@Y@pz=ge5UR2ohZQJ?ctkUUWzfljZ zCL&cJZm)@UT(&|Xmd`OsKm-@M@Pf|NB$LdQDw)1m?#FU1%|Qsu#mK#OhFIX_Hup-| zV|PGF=O=#R8UEfaI)k$BVZuUR{$_rmWEDt8Oag36ke6iO8dP&Gpsw&G}CHgld0KZEfvr=R6pp70tGy$f-xO)>JL@ ziE#6bHpkqv-mpU*=i^57K%?}4nK6Q01DW5CAiq!>^ub17o1Hw#Rr=sFVJZ(`i`YN?19#`YLp0 zI|}_-mpJ#?N}Z+FP3#wp(otan;!MvMI2X4!pu7|%_u$nQ#=m>FTT2*rfr%Ryf@#B8 zi&N_MsLagNcqaQgW@wb#OK%#Ibw=kUkO zXc6Lqy`eMWhsF6BFfLQ=(aV%6bVvO{B;e5D(ox#ewsLBHu?3QC|Kgcw_%bN9S|4ev zQEqF#+F{MS+D&jS#lPsjsCWmP#Ht?KvxiW2g&NfpqKYlt$3V1(aAM=V0vnBfCbl75 ztF?Zj`$35s-GhO*MK{5s9ovl)5Sb9$1W24Uvcw9Y!HrwozHjXk`~I^peuFr+BfoUL z88IUK96Nvrs;Yf0dX?X=x9v^kj**L_(MG^q#_qw4BP?(!)#7 zbOUT_73k(00T9@uI<`_$re$bqr?p)|Y*yD3`c4k^N0yr8(hKS|Ae>Ks(cW@HNyX$R zPV=wQ;vSV&2=(6s-;>ar2kj=$hEj}UR|_{l@MDjipm+y$q61{}nPm4dNZaP(IB(d4 zIIM!#g?^USiCv+VV0WGEsPKwW1*#6)y;%O*`mocqr5jI2UFi&7SIhox=&f*PZ<&1E zFXFypRR}#^dRhAJa+tiOTi=Wj8s0|xZW;)wyk+eZlxeZIHCU5|d zQdfIaZIfd`rZFzfNvr2|Z8xLgylLsC;CEfUr>kjaf{~3?MSVNCfR&yQ`~$Is0-pM1 z<=x_snB9BM6nFS%T5YY?yWfR=7)7$0f z)oU3N#TDMqj6UMT0-+Mc@iI1mdo1_LN1t!$Q!T=L`1brV?HK3{h0vp2?1z50T4Y$&QQfh0FnLv>Iw` zrCD`gq%I$U!=tYt>Hb~SA3w@o*RdM$D!^U;U+`TA0{b|&!%(wWnO7UkF7g40!w;M&O5ZNJ~Py(BSTa@ z)nLS_uk92Ite@RdmkRn$fQSOQNP~4H#)BPG7qf&=T6C?>^Hx&9tEK!Db{2Gs#fOLy zaq?4-cr(eUK9@A!VE}zpLx%L+JiSA8dm~0H5T%{;*v8l=n@s5fqnw8H7~%I16|!TV z`(B!4?;6p!Z#rZc;H2m4?)c3V8d0f6Oq)4Wia-HP2Kh7I@{dfGxKycK29sZ`btuJm z&Cn+UUDx2@uk1DPsQ&8A+j?1~p2p3hZxrni#|Nax-#NANtdZ|PY^A4J*qZwt0TcFd zfEML_X&l3!qFvx3_w>mQxJG)m!Kn#5^~fL6wDpg3bPH2ZgmlrYC^ysh_-gh{$<#X} z*0xp6$WPQf>uQ>XBnDAE?YGqp(Mt$b4bkk&^yW7Qb{SMR;U9Ea7FW|m?1&BQ7p>Gc zK7Oya4b_T9{IaFBW(WL=m0eRvn*9Xh2+x{+POP&r;31A!R7YPhvfxmTPE4>}`)m&b zu{oh}Un6D+6M2{QVMS3BD^l(}_49(H zfZ3&CiC4sS1ABro+FXKvV|KHSQGK6ejpSZ^-pV%AO`DJD8AL5-OYnwLFFA

    }4R+Rxg0(%Gj8c$@xc|ql=C@9aVLJIz8^A&|?4wvfL}kJLQfI*{RXS)WquY z)6?54KI3G$4SRha^NC^;)EL6+!46fwBu>xL+CTfIRf)T82YEM^OkI*o2*oxW-j$aZvyhCPrtXh)Zxz%_@$S$GX>0cfHPh(qj~=T9TD=9Eep zb7D3P`1D$$TB09nfAnWF46G+`K${Jj$bX?DI)p&z>MQ2Jo#m-$5{~BQoZCn#yPmMY zw6Hm>v=g-tYLDueU_E{Pm%=K4f&4hpXG<>l^Y_0KBvSwF+@Z5-iAj&X~1)hEsi7UxhLf$ijtgk{J32Gr~d>fUXD7R6#_6QI!Eyffv?+kQ|DN zKx8mJpfzGxB;TGDumAm8N#INl+d^MIcb)q{4$|h^P()<+{F^#q4muAL>$?+Qm>}h- z9q~~IFU&&H#!uJnaOLwRj9BpuTdvbZW6~-i>A}h^(Xn68ro@=VWA+36sT7kPG9RuHRixZ1WQhi*F)MPZ4#&j$JJjpFscV zkiu!fadCW_;^nXA`0F+Jk2(H*4gSMIukQ|t;0`J84oUA0Y3~k+|H(Q(-I+8WF`u+G zFWj}gJCG$R4q3DHI$N--`g0;nHP1)gSgXKK-dMlA`@hNQSv9l&!%QE`7!)srCBpu* ziIjzyFxiJ7C?vwMTEI2woARQz)9a9ri6HD+%d;p##GgBSNr<*~2wFZ7o zivM(~yU*YM?sk90)Bp3GvQ{?##ke0GHwh!8gc`DV@A1mQ$f1>a)&jWw#S5^$TRnPD!x@N%zvb^r9&VAY*}bEwgh7MTDqCO7X@w{WpNRANZ>fft0J6^S?O#{~OI1m;a1}d|=75+CqaEHF2&W zw3fOuvZ8}&s(NAM^fFkJ1GD5JmB3n3dP&kNuvf~RY{=;NZx#t{v0M*XJRbb~n;Rgf zCK|&jF?M)7++&ceY2?g|vZf39y>*H|%<_ZXAUo7OLc1|MV+bHX9!!uzrU=8k^n0@1 zi39mlq2QLa-%^M+ufdQa9^;4>Gq_fq<`3XPZqj7b=4F>LBWk`rnK2(aEvtDYXw%;6 z*(N6WIk`FS6}bM@aN>91tS%?kYb{VIlhG z^W+dnIE$Xqot{fdJIu?UUnINUeMrm@Sh6~$oHt~!yurc~(9}_?6qSjp)A_>K1-^2^ z|EUY#!^%rQ{_;fC;DK_;u*)L+rH$2L&KDSJ&yWlnH3Z%^M3XY)zoo{_F=Af#4DpZW zii)deN`6{~@~;@iUjwGBk+;|X7z0UGvs0K;MD?S`LA7=gA!oQI9{KSb9;S?NBJC?Q z!zehsXy34mN3Zdbc!%+phE+j>Vbp=U$Vi#;?3&B!>H*im+Ra+d`}5lw#Sg4q zv}Pj3@C9c8W;VD=n_*PXoD zXd@W(QgiGSDE9D@? zL`{E&OOGB6LSz`ttrQdkE;K3k`Y_U(5-T}qeQcwv^;&jQ2{IAZd;NI zjLxe33iDh0#Cn9prUBiojRxwVUxv{kq>~5{{Gidu8%l%m%^3E>1_X~>Ci??rxNA~u zfDx&%T3MUtpLESe+!fR9ZTIqB9x;zz#V0TwI0S%+1Z5|3CU7so@?dYM&D9 zR&xkV{pwn#N1)(vR0;N|aS@7^`AZPCVv#4T=}lmX>CEYdPJ?(JPEa1G8m20QVKDZ2 z?@IU=a&zdJtE}@bynJu^`(2wI7v0}4I@6y{E-w5*6{CNV1Ct_ZReh?r(;_ZZvy`0F z2f5Is&<@mm1h57*M`wGSU68I74T62p3DnVyn2$Bp8>@`k!p-Q$xl?pB|1{bfTA417 zG=!V6$iR-Pr|4+?qbbr-Z)xT>tKjJsgV?^wZa5?291UEu|` zm_GMyasj!X=VJPY$olh_EIG56H28~-SPGV{ffQ`70bSVdqQXM)0>2=dK0EIR{P!%@1`vL%3!?8zr?C`4VY81BXNf_C;Z&muf{cPj~O4>Pv&5W?zsK3FM(>^%#7XoZz#f^fjGB>y( zx})U>6!T{CxvCeMqVGkkh8;URi6G`AKpTmh!3W7hgKVVa<4YE||(LA?-d|bww z%jVr86hEB+2JZ)l`@$ zeDSNiCfGtd-@TLt>D;CvD$VG-#w2wUT*o4b|FsEKu1QCz@P4Dp#^ioUUp)<+dyVm- zm2}zJl2vF>`RSuX!;XT*us{OdFM;zTdMw)H+!@iI868x1F2g>7bAX2Rp`f|(;c{|_ z@!_gsbLn+-zKv9Bb^o-;oNgUwNJ>oWvJJ)NsY?KLLrn#HLrM3_qlQ6~yV@CccIBdr_y+cCGarr3ZASa$;rnyNO2tBa zyLP))nLlSkNDd2THt2MIDQ|J=$~?{ai~==`>fD1Xe;E1X&V|Vj?Pt-FmSBPMa=j?5 zS1Xx$8&|g2A+(Y}3MsTsA?*>qG4AbdL4ypM;*_>PLu9jI@Y``CSAFcs@zmBx#yMRq zy6j<--X|&A0a_wcXYGAZq^pU=N3v9MxCBg&bVav>Vivsl7KO|LW@4% zf14kqOG((jhY$2PRoGFRc6AWh8^ajOOr?!C9CnC)>ywSRYPb!&m1G# z1ub-Uum)K_horo&xMo;zTJxskuOIY@GX~$#W_|?g3yBKSkllhmvqU;QMN82pSm{Lc z38yDlR0#_s0+U(Wze3zXq1L0g_)6(gT;;;2YBTyOB!U0xSAAZbG9E-ieT*@~4YFgs zAU&6xUd^3)Y}wLM#SN`2saG|$cy~0Sp{g%*1+nXv+usz~JH|2-c)-Y4R+sp+yDo{QuveXstR>B| zPnB8zfX6R>xi9cfmOKFEa~S^2kVk)2Gx>W(_@6BK?}|NIXFyTb z2gx!t=uHW{3^Hkc6cErGgEvOH`=4|NpPS#mr<$_UQ}E<{=DA`@U{Sz@BK zb{lMB!?($TPg}F#Yt-DPWA-(5>;X=q*vmF`bq%e={fHaPtDHLED#Hp|$Fmf!e#I4q z%Vi~ts2VoY;H3-dd_$U7L>$An_S}PejSt~a%Cf%Hz%jswN`vFT<5?^~$d0?)<{A)} zR51yqas$@}t&wPpo#L4jhEI=MG)jO2IN~}-5pl|g=c=jeV2StY_Uga=`N4v|hgE#2 zS;uZBo8*KEZ5m&a$m{0iJuZPo!X3yBmq8nlTKa>SjGpL-sjr%exEcwWWMK0!%qYZK z*3Z1QC^54hA3Q>^fH_dvOTzXM>Rs3^sQsI||G%E1;<%}67M}(E_rDhOe|>WQr^xxY z^_oA0)>RkK_$ktN$WWZ(Qdl^nbAQ!TI>ZLC9 zbfTkTA0BcJ0}ujPpAsmBe4gZ(8Z6B}5m@g-88D8;-+##ks6FV%T{8HhRag6^0Vn}s z2$l`h1ljHJ1ey3j+5Z!(+u+M!T`E!EB|YGQxic0B21j80$k;D|$v<`p4NzdlHGV|y z*T6zx@ks>oz-^j6g7hl^cHn-Z#>Eo>7=xx92W2Ns_}1TRO3{$w}< zlh!E&h|hnG0ppnb(~fNY9GKk}P&}QXz#upRlh#0>3Y-SBPX@3NZnGf>4!a)&;K8Ch zb_oHf#OxlqGy~XR)mXF!0L1|$?v%l?t47#swxPtFMxjR&nFs<+B2>C0{neLEa1=l% zEc>8EvsSzfQo*G9Z}>)!VEv8YX%suSwC0rY^>DDJw4uYMRXtV+qniDl*pHD>GC(n`Y)$Gy*mn{WIuX2AjUlr%IukyBJp_i4 zr5}VQB%RbIC~H(EENgTou)Nee)_!_Hji$UoFMo{Vqb&NS&2*==*?aWhrWtuDOn7XT zM~v|~ zn{%u&6cV_Y!sY1=5sm}4@Ub5Hewo5Gih|=OC=JtM71}DIzEkb+KQ>1dIx>J!VFp2& zt|9)4yZX`zP~5tFyysfWFn}fAiyd`#18Y=Sj%pPW5kALB3H zs09x_c>%ar+WX$^p8N5g!Xl_VxE{x}qWx0d#F+Kfj%(l7(PQn-Eh_Kmra4p^R#o`b zo{E_)45$Gl6aC7#@2CkmdeQy;kY*ivI47vNTnZqm>RSCNG5O;UhAT_8yeN)(koD3* z%_gqRYO|mu^pLIr>Glg~K-rY^Nrx{I!d~BPm0z+{WGUg1ks5T-HR;sKx4S1k>QGxD zZf#mu*0)vowE)A)qitQvd~%~d9f9rKwq-Q@cR|Tyv=Y)4Hti)j5aj5Kq&C2SO0l;B zkZfV&h+WNetmTZYI(BRG-G~X&#GUTrYG`MGZJ2kLc^pIokEeRVOLW~dk}qR-7%xZl zVHhMl4)!1jx);A$%&;?IoBmfB$@(x!n3k{4EJsKP%pK+l9q{5gbm*>;1$Ej3GU=Rn z`~ql|_;h8SKIf=G4j3L6jN43YtC~^kqi^MMmKuTL$nL?-KbFYG@4&67t*)4HbqaV| zAz{2a3ldl?p)@j7U(=+oiCM&p!h{U?$h&Okf6XM0C=BZAFP?1 zBV9XzglH$nYeH5TU^CBAd83bDy~BR%Oinh;e3kX4aKR|Hp?S*u}FJH$`4@!}1a&7}C2 zdTos=@(GKqCDk39+52`Fgo}C~zHh6MP{k~H*{t~)%#_xOjp&*1FRie}M*L_>$8dWI zw02-+ECN7z%C?)z4}{T&p)ia`JQWa}8zJmgreu*b57$)U+pSwQ^9sLK&2! zkF)IN@e|pd)>hQ;5o-5cUDFOjU24Oc`4id?(}gy?A?tz-$k5Kz;T3_T!Brcx%yOu_DT3DZyOQgWj3i%{4n92NRx$BRF$0-~)Y$w~vy-}w4XQ*jF* z4slLmG6id2eFb++Nl%WFhsKJjD4bx7HTPQMoy7K4Qqfwr!;lp@o)&&!&y&OehR`n} zAQec~qoRX7WJx>wWj!T?`*?`kkxM|#htwqmCabm|@WQHhu31A}gU6aA%-Yw(rUt&c zsNf|Z?K7&m5x&)~w)Nl1VRi?!>ep=*lb z?L#0NI4&cdH;ef|S)Sjt9^FDvS&Lms6rMZQCy|JwEtywoAm<;SabU|2TRn#{%-TnX zGak$%3`_qksIkVg&Tp=I@xcDoAno0`G(EEu(!IRe)L@k?9HRfHS=4v1o&9RO++^^& zWUo4BpPX81-a+k$?m8_FSNoq@)h0lLVjOT_wRv_lpr{7d%{Q)0A=s7ZG&!*vd^3n^(VYoyaPS54DW|e5)er+ zOWYyk+e;`1<|N#E^ZIKOX2W4D<&@Ks$eso6bHf(*{5RQE6eg4cA1l|74z%}UBMb{EK~=9;cEyeAdUgn z{@d5)p29#ya8A^Ne8;rta z`=#Mm@=h^y!KJc97}YiitF)z+ljf4iE7a7(`I{-imr_PjUk9Z7#FmB2zpTtHm6=21 z!S?c%3p3Foh<(GCCj+9%9A2uZ6wD@#IN^t~5nF@fOBefyR{4POT1c}h6_HwxQ*oTE zIIlWaDW>ix?Z@=T(S-zB%m&LLh1!9wC67(%8S{+S^(1MEmB%UAp{;6J+l<>JF0G_h zn_88tW^f>2?0;P-yOdFhuBQoTdmx>A&+hD`iRqU{T88~4O`NJiXNeZS{^vIJ_vKpV z9x7Hl&bdOyZDJflf%WAoIQ2`?jPU5Xs5AJ41o zmU^V5Sy0>xWr4ePO35TzEBrh4T9mb@^kRQ*$gVk38$EL;e_*_uVASaWO8doyBaU5T?Nq`dale4@pvKUG3dQwR!lIU&z%?>SwGe5;&ueqaUF z;N+iT(&C@;fuhMjYxgSLAk1lapGy_YJ3lCC_RUML*7P<@$joRVjfT9`~c)I*b3M8 zMwTWAP%(nj2SGSe4KmqI%2#4Z^e49FD&HH`EkiEi23)I9(3R|=G2g1qHelJ6r0CD( z9fOl`)to3q8;3*|I-;`6AJQ<%^VR;&>Xd(3z)=*Vr!RTHYMqt7rEZ8j5cmL}z#~=5 zB;Wg1Q#`Jb?6$XJlOwp;NVQ(Sr18KB_1#Jua@Ceq#cg>PL!`L!9-ALNN`#_MNz;R# zFFO{sKOME-f`a|0myd0aUR)3i>a4FVlASY~tG7de-L34J)So|^W=*TSB(#q3_iK=3 zp@*ch?(?&{nDIw>bL!R+d@i>AI{JAXy!q#3kwEcXHo1XYBEji9EPNvfXqez&U ze(2D=hH4F%N(G&qI*v<(*Y>R(E)6(-w1iM9@nRV&aR*u zs1lBlQO8xB#|mZxy-3|CRnM>@r-ED>e zw>Cx1-~Y8Ruk#zIsP}gHu;IJeS3$bQF{eT!bCyD_b$W0 zrhY_;A;*Rv3hM4@(Hq@v2x*bouJpPD`CPcwz6l?>cwG)N^A%5$?}CIL||2yX!o*s+Bk* z>rcs2g^bG>1CuNFPBTn|Yn+MGhtx85Y+PEUHm@nPw9^r|{;x@nVY8|}*w6R$#$Vyf zzrLqGkM@7yW7SEU|KMZ8zdT%MXv83i;hxw`r)n})XMkWsP_j#skOPvkKBw(Cr?hk} zH01AU36^R6n7MENAY_AB?3x5Vtp^+Sxffie2RS(gzkYpusSBWkjwL-r#+O-&tD^!+ zL0ZX<8>S`13#+H`^i7Q14)1a%v{rA?r=U$3i}m9EK}BV%%?nHjEcMLGivSg=aI}Wv z@7HX_c!UCkZ(Lp6L}^<$MYD+fgg?*}ux)8uUN)DkDCU6sInP&*w$KG|z;4OUtg%Be zY>V;R#j;qti7uJM`SZP-uE*jyuXZ-?HjQVkQMPNpjUXgv+ip-L2ON)R#shTR-YGVi zjIAIhP$km=0cD}J(IE?22X9gzl2AxssKC2=PTxyhgQCjh%=~hO$!o&7In)!o=Ut=j zInGdnf!I7~zzMA}?dTH=bJJH$>~_`3GHNj5dL`tva=ws^gS+8q5^nMDJs}z-{N)Zd zz1xv8Q_?!Aa5z4yeLgHil=r$S3kSO>ibBP*E78RvvZrmj$j+9AXNcqU0miI+gLy8bqWCN#pUP-;E@NwU7$3?s|N|W;j zr$Pdx)g^!EhEK&i#u8eY0`BbwtshPHB{PiO-ZZzY>xaff;@MoE<~di&h@9 z+{=~J6~u~R7#tc!kB_}H4jx~D7;0iR^Yfq&k#a#!lZnVAZp0;{8*B8^ZVjv8USKFNir()oYg7hNO;FD0!1VnRJauJ zpbzRYKPnZ8gL;8J?ZVk*s*3{7;ew&|%amhZ{kT()(}>f6(}W>cBIO^hpf@ef>vW@K zDYf4=m>GY{S7EOeDS;v3%FJ2fQm1mas!>U|_M+UMgSOuv;Pf~!BhxRF!U(#6ma?4|VPdm&G4*u&@T0CbJ~rPw)j2CO=Y)wOD(C95xu^OLT~QA$tP^F4W1-nT`{LaSNoCcW z{iI4-X@rO{Y(BSVuruy{{68QC(eV9{D*(JIZ09O0ZA&bBlR?MNJ@vOhR=3D0Y<7Wv zb>R33hBxRwqs7`^J8*v;22nP1vGV?B;z7vS*~sg^dZ=;Z(qK%efR9_|xk!)IWBuOr z=t3KfbyP^0Krnk)%1mVp1%+0M-wUJqG}@97KY z?eCl47-djKv@?i(9ap;DBa$*Kku?-9(&IFh!$9(`-skEb!llb`{#*MpMH;T(tP?J6 zybQ|a#ex*06}-jqv!pXgks`%)Ax+Zs${#^c3!y=B77`(xSczru`LU}`_ zQ;`mAt!}I!`|P~KcujavHg8|)hJ)NXAvT>BkFzSrJ>at${&8_5^4JQf&l4X1YfS!Y zy89gO`uDN=#`3{@rn`{MfwTooe;AzJK4lyX8y;ApubR8ynRW34Y()O}Q+;5blupJt zl1%+U95<7h(+_u$jy<}Dv|BK;;Jg7;Mvd!FYDG~AO7>AP$E4G{h2?6NO>nMGq@JbZ zF>Y-iu}Eu#4r7wJD233Gs(i_;)E%QXY1iCF3%z>QdM&(t4LeuReeDb&7N?DGyHi_2 z#jv4FWDYR5ibgEDjm|uc@lH-PbH!LID;51p&_H844DjdmM zjqG)~Q`d_R4dHzSiCy;({Wc_~XYC?eO*FxBr#f5lO3ffOq}^Ar=f=!+0zHoI;$C7q z^-Wz5n$rXWub9LAP-7p6!pkTcYt!?QAd%MR@C~r`{oB8QmP(HN{gVIFmVd1*J~zDo zXJwJ>;4rI*I+QyO8C8R0YyUN7@(44fPd#2v*xZLHu@~8uNk`U4YiyD>rGPFwJXacJ zat6cii$KzquAmgHMhp?gs2xg{O1x18s3Mp$$e75; z=13Up4Tttpx5iHRM6@HCy%|wzwj+|;-BET$rl@%%p21a7%q^JcjTf}jlPM1}oedfb zSo;9P$M1j;;O&LC*eJ)!b4ZO=yKhZ4>+0Ge9^(Y%lLlPlSUslmm{UKStIT3ljTw@m zyWQEuZ-&;LW4TCC66Pt3W!Va!(g%w;0~{|kywKmAY4Nmk-h=x6-q zOHW20BK&k7rUq25M8r0r@|(Od>TQ$n8r6k5%ZOQ%YKHTA=FcDej(J`cddD_( zXN}~VB?ErB@*g@OJ?Pp#E1e$8`*#IUjAM|9^(R-Yy$JWb?)4W;WgDln2OS|n|0>q< zaeG6)e#Ua)&-4A?Bi~rry8Je2 z389Sj)NaIdd;BJxPMyD?bN0PueHQLu6-j}LJ`>s4QqdKCWl1P~iJmBTD$bkVX z#^;F-|22cse#Yhhm0RO~ocO=9PL~dpC+Z?n4u}**c8(zAxNFcPR&cOS7IxnjXu)X8 zH%#5QlVf`Y?bZ&y`|~fr0r$1-R#Nxvgw}!RrSP3r9SG&D0e9q5j8Y2ui>?P#bB7Dk z^~r2?XXM+fi}UXv6PX+=4jZdtov*bd1mHiu;M0XFc+XrL_612HmhiT2202`AW%50u zBW`T>>hS$Pti5G$99yzAYKxhfSr#)(7Be$5Gc(wt7BgC8F|);FFSnPvFr5xvMYO)BExqjvK1F z^z-s13zJ+}oNpckREH8=7_J1)M_?!`cfS*EA3jdCNlPq{xepzI9D&KJ2b_tuV>i^L zB^8)$kp5m8B&-09TW!_?0Lmk9F|Q3fq4$j=I9hnY_c0(evv#Ba?Gc(;)+U^w`?3)n zExn-o^btJS*7`vDSN;eOZ2aAzEk03%3lpz&pa4P~EC1l-M*uZ$j)_+W5EpQO1BDB~ z0pP|m6PSAm_Ce!(9KIw49N>N&yF>-Vv38t>esO9|^ZROoW8_8HcY<4OLE^d4O^$F; z4Z%cU;gyF*3lyUK(2@gMyf0lI98pb(_N5&q0lcHyC5@f>rS)S%&}6P*k0ndDQ+Y4L zr2yb7+biImEY2v-0?ldvHw=#*8o1^@WLOl;^UuT}>*JK(Y;JiV&#Tu*kdVVBgjZWH z5x+DLvgLE0I8eMr0yxkj2c$ey^`e>3T#6d{fUtc9C)%QtH-R#kgn17X34L2L-XaV2 zb*k#^)T&|*8e+-HWsao77~Hailh3MfA||T1eD^@qy$6Tva`Dbj0Z5uva>=Uk%p2e* z@G1t}Ystuy2V!29gT$V+f$H3{K#oiA(8XSCOoK3241+jXj4s$T+74)Jx(@8Fip!5{ zxt9TYHJ8Gb>W?Tnd6)W^bfL&MTcE$V%e9TD5*j}QTa1r^6&N2w-Qm6tN`y`tCG}cf ziG@z~U}GTlI$|Q#odB-nLdh?|0fHa+gAidpZ{3APzjfu}vY5i`b$L;wTpqo&taq6U zmE!r=@|7%B9}00dd59kn*?-@Uv#R^}$@79pAYCtWwS&zXx9+2j0SUXK6mCMigR&#X z9aWHy*p25<5&*ZLcwm{To>8Q&YZrqO-*+2-^$x`(s9{^*WMSJPMt!M6(R!hntCAO_|dzSh9-#W&K?F(`tz+&wMcu ze9!&cmD#*W3K^f18{8Oltf+7otg=$E3u53W5>3X~LuU^4@O`VcY0ZQm|NItX^huKbsFXWx{P=K~ zpXVrYLAs$ta4%wi)m}phMb=<@K zY|N3#PNW^{Tgmrj)`IKgdR1a_`AkcTK9DOT5#KBYQO&@s-UTqb97V7y(PBm`i>zj{ z8$v|Iiz&t2B@UxzAf^OXG&;57HaUtYb3swM=dtQnaz5`P0uBe#5ve!SS#@ZXylJQ| z11RT@mJ-ox*|M8OPGI?1v`NMjyp2&--^Yq9#&2Q9y8mp?rP@i0}D zD2hli%Kp%^s>N>GUDeBP{}!ais%PcmpAjxE{>yfGyV^x?>zBjVl%^5AH}p<%zT;r0@WV+Zs#UY_*<18_1`6^^}Tq@z=I z=Y=?djygwTe|w$5&Qm#em+H;^Lb2heN6x`}eIItB=|u6#+Jd$so9yQ2B~#xulwZ10 z`n)uIT_3PFSQcATQoqBvbwWAQm=-mr;=`m7Ppp-?w7%phiK$zp=ov=e{L%+s_EeTd zW?S4OnnsIYWUvUi6AQfX-#ZCHCAXkZ6#bxku$J7ANlIvJQGz<#T$JD0))+M;ny$I4 zI>E}dIpQaF>*nsrU*ejC+|xcQr!AbVfL7>x&mBs=I*LNup!8u~_}==eg$SNBc}Cvs zUEXNdbAC@Ok|Ei|WLZ}!k*o#8Xt0jmY*#WC>9Wyxiz>xLJ7$wXtCmQkeN}#KvrXM) z4cSfe`UYHC8wvVAC0zC$@bJSU_9ksD5^$q^78G-3VM$}`3=C*MgCqlrpf`{t_6pD|_3%w6UyyJ+F+iU@%;zcbKFYueS}Po;3a%V(mWDN|QM9f{JIwJ2Td#oY`7pXBiSY;K5IObM8@K$DFTIzqx6zoJ6$EE`Oo8 z^Q$$6ylgdv@0(gNwO3^##6T7nxnUFDSmu}3b)({8s||QG)ZmI}nWa9*W=(>5y@2Sr zk3~L`@G6i3lv(PAi4zoUPAHZXFEL`i9yV&92Du(JpgH{IbsgauHq0#eNkMwT7?WgKtetXBNxYVeG|am9 zz3*)kk~av8_rWFV1>c&G-=`x|D#6X>f?=o$b13CLH6y(f3Wi%l*aYvdx>bWb5`0Yd z=YhHi{>rC+b9m7phiw9FjC=H6YnZ*#(<;iC_wAk6@i8`1pF)J6;45!jhtVGMDcbOB zJ1x?a7hVqHhl&bfK#C$tAEW0_B%E*(e2ewl(jkp3Kx^*e7( z+>mrm>eCmqA7iM(2M|TH;RhTjDZ1$$S5<7TbAUxylYDUo_X~)&aZ_`QSKFjlnM^C? zikxFWyQ9h?eKHO+tAyReo^wbph4P_nERnWeic3LzP?L64+HaCx`}nB&Qz!Sq`BMQW>+t1KW-eY;B;>C$7w6uVoH@ElF2 znbM7+^=wR(}(8EP=EE1-3%}zkgLLBye zpK<&7AWtX%9DQh$LU|K<6SEXsYR>oD+!K@TCxE^o$N_2f`C!TfG$B)ixJW| zrhnf2oCb{x&Jc}%fl(Pba;^&Pj>Z{iK%q<=!UQwtN^y)@8NXj7z2@Z+H3rZeOQ##C zYLLzCErGK$HMwd*S*6s?EPVEV|0-HJ5`*?+{a9>tIgp5`*h>d?Clc|D;3u1FK&H$z zH1pbNZQjT1Hfnd}<(b-lHW;(arc3j-WZ9I0cw;4LAO8rxjMvo9($;aVoz4bBy$^7N zubT`c<~v0vkflxUG(u4rF1jFpMtZo#m`3hnx}M0CGvtQp(px2_bpHw<=6NlOeU_1Y z+rGI*J9fr1;(HxOEp>&ksD1a82+(5H7_@yLD#Ffu_(q`MxtkhCSZaXbldutHK$dq< zAH_QKje0K~Ajv)0i$)akQ}6g@p3myyt*y31r`I+_{%5`K_0ux=uTs$?J!M`n7GS?3 z*dk23Q`bD_-5PykPk=Qm!0GQsy!$$3C_1MET{%K(?T3OZ9 zR~{x67d6=^TE@)BJoIQV&@*L~=lDFXC+jm*VRq27+V0JU zM;|EtBDT%OLgnxp7kz+$Zh}BH%lQ<*wwdL-pX2Iy{`&d`ME~5Go`#c_9?wnfY+#~7 zM#)0S!r&~toiC=r)qi8of>=JL>QK*3%Mvm7S3Jm25S<3V_tu8%N5^FJd|H)e$dq?tQ@<|2S1`%&WEn3Y?ngrUc!}hCOtqFq;CjpFTDLSP8N9Z? z3lyr)wcSZk(O@fS`52hnzS0g7nM=zXJT2suy>hW5`0&E@ zujn&1x_Lxhzcv~@Ieh^bgRPsa(ata3fgKfkwmI6E7m}$*N4R4(=}u`j8$D^Z;#Ak0 zzGCZiu=Q#g%tPlzw7~Gy4ROSR(z3R0hedL%A3B)8I`0&?Q%`&%F4}vEBUe5WWp=e= z`z8C=aD!@-duh?H6=XbT{j|k78C+vFRDHfN3QJJDNbiw(AC>YeEI#IibCgOFgptPQ zwqud~+GWL~ck8Ni%y5DY%lfQ97WOD|F;ypq4Q452Yww!qGQKq|Q|bl5)QS9vDMS6K zn>w}Ekn}5AZFG~{BYa_~Lz7=qV-CaD&}=(z`-(3NHKAvA^D7#3x{X+EOXYP{SWPzc zr$#?ab>;26>FrEqmo&(AdwVsSj_qy1b?>lzmQ?KMx>HSz;dR}c<|j0=L-l2UT$c1< zaegvjc*g84w5AP!a_@h~I@itaFT}bewifu~kQ5O9)*svwRM5ZEPr8k(C!XjP%Pl^( zh4(f)oG^jvt`{cVA>Sd}0gd?(fgFw;5tlH4zRkAnx($!_l=ztOnELqM+m9@OJb>Eg zF?hdn=e>@XLH&JvsC-`Y63({yRX{(lPU^#<*EgLa6Q*qlb+4SP!p?}npMPvtjB3?K zI)J}>*9sD7{m(5s&_n*88%Y2396MTwI6B&y8$0}Mn@`nN5gnv9plU6-u&0dSC*-YN zyG};F6)Ywilq-!+i-$t)m(es^Z`Rhn>=AKCjQf^52z>?qTd}o6R#W5@>aEt*azfx> z!wO#M|7>-2TrIGO4qKXYs(f>Zo!3PNWyj?gKki?p5dr-~%d%+ItHubb2*oqyS0!YkZ9jN$ z7e>pP?{QQ0zcUt6)4j+nh1mm-avk{Ofzsd0{HC=qPRgmk*L}`87g9>H%3WyBRI-ne z-1nDMx)>VYn%LImpetK=2_`%Ur(txAWwIJ4Nq$D9x!abc4o$q+BfJHwdTiRYm-vx` zMIw32LmH?z4HF9{^D1R>_>f7r-McLw%7#5;&#Oaigwn{ZUx^#AzQh2%+ z&akKIg1KFy_zr6T!sYuPZz7Hn*-wU`hI1ywUm=+Fzd^9K+utP%{sCXj)IEmB&)4-^ z9#PSsk8OIsVO&HoGIC4<#4W5cD4(@p5p)Z&Zo-%s< zWa7rvJp_23q&Ga=v9Msj+Te3-4q#aCI{Zq&d@bSa?tY5 zaMqEFYJPYqw72g}$BjmtxT@}hF5PzE)_RD)fStJ!A>2+Gjp~xApo)PzkbDC>xXic0Vs`sP zAb{%=8kQd2dh;8*-@kdmxn@J4AFU&Ql8TkyHsVb+JF2oCsZHV&-+W#riIrQID z=LDGonk;{2yFwl(bLh`FT8g+r)%>3O9iJ!cDm^Q9MB2VRSmuP@!sM7>JTcdbw{?;^ ztEQHhe%2?<1nge%_g>U=QlyD42)fb#zd={j%Gl*!Gw*NchKqP>JGF~zprnY2ibPV$ zms)v&pnC)wAeHmvT4k*J`EDofDj1KrANlp&r{pUneRLDe+rF`E9?;y&gPcXc>uUg% z5#$Npxm7L4+I(%kv3g&pK_Lje2mm)`z1f7`@C*s}TO*;8U}13A7Goo#MMQd&9-;T% z;I_kfEpgW1$j(W?$S+I@8)J^dH|P$s=ksUTqjgR?aX1#R4!n9Ud>977T7$Sw4) zlT{JL`D1R%k%?TH*f?sy&&Iv<2K37O+oYa4WE8#W+A@_N9a7tjQ*6iNx}!ezjI5iW z<*2dP6Jef$2^lq+JsmK))$c?ew5+o*}P4ACXVAN}Y!_)ws+vAnVwkZ3yP z`S!uBUsdx+!T`VzBj(PA;O+aw4=s8e&tC|eW{)|K8cbSf^*S-koZ%EYSA${D>E2&^ z9_F42y~cyqPoaisZdGID+i2fgewe6aEVN9=5*$d}Vm?L|9LBAw%T;B_fE{ zQQ2|@HZ5VPZ2CfKHmbx@!cZwz!R-G<@)dJ*2Tcn79mBf6*~8D`nQHy!HH?nrcF0u* z)*vo41XyE^vb_8toW9wH>{QCx_3@@vtxvI6)6g6S;eSHduAL%8}mgHf@jlW zfAQk$(7V*Sa$plwH82)nj!1`hPK7!og$9doV$_mP4w!aotu%cX%u9%Xn?B$U&gDGc z4YrTCo51FToCI}lI@&F|X4Dctjw$tZ+@3HpbxVeuqf4JRd2b)D51}WfUMc9Mz8q!; zU%^+gG~em8+%6>fh-kDJEoP7g%6-Wc=Z8*-u9kz6iVH(OyuWUoC48q-vl8*zGrYnA z)-&*pcktVb#uCZZu*c~>Z(nz@ZbGuBr8&+@J>e65XoJ;(fgF8%zk#ZQdn^xWCLh9^Dx6w0`n9*1d`%|e+ ztkHl)4z5keAJuT3%hi1+!fgE8B_)-n;ayI(Lwwz1zpoc7V{+eIDaq5=D7ug9g<&=;*`DXuN z^MrN3gnz$@Cp-u!ndlR z!-n~B&N+J@00?BLA%nSyE;n`?avr#;H9+fm&m=u6XK2AMPkFa}e*Yue(i(BF3meKj z!86(6RlVEaP>nPAb8{LW2YY|)uvnJUvW9XId%8&E^VdlB-q2r?eKNp>j3K;*&fLM4 z$sa>OJXM@G=31_6P&eLFDDNw3>g<*G^6a`Uk5_xK9mHGqJnAGk1}F7Tqq>1J-oEDy zu1Ki@WtBWRKaP!6i?kjt@lrwT_r83(J1Omrm6f24@?ty?yDsKX%PD1Xs^*evytugq zVZb4UNFs}oZ=LxW?bu`1blSQ(nu7GkEu*+pnV{jjkl>3DAELGUdG5u~{UuBiDwn7` z)CZ*1s1oLD3MupZkNWW)-+1V1%-`J`H`kVw^kkC=RX~~9C&WLEMT+?GpZ`fs{^NkO>M4G?%oz}^n2t=}I z{mFgxKHi}HhHt`Orr*ERfccl|P1@lf;9XS!wPN&3W{{%$ieS8GiozjRX*?j9Sf=Aq zrV3#j@lZ^?c(Zd-NoT)|=!M@AM+bx>+jO81KfV(jZBgVyD@1%|ww$c|wy@!Q#&ZBd z@6bC9C&Qllpn5Qnu?8d^oQ7n4e?N>0j%E*Y8ChUMBx!VcNR0I`upV%dz7Rizh{)?; z(-N$W_~4WR6v@*vPN&6aMtUUWvO8~x)_D_32Z{o`gTRwk*Dl3psm8QCdE3{jU50ET zg;ZSplMyqaduX84m&|c!)y(p6;Yw)Lvy>V_6f2Lub1CS6MzrbC6|kYU)QqiBK7Mem zuhmS5P>G`-f?F&dKx`&0HMEH|_WR+|KU=Dg{S&EXHDogTfK3rL#m4?-U}1+pxa~}$ zizY`)h3jeNAcq?7q-&CI&jATkxHV=_terDQdzg0FYtTM*C$$u^TJ>U`31Z^R_`4SD z+XEYV{Q=O`o2)HE^(|&eK%mN9THSM4KG^s9cZud z_zN|XMI9vb7YcX27P`uR3eRcaA9kYz9Gzh?qOvL)y8foXKOsl2pnXiW;y3kg37w-_ z&|F>Jl+iFSN5+#U6E_w4hxE-%uE{V7^SR5Jh=ui&BJIv*aYu9$G2&w=#YY(_tve|w zYsx7`Q{$MgopVoh=yfB_O?~!>Gtev)DsS!RKzwXezOUgebr1S5S$DD63+ikDJSRXVEMnf1df2Ju14w} zqZcq1vRT&*!jS?84MSc=rw3HY{ew!#R(|h$r1MkS34QEFzLdoMo@X1>l#k4}Iq}7{ zi^r1n=544M{hh29i_`2oE~g*eiGk+e&Fu-jMqF*opcX@71O(ug*^VsYV-nUg!`N1N zC_AHW`cPIVJuB9^QE#uloK8!eGXUO$O45ffNf1Lahz_X!q;aK`idTU!3$ZBsyOxV2 zi>~n<7s!L_b)~DXrct#|mXw)0vBYWUBkBv!K4}>!&9)6|G+Nr=ryG7Ta93|W>YjLm z+%!wS2=Tm5sDI$PYtl#7o3yWFp&))TADQ~Z1U*6oKO{l=bII9lq#^VKM;qd*XF;u8?r zMd$_a#ffTloudicc*BvVN$bv+}_5%^&qok!AqhxagiBvD45|6K1%16H9%97r+&c& z++b?xn&CMr$#?&R{SDvqpP7ljI}v|f&iyypSjF7pf1?-UCqOo>`Ul}LBpeLn4=gPR zrVk-vBvkU<_+p}ew9#6)n%n!Ija~_VLJ>2gQhYQba|DT|ez9j+;oSb=$7b1elkGTh z{&aP7Li0IOOSv}JNEwuhJu%kQYYVpw2v}}Mixe5bX>rhKb(l-@G1Jr=3M4~=0ua%0 z7MQ{H`{=1S#*Ed11M%_poz=u%?}k79nWvT9kkYHa8Iz9($a9Denx(r zIE(TV0xH9$-K=c`m?E(T%3*caSfcXjaVeg&jG43TGrme$;`gx{e_S`(&X);3qEfld zEwi}Vw=m%3Lf2&>WYaRY()EN73QVwhC_W08%*#gqi8QH=A#bpyxb7h;$V8Npxc1Ns zO2BZ)eqS~B^TllZlb#XI#2RSPDAV%DEulCcRBjNiIx6c_ucVaGQ)?%GpjJ?T?4m^D z*4mm5cza>XchB4Q(aO451y<1%XqHU1T;39U1adaf0p3b1{B-nR6qYYr46(Y5nQ-v) zheheUwpZ|b(G`TbpGGwz*q_Sn8HX6+e@nd3DFzv>$gzhX1( zVSSAt>$?(prZD0EZO4M}sbPxlQfO+DxIj1$S(a^<&N2k5S{w-)m_dLo66%0} zfJ!G`qS1BqC zg;P~(Ql++I8YW(8gMv|q1Yr%2=G_=7&vQ_<4`1Tf*pIWfe;jHU^o!A~0zC_4kbl*c z`)`P~s+FV5Ke);7x)7AhLB!fc{Ss`?LU)ycIVhh34-G7XJujce1VM53AH9t(oL_N& zGLzp5!d!0BFzdOXWIp!Abt21=?dD~&2L0U+Ew)-KP-D*VkH#D{xRi<8yEh!p9;ZGr zFjJovhkRsFD*I@y^gerd+(HI3=UI{W-tfpQ=Ej(_SVX1(pv<3bW&aPc5SqRAm)WCv zl}%rn$I#Zw;V8XGP9mARfFAcDw6Ke1KT?awZk0GwtQsd+4nqV0S9&b>;SqXu*ynI2 zPLUa)wsMv!;%M9AwRgZ%H$1z*msO?{*O#xn)w2echpV?XiN;>5&U;a{+6g_?r5{9! znQXb`H6*MpPwwu&v46>FV@o$@LMXp(Ex0pv1OvX*H=#o0e{(w?h+itPlI~TP)a)Dp zrSeS635-1v8@P(XRV!lVN^jv{ctbU03eQ;HHBI9_XWcbyVOgk-*4}0_{gCpv8CwTw z!#0j14Zb2M?`ZdSnjhV$qu!fmEVGamIWr8rIX^Oubsev6fPb{*?vItX%6 zd0lvP=F95Q>15sEJ#$PK+krB9-dfpgpVW+b3BJT;Y1jI#7D5JtaaO#yj=ZY5+RK?E zv%<&eXkfL$*L-4010X6DxdXuOxfE#yeyF8MA!uZ7BMm+gnW8QslET}^Nr%-2JJz`R zJ43>UMxW@w$+~*J1bX}tz$vJyf#pOuGbWaeW9WPb4Ye~2DJQn8CZ=%Qi>p^s5@By9lJ@Zzk3>k-vehF z-BFboU1+#;PZ1@ipwVMsgQ(a^j4k?BG|D&mEOi{BR46=X(LdhSUxf;no^FGk3>^q*&OJmLYBjQx9 zznv9ib4ddp1MwdzpB7krC1UU4!l+QhiI4lSbEto0b}?nxo{i&JJTHzji*?v!SgGLH zm6@HjQQ62y>)U6s@_W9~V&{CwmO0PjYAp#WFF094572+^XzFP;7l$anny9juRXnbr zE82IM$C!5(jPD{(zNur2Qj?@_9gXgF=BR;0youbj3ncwzs5evP2F2{T8iHH=j1UCC zJIwcWKE!G`;=LTrCq4QVKGkE1lWRLh;PPxqud{dgIoTU@Y;yR6#5Y4DegAw+LgSk? z4c&r^D~wsv%=}I=jIeJ{hT-SRy{E@L2cJ|JX((fsna#`Wk@{T(Q3S0?J(kfw;cUB- z5*>+Ju;3=b8?S4?dv^7lLs~Ku^+uo&vC4boxhXbeBeu!?EDrMGe8AMW=R5b{kb02B zfUdp9{teY!Y4R#r5L5wwNtrcj6c~%r_P`A-@;)USww`>6whEH7|y(~5cyg# zun=fQvY!)nmMYo>tbq;>Eh4g>8MD52LlLvtA>6PYX!NK3qzaxspt*0Nl9eyZeA)f$ zYu1!qyR#2(jt`xm_Qyff-xp{8KD%|$L|~uaZ799}1!}wj=-BNRoT31_9Cp)AnSfP} zwLG8#V$F1|dY?XClXZ(PoLu0^8#C;7z=vRRgjiy~`1@d}PJ}SrSe9Fswc$%KKqiiZ z1vAbwt_T8E-@87N-~p#BAcSZ#>7Wr?mYEbY1+EH03626=mNEAar&!Uf(Qk zP7lZ+j}13x;L;9oz|5Zp@=QKp>-Y=|MPM@VV(F8`F&MpM1*EZc1OZbJYD~Nui80o? zQSSTHydsH_Ny7=Ka)Ns`$RqBms733lQUZ-GD+GJeW+8o5;s!S;aH8^4 z2hu+#^e!W~Zc)QkZ2f>cxbEhEjO=~7qz;8o?}*RC`&eBI1rh~A42{}?hQq$umECfM zOY0$rb0?Yy`od{mAJVpn0&N~UdUaY#oe(FLp#@DJ8Tu|Ts)G$_^pL-*X-(GS1HZxH zZ=JveUowQAU2=pHIvD^B9`k$s<(7wzAHGRKZ^gkO_m*H9KnrRv!3(N+;RNJe!n~>g zl^=_HPk=9QFIz(}0Rnc}GolJ}bx)K;^3?i5A1d@`BbeJg7o7A~QrYv}BOC#-F&r}j6Nz)CH6rmNP((R8h+iI>v0_2gFk5wAz zuq>@2785 zz{u*{92UHjM&g^j$fKMcaKD~ThoHt~0vE)3xmSCqF$!O}twP_j2^zd-KJ(RUp=F42eE48cDYwCG?Cm%(chVbmXSv<$zmXPuEg%sAC$mybB3-vC(3G6kK9_FO@l-3U#8&?c^5#SqW*E~=ktV`<{!u=t`1J)O zknIKOdeH1 z82d(=*Bu^G7PJLNU0kp=t58m|GsE_Jbs&srzhQygLAD7#L`89Y_F;WyyZw!H=}Qe>pVg zJG@!@!Gy{?UhGc1=HF7GFM0XEeIzzqh#s*!f4E72Vi6OtzEv)fZW7Xm8-`ju<8)g| z1n=%~Ji^^;MozAE)Wj8$HX&mG~8I!)O5G7V8eH;iV9xfJYA^rZHbZUuU4GnJZfr}$7 z$U-$9%cmf-?RmI*Ds&Q4U$J;6S@BDJoGb-;`ZYUe`dz=br_n(QuGwS zKt%}*2{$XjVvaYh4DBY$Uy7uXPDhaU!{URANn45pvbl;?XqZcIt}l%YOohM{tHwey zFTx??-VwEWd1{WAPg!X+)xJwtn;0oEuKa34mKn=;{j?_EBk_ZIc4%%IfTy%}qQEt! z^?I2PmGzG67lk(6v-tL}R_sYIb52ghMNm~{x)W+4-kKhVnWBK!qSo&b_XajCa}#oV zqm|19Ft?e3)BZ6DTl-sB!7?>6JO%2SMOn(SIH!&_%y@XBPj8u=P+Xj0_`zy;rGm}v zKU9%6^G2$YMbnJRG3VNd_xs`;cxV)d`rMRBfU_Dl%r|?xx=?VVhmNdjpNZeK8B=lg zW`Dl)1gSVMsPMHqz%1zJLWg`pNo~*m5#FX<*d#MYm4Fvtnz>b=eQc60_K97i*#o9& zliVq-yB(qtpW>Lmg}$t+Q*&z~6j^p{K5^p^WgeCy? zDqb8d$$N~D#BcA_=Blc+*&*g<$Z7zdvRKfR<7bynz}{({)A->#les3D4f_x-m^rKn zUfhkuq6M?Et{0=?emyGMscu}r#g!}p(3?er<;9T5R=n5y#C3BzH)s-PZU3Vfd}#=sRu z<+$#3F!vgxWB?{M~=XnbI#vve+_OVObjePOKqy&9#pJ%}P z&9K)`Ms8yyM8Ur6Sk|)O^4{-$X3T%%vdvLgP}c_>rR75Bos+rghwxwJ7K+Qb1}w=dXR|gXy?D)VBw6rRBlt=J$QR#=KJ)(ME%M|7dZ6u zj{;APJsxo#?%lh3knrz+o_hN~7kK}CYF%C5byfmH;N(LBLsUO3YzQyudxD`M zvRHnGigPJM2oSqjJ`De1xSmd1=tXfQOd$;0;7Cq%=Ho5&?XOF?wG@o8ezJ7j!5v@C z71zm5Uk~?(UyHwfLT`a_n)@;B0HQNl%UNw$9Y(YijbN;ei$i&6#;hR`s4@DPi-dX$ znJq&`KH&6Y_m0to@G*`>GnXXBvs92p-O%nkD-W3Os{4gyMN;ai*G0qAYB=c+AU{eB z(6VFN_9iPUIqM2>*5+;_b!j+bivzrh?Y$iUzs8&>aJqbnCif~j_!kJo)!tahlgjJj`TdSKjA~AAd1sXTVHk9zITr3aa@>^0X`N zUA};6>D0L`Rvk5Ye{Jt!A#8J4v9L^-xRI&o!PN>iF0@&BOTe+a%%dVmY=Ulm%PHbdNKm2e0`x zU@%87y^g#@SWP2&jvIP2U($O*Obx?}$S`y( zDVc?8zoC~1V0dd}y3GkwXWpg*;jHk2FY`=H5S1s zA}hJ5=BV%T4wA`^rwNNaU+b35z)#0iFN9-(a z(>;x*1lJnJOlGe#z~MShSle5wgjUxpzFP^<(xxM&BN`Tyyy+q6Cvnj)%dv^qOzP>F zPv*$=_yau<7cBSRb5$0Y7C0ier?N3iF>h5*SKzw;j6}4*C_9|@{>n#qo<7vBpEwaZ zS!#5*7EGu06u0ck*UzmF`=&oz%ddR}8MF-zzurIHgaTNO6>-a*WvPdI@Ul!fdC(Z!R7K(2uq1acU&u4nDZ^VZ3cSsr}t6Jin^`sCg$ z@<8i$e&O&$huMG z%P$%_9~xjIu=Xw$`7B;9t57 z5(ol`10h5U4R?1BgR!RtLiU>&fhWo#RQ@r3pB%qH8vzQZ4)p!cM1=C6@9-btfK)Pm zXQIFV|5veTASx>Uj^*PYv5a-N{dF)TbanK~g48Tb`kR6@bR4ZA3Bov)qJms?6r#iy zaOA=KSjw1_9cVb2zb)hfQDMG(b}%t}ztYnUW&(1Mlcs{w_#-rqglM#I(88<#GBn(O zAKJf$19Hbx_+Qp|?*Ha)$)WxiF}~C1A`gbqDs7-(lib9uq_3m+9-bf!V;3L5JT=#! zHCEnF5ii_}((}h%OpgKGMS76Tlbp~n!Yu?jJUKWtI9>QaHxk7coTWd4#n}$O`>lSo z`Io^G{By9Xrp9*0ptp*N`QK)C{|B0ke=1ioLPAdWLjKyhY{b&R3$ z<<`0e$k37p3;mA?prN&ciVZ4xu$Hrni9&vW18AQ+fH@pC!ARN62n-JzELsSL9bKv( z+&jqVBRF`)f(O)oj1eTlAIl#Z*MV~fE$%lI|1%89{;_-)a|=6jQ#U4I2X75yJ9%RV zYYTJNzin{*PY|Fx_BRnO5cW3vJH{I3w$c7A_Wq4H7j7-@TmD=F0g2%zOARYM3$z6c zc`!~nyIc?mb`if}_lIpE=5O1=-;0KZQNuE~{2fH{Uk33%v#5U$Le$RK)%EW&{Aq+` z;qE};>TY7}V(Ci3@+TH!RQ`Cw3uJO{^gByuh(y8WK_bX!;P1hP!okA%%b@3B72ZSI zj}^N#HWqMKLu|8o0ilQK#HIGg@~%fENe88cgqV6~a=!3d?mq0ug zZ3s>BOF^&lUTG1^kQ~H;>Qn+-w^L89)5<&0J0B4j{acG!{^wR0dh|u-@qR=-4rjI? z-fIMH-4T*nLc^zxVe@coSw-A)jLdcu!?~18kK?n6YBwm)IJTl4jK1w(DKqh*CTvb2 zXm?$wv}7k0G7vp&muuAshohW*cFr%8q{x1T(!lBD-N*vHNy0VLDNU-0F~hq?Wk)Fc z`A>|vtn!OVRZcbJFPZrT;Un3FAAiLarv9quP5ETWE)LkPZ%-WW(A)3QUEUCF-JYp7MO*7#T5%vqRB&lq!(D_ zLG|GSDBHUUjbOeR%Xbz-CJeK=$pBy%zrUD-zbI`pE zwjcwsAsONH(7dF#%mXzc3^2Xqx7-7{AQ<60Xf9K`SHUh|I!*#ZhWNFHYIAx9lm@pf zP!Ncqn1Yexe3T;6q1CzjK>|3+00)qL2r?Ug?^K^tV%}shUVrBY$zFX~D-# zpHLkx@vf&c7;PoE&e>ZA$+kWR&ePh33f)}-OCky^EGvupf|# zYINdoW`(&lZ#owhJOwIY5%N!bI ze}Kf}_=py7^HRM0!c1fyG?JTmV0DQ>!`=_6keHJ?&3kH4@o%y=8Z5yS14K4_epYblfQHxhhN`%gunUwYhvs5T`O%oz~F@sgxZNUu&i@l`GZTU0Cl}8AfIn_st zD|4>R51;L6cDTHn``hh>KD;z3i>H38@ie|}58uR(NX+=$+Mlxenfyvm6fJhxhhf9? zx;<YzzdNHXH9tLW{Xx* zv$7yZ=-If-5^HQteNr>+p^kt9RO4((PmxxIY8ctJS@ufyKL5}?=GY)k#o!3u>_?;WDHp+P6Ufa3#a{Wd2m0@yK}LB!z~h2+S=riO?HZ(^JSYS3VCAnk$A2k$0jPsujTntt581E-t4x><`SydVfatC; z^&%u_TnglmA|s~+PhN&zHXi==u`x-nJFQCu&ux)%GqHw| z2xb|Lw{R+r0b!$M!S*%k5BBqm2REPBi=S_Ji|-%5biIP*!Z;ulf;A-J|tBYSTiA9K5wiJnWgKqR5S{!PqL?QzydSMVr z*z`4kb(ImLh>OR{q4&6acr*CWES1L4%^YS zMcGw-wp4WW$l3%uMZLgCYMwj>pV(r^l(V~V$>PeR=v~1qTtC;T28D~F4}JI29^3B@ zA{?1fiJ2Ji!LQ(Ex#*}ZWAf3g5L*+?gy)}DP1qsxu(#oa9=u&(eBa5VT`(#FV9$U ze7v#36i$Q@#0g~{1=A=@ZiumeQFg=L#M4AkmZbf5}4P96(;3{Fb%P+KIK zv}L)Ga$}Y-Hzkc^5~y9z(e97`1U9YiO~9D3MeQ4NKoQUtCMTFbBB&JK8h4XZf{-V) zR47YEM=_{&R62Yv|0R`SfprPpFR)GiOTh{2Mrk)6pY?~=e9E23H>-WThR5VXH4eMn zPD@(U;6^k7_GN1xHdtFT14TgBrCkJ)|Nwu#Tgpx6K zk|$icUy!%baglbiQFfS2tu_a0x#Cv_Rz=j0r@-c#154&wVwJy0yc!=PfR>|C_~Q%9 z`^wDCufOxl%kN!z?h+dp=z*%UqS+mH>q9qzu!m`IOq|x2)XM|pMnwh_%HlGn<(t**$(bv(vd=PHr0$J?R9eM2SKQW& zsw617lVYF*mvhbi=p*ta+>lH$gYpvV$mX*D*Y7$ozq_D@1LT<1!~IV3iAE^0gmmonuz}RhClgpW)^@Jl z)~dAc$F?eC97Y3&$98mR&0bcliYIl7%a%T6A6g>9T=u<>QO>NGt*w^6@=70753w>X zWM)%ugO`Gtk4W_np>RWQ=TQ25A9FBi$O|qBSo{jnn;%ARKP*T|6rHimXv_ewwwaI^ z*1Wi~9s!TYND@VJfPSCvQ zw&H9EGd%sLoWq9_$dF2YUoW{MRMs2)IN{#!jZ_)Nd%Bs)8>5yktU{KsQc0wXT#TiB zZaSDh9`zaYMzHb^zu7h5t&GG@BCm9yl<2SHWLotUMAAqiInRV#wQQTknysx&7L+`4 z8D!kLLa@zEW8sUE8@T1)+JBq2uYqYR>fMon=E%mY9yBJHG)SXa_X;q@!;Ylc2a>$5cv-i(vyWUZ2vr zkY;5&eqNyhU7y?u8x7~Q*rxpEL=kQ6`~izc(xZl4F0-eIZNX)hXC*Kj6s3QS!p_r^d|hf?f2 zO9OMs1H6dv7!HTX9F35g5!d=HtRkS#cL4$D$$0QZ^q)8Fe5#7`v;YMf-orIkNyo)6 zL>~H8FyQ01+SjbJp=Q+P zZC`gd!d*%M?_*9~y}=w?drIYUv`bF*zZTNRh%V*2$I^RIyoltFk(ZiXrn(BL_yW|& z`7se)+)Bms7p=q3MWlyW2-rCVDCP0XADF?AjsUp!)sHtCuW7?yN$%Fd%W!Xes~7Df z57_o5!kPuoXT0mF*M#wCXokdq;CMG5qeM+t&z=JgS?nZSEd;W8PfgN;G$eP~#-d~_ zmmXrB07>O?1a)i`ib_$^)#mb5hn{rphzlQ4;|7A>(r_Dxp*OZZALeoL&0NZu61QuE z^%6a`DP+efG1XM>l?X4(V?KTO-Vcik9_e}p_p`O`73yft>*r_ix45l)Cl}DA(&AoW zw9f=`te*vwUR+2LO0-I2;!T{GS~SkjxBzqSW|0(<4R=u(*bMr|9fWOVZ9s1LXi zjPpBCNX`ANPx9|W>MuJkM9shv9kf`ccM;q`SNF+S&o;8k4eTsp6K(SDM{8dx#k3~{Er$+UM`|BIDsNVZVgjS}$KHDypuc`6pD1?x^w zWRB11XLJRyJWXxl*{ag|dEH^AJrxRWduvLTWR_L?Ko6)RHmk_;?5UMI=!0A>(8s2T zAWa`{vRq(vP>0k=*5JNDGw?h8kja4QLu0osNR}0QhAdWadg=m5i>UExT(7uZ$d(`e z3eToNlwmukdG`^#tS3J62V61PePY&10#1^BwDA+lw;%N~s=o+vD}qVJ*SY{@S?*7-TuLdz)k$hsMYBsu!ryuD$DV-(xX7iBbZ;+o7fo z;S9{Si+2SefcY|_VvgsC5*7N9W%T#^x5+r_<;^}4qZg;1lRhG`f0Q)YNMQVmLVEva z?K`aFLM8(!{C*3A|1Y)4f6i!Z|IBCxE;FDL&^+7QW<6XmOG<9DHQqi(7-=P0qz2YvZ3S5<5}w0I zZuwGQLK(obcLXt)|7-Dv|LV6n)EGQ$*F~E1{6g zXs28){n5!hHR8SOO|ACCu#5DMuXninHvv7muc^|MrR?aSoVO4Z(}*$YdOAbN-p$6F z5-83(W>J53)kYZ=Av2=trabs<@|u!RW-T+ZlX@0sJK}z}d@|w^O?nHnakGpVPE@>Z6NXbX z7ah@T?=X!LLGNai#tpw`pB@%o7VO^R zF94HN5<&D{&}xup1?EbtwW3|EHIu19md~;6Ss0&4F|IQ=zzTfwrUFtXA&ah1<&35d zZPVED3A2q!Xj#{Cac_Z4XgD?4s0gmUqKWJwppU@_2J(IqKOv~dinnKhJxc1H2#bL| zcFMSuIFhr%7_VGakT*%+r^Md~k6CUJ`WdRA=aAY~(G`BQhvksZOY9I7O#o~rXs~HQ zzy3n{ElLj=vW6J{e~3T!zv54Q^tUP%%vz){h6MwM9U^BlkxpdYK?qXDwz1Zb#(t0B zA!a45mVh@VUoleMvWSH+E*hYk%=AJtx|+ah-_LYqf#VaR_y6tiu9#Yz*r@>K@>Lzfjd_ zgCC=2B>fsx)$V3|hAtpkqsA&PR((yBpr!9SUekx{u@+d|Jj{~FTVM07QK}%Y!iDrL z*_d;eKKb6jSTDrI;>FsT2?1RKr%|_;vq`=R2MUx9i`fl2zcmuM)G3?EX!jTtb+ebZ zTBjlcHGDGIZ?Ho8<)T-xs~x2^0EQnc%=Gm0vkI$!Dx(+eMH$ZE@)lPl<#f?@PT`_| z+4hQ6P@`O}RCO6vC^e2ReP~-R(Rz?0jTV1s2N=&ZM`$7yuQX*~uC6(UtbTTb&w5l> z82rHrz*G8oy>3Ost=oQU=!5G$ahhO!EZlB}+m$zr*o))gZ@d@3;3}J)F^w2GVeJ<{ z>H^nW-Z8y)P;8!8wJucZI+^FHvWFoloXiS^p4a?oB+A*z-?ylbMT&JtWKg_-;0FfI z(K!@Nv5PQknytudL}#pYqf|IKBIG65_LRtFuG~KV@LF#abHbgu$?{q;!jW)Tr$1bd z(Do{VCCc=wf*_DbF2eqp;gyOaeW(dlSiCMI+wt(%Oyho-Nw)L5>wjEaC%)c2)&vD= z;QumFa{Lvj38OAEPN;)jqx2tW3mr)+p$Kf>*_#az#Nhl+lcAa@8-7zCN2@X7Wdqq? z#In7o3{OFq8S-I%(F`iULKK$po>H~v_)dV!3i`?&tYO--XqPqAq;;z{-q1xk7uB|koy?L^ zob7yi+~^I=$f)N5psBgF`zkdsST7`v>a^JSB^FZ0ezwDHr?zvWST_dtVVKij(2{Dp zAX6FxtL&B!J@o24wy4R+LV59H7~P0VE~{sd%paqg7C+y$z3Ee%NzJ5>)gJVA)m$~=W_WMHh%nF#ag}I#SxyqbXs76M2?uHz-9H+ZNI| zskcY!r7y-*ZwXgK6~PY{OA_B)16r+b;(tIdh`EM-5#CH^%p!LS7~`GEVf|%Iw@)CK zuPk^}-e!6j+OYD**p(S^gIR|EnzjKutP=OxF&HWE}2K|9*tdY6;Nbr6R+=P6?PesELZekVQOZb|q0BroiE&wM>VT&dTzs+DX|XFE zaaZG0DGN7a9SlSTiG@^qn*pITpJ6-7U?wt(g3;j0!l<{ zxPW|C(n9TUv}fv$f^z4a(=AEs%AqOOGDFs=Ks^`oQ@PJQs4&;VT8SxD)l{+pSSYc2 zTBx;GD6kn~UZxlQ?03A@m05U+&+2NR0t@K0UAG4SZ9jF- zWn(;Vg}ekZ*=dB?K?K3g*N^tlC?4*xz_(trtT4B4ATsu^9uQQX6R6irAh-wiY5e*lWV;IB2&Dzv3dxLaSe{`0pQb0wWW{A?%7NCm!F!v1k_%Wc(k!g>| z+dw>Hed?e^k4aZg)&Z^6ooZ$OYZ6ig<{Fg~F)7SxVOOkd*FNT(=PL6itF9c8(AQqV zC&JKkJD+zKw4N`x(SknDPlHZKiueYgkJ5d7RZbRcmG5!>b~@(%>nZ0vdt*4KOV|%AwTA$uh{w=LoEMuX zna9wX!KK3K+m7dW+?Mta?Okn0Nr>N;;u(ogy(0G9@c{lD?~Vd?4DHyWf^GkzOErK~<}?OP@s?&$e{x zM-DDcwv%ORpyUv%`oZQiv!*$2YUROm7Z<7Mp)d>4cR7FC-yH7g4{Xf*Zbll>-JUd_ zCevC#>$`Y&E}03`Nj)+lLd-5;*&%TeE#@fHwT5ry2uWh6{qFLnlowr81n5dIXC3PX>t+_JRkanl4EUPt56#fNb6SFH+U@K`We|G1Tu7+oFq( z{7>z?B&(Jm$?-U6TM-rfB|I$Y4T2CEU1Oi76z|t5I@XJuMcQZxtnA@qb8=4*&JGha zFB-so`X|HXlGQvtp8eWlr}ao`l)xf! z>7fuYsw%S8F4s+67iO)0QJJ);4Yw>A;K<}uMwfo6W4Gs#eCCF^fvZIFeEDNZ^iyA+ z{05TQ{|-ohDV_hoNTH~t#v04!Qxxyf`jJqPGQ@kS!a@u=fp9_p4I@zmQ1m*pue(fm zyz$Bv|AUbRE(ao?37L6J2-yh{K+J^AyF*ybW9UKwf>koFCcbjeIrJQGG*ox(vO7qK zq-DkHlYyZ3T>c^qiNn)$ESm87c9XaGD~OT&1o~S@Fj`IqF#)fhF)U&q{#eCPaje&R zfE8t)i79+|>brhRG|u3(hzb0jCTftmS#c6mm_f^i62GhnFD5QC^i@V`GF3$|Yl6>Y z_3L!71HX94dI=duJwsf^@bN%Q1J$*R9a}@_j%8PKU5Ap&UNp^ftLg>AC#UxwKO+)k z3v@*_4sE_nUcOiV+O$^{A2TqvR>YDVTzVl(*C%N6R9E8c*a@U8M#nA3EcvkZwDCm- zad;s_s856bq}Wx>qX4u%Q;yw1U@@3Jtd zwK?1?8d&=9I{g_l$Wbt%G2U53>nS_Ba^4F7ZCt20VsuFr!uyMs9&tpqED4!3CAp>^ z8_M%OxWgmf<-=xNQ9gfbe>|;cIwKZ^+6-BR^9PVwoOw3HaRf4p-@=q}B364I`D-%; zB@um78vKn9Wi2y|A7nF=m&cW?_Vm5GQDbbx{hV}F$B*+UMn-vs!|U}~!_OmQxWuGd zpXa8J&T}npvv;S1WpJi>q6ddDCkHvHJ>HvY(q$AKt-9KCO(P9WNo$pmh_oF`9@$52 zc!TU>V51P*=isL5hWtNv@q_d!1sPORsAm5H(l3#G0zM7VQ8WIvC2PdYIE{t2AvE2! zDNZY>hIW5%zxS8pA(`KEBdyvAo)DYMBr@n_f`;bft+WCv?2VI*(!YBJ!*9KJ(@Wa) zMBLO|WZop@qlaL_WC;a|eh}UsITnQ8(*(nG;4CVkN?MMMl=qJhcHj2v*s*iI$SEfit5>L z8AZ~Qg&e*g(}D8n{D@}(KXxuEFNjnyy`&O5%I1932uVg?qfDg3)n!ZIiOxuHsUUc* z)re`1jFzM?V0|@kR{{z|GxIH~Bj|1|@O7Q&0>YgG*!oH@UbXtZ+8J!^M%es_#v7kH z)q!68?EI09LxNMRsu2A*9T}Kfxu-VMr*rHeLZm2wIO#R`=lpGm$8=Z@kTvJKCsjmu zeUf|{YimjAWJ{9=HyPq>Pn6|}k<(x>+G8R*Um@+hZ1Gn)HEr4i@U_q$zA>t^*}Fnx zTX*kLBb#s1d>Q4{p9Lt3DjUp4A;a;1y6pQaQ~z9DI~vuEKtU7R#JXiiG&nJ1k1~k7 zrbeloS0BW2GcM;cD>{fR4KPTSuoj#*k8BmiUQZ#i2gIn}hvd&Li<})-?pz@RG3C+X zf6W>riOq0-(guus(uwpoAFkLH5PSs&R4Fs3AC(alH^X_K6!O;r76{1mEv=qq`2kYL zS(@LexPs#dSH;RrP9%TjcEe@S=2F%XDV{PwN0=owI_x1bDjrh3@HX&=w)uU(9}mbJ z$PKb;$9MwwpqI!P8ODqwfSRr%)LHEr>Bayjhu!-wa@<7MwN044UiA^F_>ckRK@Vt56-`9W5xZhpsy8c^)A1K z;5)<`oDrfJn7q+@f2l)DSb(dw4p3Zj68Di&%)I4G0FZT!WE0C#LLEnvjxQf!6yMT{ zuC8ji$f96qP0oUz)D?}A)PTy4(Nw(AmCrVk(Li!H-5g{7LXwD8t%%lnR!2ce)Q1iHu_U_J%t(vDw9MpyfZ*W$P>kvw0t>!>~beI^oiu#nt z(qJzW(jujhjiYfo&fT!34o+SOP8p`R**+Ogs^j?JCf`_RBg)R<6W`6AAR}8hcoS8F zcf6BMno@VfXkAr)&N|mV)^SjA#boUrhTGX9@JuFzV^ShGV#Gtzzx8p{Y&uwsD4aZw6Cyq2S$SOpQBoJu;|OZ7Dq~gE{p$rru2! zJ{h@Ws3E!DE{#eLs76Bg1RQHpn=sN5DniiX zBol5?79BsX;D|t=%NdY4=ZU_nkKq=Rt9@RYK0eO{xFuf&2aBXk(?kzV5N3Uqb9Ycu zOe?iZ-*=pz*O6-ml}HA^OC&dLc}Mb_1(|y@s7j5v00<6WAd&Ah2ufRmwYynwkl?T&?0(%117?&iLq z)|UN(Tx9~y4Ub3Sz9=?R3}U3%Q+0wnm0#mf9ZI%P=dXXTbPB!!_Vm9mlDPkgrB#sK ziVFA@@I4rnG*e>&odc3$2aYn#8IvOuvUaMR6t71$1L@qM;U~DwQMo5@<3qT4QnOH^ zxstRfGQpOGWzq8qs7{)={9`x_T=qn-5-Rch!*IxU{%tr2?0Qb1ZT~SGD#ftcj+KKH zQOZ8`L*RG|ZrL0RnBN|0<`AO@v>Z#2NPcSxIXTAoxg&HA;wO)e)b9$+RII1{riiQ$ zFKrDBUpPvsqsNkgd@K!pQ|P!6r;#3H1qkHC>GL7xA$Yox6$DMCc%Rr%zAi_=8w5*B zr>N-3;gd4PNiUm7r2xY$mc@~HRd_8{I3&EO3XfQ=0qin%tPN1f@V_>WCxj&8xJyp4 z3JueCwmuB7MHo}6G6Csa8@eo7Fot9^oV8>^i=uCmr&2heV3j#&HH%2^pTeC+Y>k&am3C1xV^BcG+5EEb zT4mLigWLIy@1BkA!qRG}kR?6b&b+a(eD^bPh9N^f3IY>9?Y;&_}#}XKV zUiq&0`D!Q~Ng53c(4nzYPf6}K<(ihd0VsU{w6VJN2D&DT2bANisbK+Kiq-QzMPSzl zO&!}nDJyr;#qG&F1D&~Bh)zf3JxFLm@zu;JsTNJ96>V8R3TH;h9wg#7Xx9X1}nQ8UIO;y6d+4Kmt;CLeQH+;&QaF{mn5tYeSZwXFzpQ%HqJB zaD5#YVct?nQ}t!1F!yL zIJkfE$WJO(mLuu?S67XXD6GuVJ8u5^^^d=f&X2NQUhl8D{n5VNm;~?AN(Gxnm?rPy zfm&+>23=xs2w6exH4-s}e~3xL>Kcfy<$IYs3RC57sIL->e0zA)9ct_6YC06V+5#jt z7AD^6B#CvJjAdQ>X$l_XCmt%u^Sq zNpNCH-ORv^VO%=CHD%0Q|By^xv@ME1-8#5Yo$f4*(EwnYeuny+nAp)q(bGyBil_@X zvlUFTfV>QvkFBGaD$IlU#XB%oajA z-y_=VsgjKDkSn<{!L8~wY#`O zwL~B|*X=gw&uNVZUa%kk={@wdju~nCn0~qj4{UO$ywlJ=CVC<*Y1}g*F z$GD*%->mdvD(s93x2VDMVSju#{4MkLQ9GlXK4$wiBtw5L^2fXR0p13urFlX6Fd&34 zcf=fkCBJJSw3~nKE&b$bFaHY6vgs8%?rP7?Z_wP@{_@+uqpTGXe+9wG_J0Q_-v11z z@4+pysF!^rUp2&(cXU{PbCdr_wYBcmYVAZzr}pS7MP>B3B_P^sEI>`NR=Ba<$1N)$s8L7{C0b9` zR0hY5?+eu0jEIjo3FbWom7ud9^Ncx?7^OKcp0K=WesmSdbt zQW4(^-uD2rb`xef`H7!V74lKb7iJd2Y(q+n4uo7*{5`1n&Ft|&UUO|{rWAvuiM(Sf zF}d%AH|HK}E`U;uUdsdOWYq+|-da`3pb#3sP+*qJjPr={6RG^sUa=S^4G-8~5mqwqZjlThq@*1ZN+uq<($5RiwYnUNT0+TxQb6T4lL} zkqVshF>jE)Geh+uIiLd-QKX&G3-4XQ4kE2A(U5$lMi<|)!3lo25l7_5q=C9 zvNWHLA=CMi`3O>eAc})MzWJp3!&Vqg=jb71pE2{N)aSe~8GRbb8_Wq9*k6zzmqjyj z-Uxjo;QC&uferVXgL9#dO5{yWFM991Ll^kg-u`%cAzXEx2%s-a$$twjqW`)1^B<@R zNs?0{(82|5!Zo4rAfqNYDfn|ZAUF_&$~hoWBBA#5r=V6bb(ozZ8l{ej4~Xa4G@sR} zY8glz*R|)F1?v_twT;CNE0z58>kU?){S?}N-ElCs%bUW>_wDImvg;knd$WsM|R zB17TE;P#<-MGTcu8DNuZQ@@uD*#_p_RPF)+O#$7fO>gp(scnsEX&krf>#O)r%k6p# z3W92!T=Mz)qicu%b+0UO3eIP~2&6MjM~WXaRGF|>RX7yIdH6Dir4myzTci?dEF z@DYi~4SN`=?3$ubuPF(CX6pV|&%84E3sEm{K)Qq#zbSE>#h5}KmS{&e)k#pk1&wMA z5yQb6w}iZf+S2-0=~YvxmT|CF96;@xPTQXB)@^EHWN)QJ^@LC3);v`8enLY=p4a$#FQ(LF-TVOEmt)vCF?}H?0)+>74?*A{27dg?1p_|uSJr0s z$w((;jBL+o12j~HF7eQhz8@NKOYWZM$>twFG|pBhmtcd>6L+eR&d6T>X|-J=|)ZL(tUk7|)@Jz^LcE2)}9kr=f1j0o>hZiq8sAO3ggUQzq(`BhcpRLteKTI-^b3=OpX!}2lM)EoBOp%y-@M5omxI1 zZGNKuvWNXT)@b_>*d-j2^k>>XR2-o=2!v+jzis7a`k&GKXB6na8!fR_BhbM0LvFQ$ zU4>NrKzN~{HalPv z?>JF%$U#j|bI(`<7uA%3YYxl%@gOQlGn7 zn8o^Qq9ls;oWususm!c!Q)_YP)#j-Wnt;(gPBW1(bd5 z6jAT6qVooB@g3&@X%pJUCk*^53?^#KO9{9Q*0pCUM3|d=h&OPoWE38t6D*$fpbi)t z)n}3g)fl{PD`8_p(O2AE&NiTGaOz#3}cA$NSS;M>}0| z(^x~qMWW5|D8AZ@4Xa=Lq_v#W6(s3M-Gyhwmt}{nL)VpbLw>`fzRO;*wo?8@DADY^ z4^9Ow1#o@TZffPHalF}Dxx)uxeaKqcxv!X(@0$p|o5#|lW-+#DDiSPurLz=UGQrK2 zYj&!t1V}4#4idMPiR|JxySh@2tJ*J;rF<}SIm~d!+Ft>RlV2S23(nNY6+bn%SQ6snHT`rDVJ9Lu zyTJ@O8c0U_5a{UaJ%j!N?a8=)FgIegBNeEFfA^5chWCBQGyQ&ztC zZMJmo>4ouL5|$3u;cP>0%r*F_NorW{rZN%z!uLEIcMa4zS)T)xjUoe4?DI&k34ui4 z>XT^m`;`EO6J=wMW%4{(%iTVWYgFD?j`L2d?w4=OLi!NRuPq0!EhwzsMn}u|vKL#K zBuT_>BsLi&NgGf2$JJp(g`H%__>!MXqNPf!;nq9e`4u7cFTiw>r~5`A?4dD}w}wlL z3JipZEqIJtG47h=#pFi0iy{|BLDoaWqE&w8y86yO^(7MCQP^KI7~@w7rqo{1vI$Me!;w7sd|A@;iFQJ0Sxwe6Go|1@;!UYTa%~kFWk@sh z`4_}*QLoZK52pxfnY#YlO!cn?5dWNg`)7aYU$SFnR2D)66>3UP<=c)ZWOTLFjwwe% zv?+Q{3#I`rlk1NvLcaJZSJ0Bcw{!&I@3H8EA5bM2@zblS)aE(nR-Z@d{rPPAz-y%$ z&_>0>wMjL@*;Ggu!rV-PmMF+fBA2Mj#2IJ?aEN?o`fOX2s?PbC&T{4)pk`C>R~S=n zJ?hju;ESr-gA9)+RyW|N)Q~J=A#MZHa()EP)KDC?CQt6}&DkbLufXkhS$3iS*u+pN z021W2<^B|2;tW~OH;yQxy{o9^V3=y~f9&goLS}HX3Lr-yMA#Aj?nL2`O|o`qA<6s51JW8+1wjOR86N{O1$?wTj72c~r3n z)S4?sKZ{-P?K7$-6@!0Bv~4)k=L&cR<$E!rp|7HLSDd^ZPI zYkPBM5fc|zXJb=WNjC>mS8GRyzs5Z5mAikxd?rj`Wg&Q8IE-l&_odM-A}K3XW*9Nc zM)Qema-w~m-6IokoaZIP3r$acF7YK>y`#JPhR<`y+Y7i;>@2DjSUQ+4m?D|_)?gSa z`UKzlv^s-$JRG~sz=RF^5n5T+IX!UW3v-k!AGZfY;O3$vzfh)dfs18`Yj4i#YAa^i zd(YU%3^ER>m6BqUWV&avnW!YIOdQQJ;0AVO7hv*8U1pGZ#D90T}+ zsq3SI;RQZ%b)VXGm7W8+QB-T25!Jp;hEOW(V3Z$if7A<<&+d;6?RFht)63urP5sHA zuJWMr=-hKqNUHwr12X>AY^+W_y8S^$($rs0v9%m`L2ub=kBOz{31N~ z=zTn~+HDRZ0A2TX;UO$B{0|o?>!VTE6yv5V65mrpS*43Rgk#66UQDq^y>vaFtsZyV*+UKKQGCJQV@4T1LCE;_C^{Q*S_MOa7?Hv5ktNfP{dQ5vPB7* zGX5kiPNqam5NpD7-@;+YOgyPy1<=aWRSz7y3jIb7xLA->PnS zUtB%hUr+lsNrCsJ|C@ftiwE_15;ECvD8AW}r__~mauPjvr_JCTM9?ytF;gsaL^;ju zhof+-pHQOs6#cP@l~WB!J#h=7D9Hhv4HggLweqk_7(NBrL?tH>x7sHsjFR3ZxGf=% z2X06bPs~B-5`^rj<$cQNk-M%Te$oHbG%C0J(gz-!j(kLj?U^^2KKos!#9f|BWnesq)(CkVt zw+EA@kkIA^*iEfoUmKtZz=*_%w93aub39~R8AT^+&Y^5Hc0Zs?Hy-Ic^s(`k$PdW5;9z}+$Ejx?srAwMbK#%#PN$~=jF@OEHGF@ znDcR$tWpb$=^i$I1PZG5D=T@c7R({vP_9xbtl4D=R=zuuO5WN)AGzyDYM>Ms3nGg-bCY&h$-_>KQnr-4>ofn9?6sK;=4Ul7Tb z6dEzqt$#|IZ+-ps#_GlG0ggX^CV&Ti3ULbF2^|P7EE)XOY2hfQ$b>XrK2?UzT{A-_ zR{6~JBi!wqk8#0lYby!rODEUL)%FW%7C(If!Q0x#MBxx*x6jxnCXjbVQV^?NeHkat zqC$N4Ta7BxZIjh2=V!%YSw24%Xo{|Y;PNaJgxwo?N*G2s;Hby7_u{1|Wp=v9x-$Wo z>DW9JJFC{}O}99Y#f==T#UAq#0rUO9$~b)E58(yLG=ohL-@?GAw+7r?{M%Ia?_cn* zw!;7R1x*D+rK8}e^I)Rx&)VWC%`+JEI^`58h+p0bM_kgBzBE0 zgK~g!w5CJ4etB@`&LlwvyJ7lP1UnwbbzGWSIMNx$VM>XA5Rzn5(kyXOz3sxMF>@8k z_WluxLjMd?54YHoj@L>C)}S+Eu^li-e>L~*BzYAU*R+Z$HN17D2!*PLHUFb5EBTl1 z4bN8Y1qaO%#$UBuBd1S)u9r2j3$y?G7kvL;$-vp!*h}>v%Q@)-2X1H@B)L49dQLugu-FG;;)mtv08 zJMtR=apE>1!WJ;tN>rh5sH~!*rcT$QhbR`dwKy`QH*{&mwrNTC*dBwj<<6p3LUt}P zVHUEZ#Wo4D*9=KpHI83Wpv0$Xl*|0S#agmJxZt&K2?z(f*S-*2 zh=Kv+0j2@+xvS#A7J-6&a5mDagk%cSBK-Zd$gnyaU1{Y>@tUn^gn*frWnN(N{RQm$ z=bMX$PsW*WDZz&;o3`N54`r2xP9H-Em_yq*M$)A~vndT<^q_ZJvx9||Hv5A&dc}1p zM+*@wfIpJ%HR(Odi-UO{NMtJ#4UW4LHOR5^*Da_QHZ3&w>idrhMn0&1%Tp1zoy(~; zAj&?yNUeB6>gxwlFRBt&0P}Cm&N?_vUV>gm>2KfeKbW0=$q!LSPjj<>ouv7{iWz}X z8LcMl0g90ce-Ra@jf}MIgS;#EcSt~#Jwpht@DLV4Gy=K;2(2TY)>5YO`ts*%E)#UIH*Ue*-Q6`vg1fuBJ0xfzSa8DK zNxqqH=A49{;QW!kTqrq-sSi3GsxEo6uVafq%O`zEnttZ}W+O?XEu z=aTlE@H<)BHIlpj#vMyLO=pQ5fs$hR8TV836Zqt9Lw$S0PU1WS|6hua{n+?{5vcT$ zKPn#ge}NX*u<>ueD)s_zeq^7l(oJ@W^@Rr5AHtck7gZj|G{n#h9@|SSwySo;HH6Q! z5K(&tL^E-yWwkR=^~`Mc2c1USw})O}JE(`KWvCGYO2!Kzadyy1M&X!&&;nv8kl|$6 zyIRE3!_Bha3!RE=Gp2Q`0q-ehWQOlR`y1}vPp`9=b8G;O?MD0+gi^xj8;J!Nc>uMx zF$*;tvzy9HJoAAtlMq)m6^0q1iSq+lZEg)AhGgaUFSa@Qv$4D-k^w#Heg=j~g6$|z z!*(kz!+JF_w=fxcRkOasjK)nq4Tt10xX^V-4c#;EZLselej-+&meJ@E zs21rzD(+w0v#o&7Yhvo(k@Ojro%`z`}3~I`!a9qOG9_3h^jJaRxT` zFB4}uWYAv;6nX~u{dd>)e;-ptV{2fO#lHa)ziHjaj6bN@kh!+_Q2SVm6~CsJtjFh8 zuOp(g;;b_^*L=Ivt7qL^ARGIs`=R~&mJaN) zpOl>9gU*PNhQ%RjqIj(t*QnV%e4D$G@ldQ$Ci@el)!4tW00%X&i4MQ+BMiauuyjUw z?4wE%p%bYUPml$DM^~f}eZePFv~DeyR*@atm77 zvxap@cHW=BR{0*>Pafv^7d>f{`c(V?)L-}?fkO=Zh4as@noLfA0q3vQuS!n#9`ApH zM=y39Hc$ze%cFdqj42isn48Z@pxtbbfpZToDUiMgK^f!Oh$$e9gh3F)$-)dz^lGO6 zZ5XWw6D^>?pK1p#8YXJ-(IG;;z_cQ>9-ezPTm3ke8Hvx^c3kQ@;&k0-{uEkYs~P{) zN5is@T~m_+PQ8RR+=+k-y!{|}$4}LZ%IJ2xAjb3|WJ5z<uBJ#Ft6ch_$LZKM7X3V3~ZzxHdv(O)@kerv6|q4Gs6Hm z^OiLtCgB7cr(G0HfQNU>Ohw=h4rj)2D(x!?q65J7u!}@tW^6Ja zNj^lZN;Z_(%}|~KPExhu!CYi{Kgk3ulcQTje)#GpbNpoU|E8PR z^A3ywfnv632FHv}Fw2v446MOo;QTcoUKN4l`>)=S2mVt^SwKKH|IwHHm$d1>K>aU` zWB&!%K59Cu^TOByRatZ&Adk8I&#~F)u;UBfk`iqhC-IgY*@{ltz%fj-EzG zFF*}j_$BW*TN%pWdGKTjcuh4MOi@1FZGYT;Q+g3Q3>&9TXv4qszT@*{q-5?eWZW`I zFmsO~GdyA~^Yf9(%(NjpJfQ%nH#RDA4+3OAyd5hx%8b80h=?xQO!z73oK%EI>^l5; zlva-@N|Xn&F^s!-M^9BaJ|zx6N^OKAioJvEfR0?mig6QQ1En^|3h~GNN=Fb5R6Qc2 z^HF%*7sL`I6`7=)mxZLc3sT;f}G9<3mcZ2{eg~rMSTLnN|UU#e_V#id$iT%3yxmJP6PY#5qI!) zC}2!R!c6@ADm9@rr9j9=8A4Ey60_}^v+A6!HVo}IfHYl#vmzV$xEUT&@tjD>@+WQn zIkk6<4JLc)FLJ1J@IKoaN}i1{Uk#`5eG$E;{Q-KxpV z*xF)CujEj(3BGqK3#kc44~~|fqN@~4W>}pdPXp9azELwvVL>b2Ns{SdpxhSCccK|} z^TdFzt>g$-va!p~HlpUUWJ=Z8RVUBNkEYBH85KsC6XjV(^qce%$B>g{^Kl_1tSXvt zCzx(A=9T%Pz-VqOl9}M4mRa5=GVbKk#V8*NHl<4C7LLlc2f@VEF>Id{1HL9!CjIbN zqB|qWmfT1gvho{hN@UM=V5K43>tDvzBr<|D(QIAwxmVdYj-e-epHev=HqB9kZFI}8 zpr|SNLIRImdO6}M-#+R_<(rr^hw)16@D@TV1r2(8dUJAWSK|2TNFpj}iY@c$;zsBv z$Jm2z^#TVz8jeB!$#RnXFU$GsQ~~`kHDHMIFQO5n8Rdt*i1C9muCu*92>}xH2s0}D z6AX+EF?e!#JVL?%`2aaM7_}T{lBtm!WzJ?yMD_R8w~I|nOB%L-)5?a_rIN<1L1Lt8 z@v|o@gIAH)u%^R_oI%@_d$Ol}KIbm4^G*xkAG7nYSD#aUf6C|f=hoip;qggfFnLSA03NXMft#AR^JypW`J5@b8pw6I&;9Hp7 zOb#MDdcYR1g2?wuJIkQmDEHMp-@tvDE}$?3;VY2geM8>_jUk)PV84Z5gqMw1aU_t# z;E?*D1uHwqxu&ZZ?Aiv2kO@5g|m-;gHz zGlZh$JzZD+uKj&sXqnP7l8-+X9M z8Ni~Q?;t*~B%$e{bKQInvavdP;Gn)1W^;CbAtlqA?Cd&g?QA04s^y)_#37%Y&f#ZT z(wyu^C)R6izUl^+bA;u0S0!S_Nk3Xn=cJaO!|m36+Sf+-Yq*l|3ud|7!tW$1ZFGX; z82Xx-`rPUJm^kgz0$oe)RhTxkSuZ|7$eGXa!kjZW>rfiSdh(_o34VeWYJm0=?tMh{ zKE{Ylj-(fOr}_B=v=g(`RBu^~U;Po^i+z98?^@loqGp^FO#C_c>>`Uk4xf4rtOPTSyaT;y^q!O1>ves$rmhJ%5Ml)jyxvDP6KuU zgl~T!CKg;WnZVX0oTY)yh1rEwX^>t(OXds@(Ua-Ow2B=;?wtZ&(Gzvr*@nG;Kq`}i z$6H1Dkp&jQ-D=Y?>V{RyqqJNx4>9`^OP0c% z8Ut#zB$48P`=on0eADWbhg#mkKAHqO3kO;bJKH`C9Y)SVPV%l;QdZ&FP=A0UfZIUT_%L^iXxKuGsU&#oM@f zzlB76U8?t_QXyYNGLJPI9qUP-PC>Tzd<~HXf)2C75Mp;P^?}c_I`BZzQm;~{i6gak ziBg4;i0!+YHDG>?5+dtkiOyQ8O#UY;{uD)YUdyO=J$`_`1M0w0wIGY`Eesyywem<$Ta=r`ULYuXvhaIp<-u{?6> ziW;asAC@6C9I}8xFvT|~)4xXWRAmh2velsV#9gZ6oItP6QjE{%}wwv=dW8?R8*}V~_(UM}gkX95$y6CKqeriKC?SPP_== zI&`E%Basg7)46JiiLjPda~4aEpv_RnuyDoc<*CBX6(0}bslv5O7k`3~G3afE`Br8x zn~$Tu@|LJ@@V4fu zLnwW+C*Jeo=^k^`^D>ViG&?-nBd87op^Z~mEL+oET*xW*wy_Z0cdeoPZf9mgOgKVp z?ze1c$#U@-O@ij2i7?Fd*M#Q5rr>ORw=DZf(Qz4kkQp%2lml|M;ThJWYHaf-Lu%5c zv~sLlVmORSwY9;KEGo4*U7t;ET08N$k~YH;=Qu)hTB3YeZcLCuM`H`ohDQ_%;E)mo z0@^D&jEhyc^hv#N1&a0I>x-Wt>Oh`@jf*2eXuDW5&rDw&nk#{$A1XY4+(2rIx~VN+ z$_{_y6*&giqax1nKZQPqUcQtbKIA1i4%RKx=3T5&rO2Um!W^k92&R7_AAh6g`gpv> zDm8awB5+#}Ys(f|zVTQK_oV0O>B#grLP(x)PR17i#x27sIss-%)K9{%a0}ijD9S1P zR^WJa?r8K%UnCWIJQ3{MWYt0_4o8@0Uln>djvpdQUzJ)!h9P3~I~okO0Q?i^dKD&f zF~n1tdKGqaZ=Nc$37IeHFkgDqQ&X;3RCD?z`l=RYrg6=M-u02RL4Zn1z0F|^kvzKP z2y`{gLN3dBKTuu5SodV9Nl3~U%c5m!adeDQrpaJU z&r0(u1FJ$PN)W zRdc7UGjcV)v@~r@Cr?nKdz`iG zZxWIJ#8gQC7gN!&w>5IHvNij+a8lg#&v4REH(<^Vw}>-yDGDk6pw6QSdQMdsnf|7$ z+GM3}Q&?BvUJW*KuNUf~pm?78W)B|z`Y_ALcUvgW;LVf+sUx`~eS$Rn6ZCA{tO!1t zaPrO@=|J=#aI&v(rVsblB}9#NYwFeqc>5om1Bi*HAwu6jd~4uhnYxDyaM0dP^rz;b zkhSSr9+R&&U%&=TCh2Qw6t~lrT&pRQF|T~D!J^al448i^Y0~CEn#$s+cn@ggO7-{> zdJax%@cN@CnPfkFiK&@eZfiIDn48dv!`g8s2VoP-mf_3jRvx9zT|l{q_#Ig1 zp+>;3zDg7FuItx81$89sj2Pjg@gYZh;+K{|=H10M_*4C-A*=vP@>*Ejr#o$3nr=E$3eNyroZ!ZyhDdju zIYCRIVvuIh%bY@tt^Rr zb3Ql~EAdm03Fug7@oG3r67dSC;8X>3H&9UST^+IN<0A>Zx&}J6QFb46^8;0^as{xca4^_WYX(^_AOVwVZS2q|GX$k*;>XiQq z|7C0ms?;EXhsgM(v+Fy2rC{(2_t1B04VB@&UnzZ8itmtgfSbktN1vDYj~n(oxDlhG zr?>!oC9fZEYH##b7evRvs-D3tsIiyW1H(m2lprJ~f8t)xp%-maRnb9xO`)tKPrLxT zR}SXejA6kjWIEhx?L6Fw+d9oM(0s#t#C};7(F8LcY;Qb2V1uT^fUPz2$+93e46~Hk zqJRZbvOf_5?V3~LQmbDT?^LU{Hoy!IL>F(}!-{NxEEHloPT?L039SZl*Y}&)z{8XE zJ@Irgu2ReSgCsoP);7A(Bkpx@m}4gSO=<;fnD~@eMEWOpxQrWd3DPL{S?SwWu32iQ zjNrU38o}gCl<8s(8n*Ol!>>{l*GTZHMDtyS>6PT3YWb*ZjAl6IWEBHx#w%#61+i=$ z_eDtpuJo+$n!G)-VYcE_3YEL3^x*0c=SZUlEh^05eLZQ_@^0CXc|@NK3Kv|}6Sd6J zY6!Qg;eo9;%tODwuCw%yy+joke0x1=(=8c0V_tANsmmxcF$x{RQG^xYvYKfH;g~p; zC7qrf7Hd*S;L?J8k@RdFWv!dsq0%PP+8!+%3zopyF0^gymcHdPULL8@E1d20KUjPbcp9obSyl5GJlbNAACezbOA5Ci1RM09Pj|BWKIM$GH4+e2bpq&xUEL>x@;^ zO_rBn$_o&YlnN(FFqUu02PoCpk zOm8Q$z0N0F+3u#d4Z1;GJ{=n^_M;)$5axOU$LF?&^+5Flh$uF$lqDu5F}~xb&X(`8 zWO7vPDC6U? zDFw7A{{bR9qu}hyFtsok@B;(G$EcGp3bT7%-s5MVpZ z^>RL+c)7k?wtthQb@%D(ZbTRclA-|r^~HwhPS9>s9ID*|bI zSttiR^M<9pw-I$SA09!dc;E{g3o<_@?;Zg#5;4gsTImxbc$70bV}}J}Q-5z2X=Fzg zouYBeAnJPPr1~R1SL&+uW5e_3#9a2_LM*iTBin!1i_nfxSX9IWS*xY4b zRifpgb7n{Ir83x`bhYR;nNv`8u{~{09Tk)Fgh?{}^)d8@Y~m&T!&GE%heoF%9g}Zg zcRGoWlvZDm-%l%&r+ZcRe?#Q&kx;+b!GE$M&hG&LGZ*0W$6v&gh0X4*=D#nGVq%b>BhQ{8GZSZG_FP2U11CK}TLjDFlmDsdHP(_NAKl{*b zQo=4#xw9RfXSX^ZPK<662?_auFh`kTIMdoyv{W053=&{O(VlX$hr~vrJD=O3o)M+ofc*aoNH^xK1|=|3bC= z)_ZCu&tUryO1HU1b(RaPoxUScm*6$YPj?F|)FAUY%h)5s;3M9~UKGju+4nJc>IGR)2%Y8-81ogU9OsK6W$Vt5kt_QF zUxXabFqF}zY8;}6Btl?sBs4crb4MAj zW{GAQ>kLI1>r$v^KZ{4=OI^7}pT`C;bQUdu59TvBwz6CsOFtlTxCQT4T_Jo$&ZxS= z_=?7eU%*!BEjInu`w=`7pX%r&uG_jWiqeYb?H)jNkK{3YbOfbtk-Az3krci43{2%) z3FQW{oDwjkH|)?z0f@CjE8|v6w|+|}$Cbc1!9J@uLMv|yTXrO1SMSH_Lh|}+g0W*m zzp4k2Lh%7}j{p1p`1eEV|3vn03RR-Mt%)a&{)4i8H1X34yRcPXoU!~mIW*0pHp9wW zD=SqnY9)5##&K2>xU85>TW;gCAbccuW-(t;)p$8pm$r4 zKf+#WzX9nW74v)Z{RlQ6gbED&90vJ*@Qp}YFgMA7EC_p~Q4;dxQB7Fn{C5vTCOAi# za6t^KYz$+m@NwE92Z=%OEmULO!F6o)OGL)<5kuj@_Lg#E+ltiEF;JmbG% za}y|&taTkn$YPO$W!Rs!XmgFZg=qyoGFVHi)*mF&jPE*cP^yKcmrJKRwN&#!fFOfV z$8x1QfzcG_y#uRd02wwvu(C3sm~$9)+P}GdasOJXbp|@-%HG5UVF%M$3m}Kj(5}_` zLaeVt-0$KlKk5|CkEI)ZZM*$?7ddzO=sN!i{t!OsW4G~$Xvn*%o^r#8eE6ri&_PMr z2OK-aNCcMP<}(u$IAz*%^pos@2h*W4O@_|$SgE_W5liPEdAO48`RdV^ z3R}q5vI~&x2(pU;^c<*FGA&>*x4sKAi@D_#F}R((Qetbn86_yJDG-=gZY5|x>bjM7 zXb;>*l$&Ez*^Wluo+I$|&eTH{wJ!+6^+<%4K)(IQ% z=;8prxRr#-pm@Te5Wlt!;?D9Rc-**agaD zR+p+vD75@YG|CJ>qVRdpliR)BGcQ_b16 zT%Fr5Z@X;Pmp6-DziF!Ce<)TWb!}DBL%3H3_4~x8RRQ8Bu_K4!oj`jPZx1rbZ8KOUM>llFp@k^kJPw1~`(-$k+J{UpwPc@h{ipc^!$2D#A>~PTmdf ze$u@htx57~_eeCFKj>O_@c1Ul7+DaDqUzeo8yo{>APASe6NAOT|<|J^A4{bv0? zjM86k)@qwh^BTxM6sp9UlzTZrjOU7-YQ6$)S~dMOa&iL~c$_r#R(m0y5G z=As?}f+9?7SUaUyfG{ORCT!2ej4olKKzGBoLn**qJbdnDvqkr2eY-{6?eq7?J1pSq zru@NE3f;C@bhkl_nWOsPA?23hY5qL{5UuKu{+uR*PyhZl;CpK_t>hqf`e4Jc6*Vb@4d@++2m}> z`xhK!djQ_W1p%3%5Jr};Ep!WBpywtU{fSyWAy%PsW`(`XS{Yg%1I;bRPTc@T$%6f6 zo4I>`kqY+UIOO7r6v!8F$HF>`Y2&Zet48t{`(6+2HgUFeALKivN8jj0wQp1ncVX}Z z2&pFp#7)>f%j#NSr#pI66^1ymX*J_uzsJ{c@aB_l?OWD09=9rIz9k#3pKRT!?d+1l zqfsot3Dd{lI@p|)J|2uWK-`2+p8v% z$#eAs>>5a;_0I%$fVI;4Mu6K!%h|nNeSd!qLFQHikG=iHO3XRLR>x++2f+54T@%ew zJbx#lWNhWPTe0QpetDNq3H@@xw~xCz-6dXY?%nWshn;OQ|FI4uWoVRA#lzF((o64Xui0 zqkl%R!8koZUrsyf9`{>WS3fyK7LA#&U{6AB?1CdA4vx!$N9exLUa&~rLcfEJITVEG zYOfh*<|M!Y{tk#dfZON=%>;s>fyTJW2A z-&!e++9+G5C@U@5F7szhVdcGc9(^;j26$9RfOZBk=uy(41yEvy2&d`l`%0sA^e|H} zEsu@TYq{?l`Vn=NNtS|wXgJ^t;vH@LO6pRNv`}3K3QqmUz$5xURU>BvES9nS$E4qwM0rJEhHt2a z2o2FyL13i8;Vo%|W(Rh-j7m^Ka8Pb)+uqw_&h~YQADK{o{z7BiwjeCH{l``o2R;^V z7D7LB{&(;d=An&y6CkL-X`A+AE#>fZ*=t&+Gr>=y;uV98ioU3p=!)c}59nezf3(hj zzrThBcGBL5XTlBOLjt#Mx^5}_YBd^Uijc1MpMX+u(m?f!Lp5$-8h(#MlZd?IOR zQpkE)bGxQQfBRcsE)`?{%pu{TE_lwZ5TsT!DN~bJK0AU--XjW`FmFq^LVkvKDd8zw z#SGH8E-u6tT#^Nw0q8?+c=52gO&?vEQ8)z{iA$F3!Q^?eFyeS;Z6sx+u-ixj4t`4i zmnP`1j*BrF$=nl81la{WdzUv7ax)##213p$--=wJ$ZW$p8=#ZIMDmmIomm_p4}%uI z=79%)#E-%EjL4uM{%%I9b>}C3tar+r}Ss zC!{XMy%o+P$moL*{HOK&G}ZhjwK~o_pt{M}uTsQ$NwSQsTT@tXMDGc$32_NCgY7;F zZC1!Z)XEeZjxLeUhR;0ih&g}KmV;=dde=4cbYsZf_^?w;p_X<$8|rCmEPs(=Rjw}ea5h&RU@cZu_yNowE)g)>EE%j}jineUkt^R;D z7b3uVl+lmc?adHKT`<%UCB8A;E=dzw?2K#+)MyT9dx!hX?=nwc+^DzDK=l;<@znnf z9-snktxSNAHTHJWV!u_W9xJcV&x|b5Z6zuXcY}2vZyP?4icv_c{?=GI0-|ce5JEEo zuxhthU%T@LA9hipsL*ql>v70_sM~K_=*>B^S&L~pa?Si#v&m-uk1e#o}nLuz$9`qqu<+e28vy(;#$B!x2Am>BL;4zJ%Eu2YA5 ztPi(>U3*dTz%{fxS)zE1p1XHl8!8 zG4s8uAS?iT%TG_Dn-2#bs~xMO--uth?BHK)L}bxRo*mW>*7U1y)QIus@&Qm;a(`6T zPdfPfq{$n(nc4l%v-Xc%UoyzwGvI%w`$|BfAjs!c4b+LfWDj(>yzC?v0;k&BNI-(5 zJLp?S_*%vzDM<}*)zWh?Q8yBIbJvr1m#ZL=PmlCqWE*HFXP=~IB#n*qBxiswL@`hQ zHRL{^wShV;L8PIOyMkPmXc9jkGMxKuID#cA7#T7v*yi{^-$+kyPhZcQ;#O>}0Mcmq zuf&d!oWD$pPCntge4rR}!0*2wE3|(K3Pgpxk(J$VJp3)7!moMhh!T(l2zilv#ffr0 z!-1fO&m9dDH*AGr}!y`R#%^<}ozXqr$fPiDa!^T;`Nf)7q)F2)m=mGsDOD9N9 ze-v<~g@2R<^PjT(_x*BngVX>@Bmwy&e^#pEepYLq$W{Yx zT70^%g0X&Em!FD;wG)g|`R^SlK(d5I10w?iXJ-BMMw$}R{MYjC_G9^VfdAa)|K;)| zTy1St%xvwAOn++|p#1aVMb9qzQq-tK4QhRZ$dOPk8G}VewFeRn#n14G#Aa+MdFi5v z-;$K&atG-`5@(xJBAGpF;&du2qpQo;x6|(pcds2vHK`l@XQc(Dx?Uv*rGPK_0RSlZ z1)ONNL(V}I(W1@P)9q`?Erga#*Dy!&#jnu?t5f-n)hUbE#|pc;v^WOL>1n%a%q^Yd z(2%6i4>wYL;m5KRHAxRzVqD(%@k4~6((EV#6-?0w6&4C6mzJp$A(2?{57NXf#w$9f zkP(qzbaff1_DE7mI4Z{nxl=d4gL9YqFt4c~=ITQTy!XUYli#CrfYxivNhFnA{JtH3 zhos5uZ31x(Y)YhU%?2YIj7gAHrDG-hpy+=oWGFGVU~LR3 zA9Z=nbqVy&tF&O09bo!L&kizfbnnazB_KcvRVI_2vKSgZkh9tr&X`noD3QlBPiR&i zYt|tz1F6akmmn+oZ4Zv8Fs?Nn}|WK6DmXROgxv zflcb{4WN_y@|>S1PCuB*=P}J`x%ZZ*mcHw}mc?G(iC6gH_ZZ<)ue|`L9de>;cVb^R zW64GasH%_f4U;F&l4t+8izDlRY<}fAOTAW)56X7l!)1@jj#v=#!U|BLBbO7EP1pH+ zJ~GpGt}e?{T~~PE!YVzP`E5G=y}Ct`d=ok22d_+)k2r22?*kEC(Rfd$95vEpM%W8# z(o5|aY}!h-GY3g@vCKpnbdcoNmE}bniP9P=-qm=KAy^0TP-iX5=(nqzW?3cyI9@H4 z8QQa8xqPfZty`;bAlCHt%VOj6&otC?A|ION`B)|jUS6<5MHTh5YH;0Ziy6KiHrDB} zBbx40};1yC$Cx%|+>(@|{U!>iQ_n&UiGYI9nPvMjP)%tC40 zAS{uuoZWuN-HIn(IcTT2Zpu*qPTLr?ZQ^JZg}Wa;g2lT&)Jo@KBX*Q)lMz*G1Dc=i zy8i)hd9%F5u|Fn@{;hEu1oDZICeq~_?G+yoMA-pInmU6|p(`Me1|-kX*kK=zye3Rtj+lLqJq5i=!io5~(3jl_d({P!8r~c?qmkXA`tM*C zkkc@_LVe$%SYyYhAWOqMKp1~7A}C&*Cz*?Ym_Z`6+GBI^bgxjxvm8XsAqcb2hOBw{ z6$2+$?z$5OhMOk;*y{_JTk{vc5cjb9%QyY|&IQs5L^nv_&C4hOX^tSTC;_Rk965pd zpYEyD&(G+@6mJserllujm+utEWaJq^Xe+4|Xh#>ItiZ=axw|4V-iuTWmhe#6M#Ia& zM+=X@HzXv4Bn0FHKnwtLzY^u(tA5>LFbbw~-oSN5|M3?4n@0XhP5yT#t{3-q*hT<(#Fc+^H@P>n)HHjqxU(gst>~XUExhqI)V>Nut4Q*yh=ka6qN1tM7|q!$mFDQi99lYnYOLa@(|*U(a3z`4 z%qsm}FM{jN9e0rFwew@)k8OY+9ZgG0o2M4zUgx9jjf7oTNQU7c-)>tyX||~+Ky3B_ zB~*7gxOV1SqfeG3C-_Ix`IW(2mb|MvWCo++M~5Zx(Tz_JE(BdprznB)Mz~qw%F6mR?8>&%gL3uV)p(D^P<`f8>{c z-$6e)mx`IQtL^_n!9S^r@Akk%NB+XzT=sv2!2c(zQZ_nn@PAVkVxR!O>F?iH z{|eWi9QYSaeDN<_NM2}|=vY7pa$srje<8j9ff@$Tu!a2OhXwz}4~awUsQa+a_Mh1< zQ1d{-s}E$ki4z@jYX9ln{ST#+ffumR<5VJ{2*MQc%@GjN3c z69O=#SXn;(Tp$!Q@k@W3J#evpe_Zg-^1FWov49V~W^QKU9wufEKRvgKBVLzzG=@A91X?R+9`ZV~SDl21zjzEM99T+Sa4 z+|NhH-+$bclSt|$l_wV8~zSy7&s%jQzUzYAXy1ksYR~VfG#OWal_!sh)P!Y zF4w-{!$9mQSGSK%oSeC`ZA1LKNu5>Q(asT>$!H}f-6&|0Dw&+CwUiliUOGkH%s^zy zsvWq3_twc>D~Lk#u1=rm3sU@?V1n$^x{>2{{FUO?KOtFyVy-(FtcdjMLw9W=c2ND) z0Hk|5pam%3&r-zy4*tKfBx(n`iUh~WTickd zB`q~VZ>~|&CN+B^W3#tP&+i|WS#Adt7^1z<+WJ82s_+^{=8tHNNQHWiwO?QUG znip*nMX_+WBBeYv3Bn3JX_6_@3(U5IR-vx=df*81_3b7eV!(ABjG;vPERb;M0&H9Ts zgfbI}Zyh3yww~?}Ufy@Tv1Oi2I7~-w8n4^KqdQ#OieLy`fS?+?UvmN2oaVdH_q7dI z2EZ*_xGtn{6cA4!?QzH+Pnr?17`a{P+1NfyNwr#i7SVdH^l|Mz*;$yLh9*PaQ+hkX z9E&yQ8yD~WE8d&Hs#KW@l&t%YJO9({$&0IrFi0y%Fv*K3NK1&Tsxit-_{6dx_A$eY z-^sL!GR9M%r6(w)9D#;!%Ik$E^Qz;M$0|Z%N*P^VynNpx3V068Uc^9qlG12e3pdwl z_NP+`)$O|~=AooBX|r-e7QRfbTpB#|*C5;#IghmpyVAhkbXzM|-F{)NFsd;M3nf|y_B*0(%Jj+Z! z)Xstio z-0uE?XowOQi7dWBy9XD`hwsjF?&v*QJ21PWb#NcKFE|W4+%E{f;|aqPofbd5hzi@H z@fL+k#=)TxJ~nRbs6WgsS;#eiD0KA zF;dAziB*&yp4Ge^v+3#lHmls?^kCB_*q7Cz8}V?X&vgT30-$=AvUf!TMJtVc2%vPi z!SYt>CSOsZb_Mwq&;;@~ee51+?sXT+AQxCL`m_H*wOd@^Wl3Y5WF5wRTn*D8O3B zVj{MiN-{iYl~1%E80I(uX3Hv;gL5LA1E@)8P44(}v|hv!{NB6F@LWHJn^3p6VH!vV z8_5(ytQz4(J2%a>pbMMg3vP003XtMLu*76s0?qg6X4Pu3j%Oefv?8kPMw1;rh9N&n z>ttyKvqq|u^EMBDupS*~<`c0{4;f=mxs2sn7t8rpK&kN*IHka2fY0$_J?QM^(@Cf!Qw;?^1RWJ6Qm|Mv_6-x6X|B&K()=pKdo5 zw*{f{!SWVJ==Q3@w%E5eZN!cat*F#;Em$5Ybj&PqDEP-j)#{e%1-^^wS&nv3^7TPg% z9UBq{kqw?Ln%R{6>IZeu;Et45Bzh2?5zei&R_qI_lZV(4jd$9va_BcU{-F!-{v>!s z7Jjn*VUL7e3LIc({|4bt8YY@>2qXS_s}@2Z;<_H z`oNuO$LPAkJ`}5O6dLPRpCgV%dcv@~Df)#xj;e>!(6@|BL|^iK{OPIvFjmH$Q+Ri_ z4&r^%E|Psz!c@l$gcp#w@q4;TKdfQX=NJpWLxR^HX%J-M%C?k-IYLTUv zKHxqmm%f!9W*$GCCb$c9mYJQb+r!Nn^!S)U$T3`Z+c0||gJWYm!M8cMD=#X}PiiRb zF`wh?+(@D;U6C0D*nQyXOiZ%AUFTxAY3acBJ}ny5bWi}vzCgJupih6LTVs%fbv~ww(MED462Ayku=!dQK$|t1SeozfHo7d8@rxa=%V5-l?7qHATY|bd% z_MJwGbG#KF?ya&pJp+{G)ItuBx%`wy*C4zoI~Ind0=N*Rv#;z;ac)S>mFv-nFP_yl zVg8VX({1$mp-ZFtVnm#q$*Ib0O&O}|G`?<;*S-4MK@P*a_ngb}qALkZ z$}%Pu?WF_E*k0Oc;7ZwqQ*5%xhD69rcuH+n2h-o0p96=L%`uX0+LlP^SlR`t$9J4Y zclG(gP;y9r%%$l`Hc#9dbTMy`vZ-oYqJ2+qHg>lX$~o7a73Xj9<;1kT&wlVWvC{20 zhwp|UN6$KJYjbgSb5mN-Go(?``TM2ddXw#%o^OUSB%Ms$m~}uZ+NV-#Wtj*AOlv1y z^Uqo}Z4(lV`YDy-)#dW)IRfAxnLk$0N{8hZCVuZUEC(mHy`y{nKAmF=Gnf~R zH&Mp|3ezT;40~7%G41y(@+Jts%;y#}ls>X__;zr2t#h>_%012+H z%7dFF2#^)#VC$CsV?6@X#wO$_-RzxO7!jXquj$=#0#IpCzX=4TRHs>ekZaNxRFuJ; z39V+M)QA`Av1Y|6^h;m70Yf2Ov%b3I;NQtWr?i|##l4f@r?TgbIpANyycmSj!e>Hh zzg|&O*w!rEjvxMBo2g}3fsB^ouaYFWZdEm6BCA}bfO4&cRY_qtvu{_k1DT=274kTM zj*BV-0GZt@j-`ZXIJdy7&RRD%u1bxOBl!%oglvFf;wqDW)a$mSpY1wWJXgi0VUP(} zXQUG;QAkLB)S_NOw~6MEE1)T0r;d?HLbXnXTNx|dWE1ot6+s;}0TWFZvsxL+XVo44 z)a8%4wbJiECR*iBQ2(GOBb3*p&2Bu`nD7z3FJLOvxG}x=t)LZlaW>gB1oNV?5>&8sa zCc@cBnfXuBxDac!mYv_8J~VAD3n{($x}%NMl0STTI`G*(^y>U}_q5evq3^Lr?>~93 z_~!p%?480ZU7K#<)nO-@v7L@>vt!$~Z95&?wr$(CjgHZ=JNeVS-nHMo{%e1|_rWs{ z<~*BKPu+Eo8e^0-%y!LRnToc&1A@p~GFOtwTgq3!$UF*H1jsx}SBS_widPKCJjz#4 z$XyCo6v$mlS0u<&B`#V6S&FZM1KNtOk^@_culxf%im&1WUH&pLkoZ1JAT@w1yu>{n zxP~LxTp0S`vKT<_rzX?OFBAPnyR^1U3fswGJZ{MVll)NwyP!ZktA*?wW&Y3T#Li~L zgWM4ogpBjm0?;sYu6aU>pEZWs-LRI1^9Q~MfMJ6(mVf`IxLmb^ackO)0v5*$U3TRL zZGH(xY&|#In3PEugBEA175bR!t#Ae_3PsF$e*L1!# zyY>>?Q{QEYi$FR9+(A3Xzca=SU3vM+dhl%nxXGf2xPbIs(z;nY@o0Ue8PHK&fNL?D*?LbGnL`S?94UHT6!_?Vfi80 z*wKv+69NuJx<7_flxjB|X9F~qA!T_q)iNvz9~_#I*{vlKvrs8Y zIDfI7CAxZ++wD-^o{MBm*iKrk{zBh1Du$xvNrp$(oCD$=LcSzi=#v^uxZu>0s7#c4O*JXr;rTG%a>d zHZ;HIP*AcYx~!17bgsrQ`5+>U=`a69YLF8~s=;zq#@=9!b_&6*L=x3$Ki?d;w4_Rtek853>Vj|!B!&~$A1-{^@ zB_@7v;Y?*Hr^F7Qbxd&|C;Z~wW$HS+4?Dy}85VvY$104lDY1eXy-DS%`au<`UAvTX zAcG5nT(6N_-OV*t>^~{2sP5QZPZFywPmC3+RoR9lpE#>5K^t+Tt6MfH5=R12O;v}f zQ{ft_!wD)-6Q z7$5Vcu614LXR>VOx+cYxN^=$w6Yt}k_gX@Ww@NIl?Bm2o^>vV?nl^3yxuxkNep zxnO+4G~ru#dJel?1^}69A$Y2;ITR?9A!FQ* zhMxmzSa!iWH&C`Z7L##&0rx{N8!{ZyVk|*NIWnq2yKNhSdF<3GDa>QQaFAP6l1t_2n6iR0&3%4m zYpQ)N3WrpAE{o@@G1L3wY*G{sT%HSyEzYM==4mq~DjwF5)n@v@3U?A&RNTJC$wbc7 zJvwciioN9p&d@?mrvqDgoT^~OaJ<-|vfI#P9xwP^pNlS^X<>2~ivI-QzmD3G=M-m}%ZkM6~fz_I@nZl$vW#jr|u8(?t`=)lD<)y{B zW@3U10OZ{4-9q^C3<2o#MQA2N*LBu&{s?+s<)oRgy+KYr*ZQZyt6X?S7=De~Dnx6R zG^mC6(;QCbNc|gMQ4`xu6E4O`CoaZ7)GTU)3kCt{X++58&`e*)TfVYFEb1avXs(({ zKJfiVC%2Tl8RytRj$>6P+=z#{*H0L@1w$))x6B;EM~6q}ciI@*Upo-`%><{L(+58< zlB-uswLt6c!!1%ENQO$94^5Y^N1)USMJ4J@Y#Wv4&|?rN;8*aQq^n}lWc8Q2jG;M6 zWak0+i!U?Cc^t_gc_O}IK1oKEky%DfV={f+)3nT6-sZT1qt|efmYjlVXp|AsdsF$Q zWefjlgC{(}O%R7lL#j2){D`QX{k6-OBxfjX$mRy*K5?cm!pWUz{~!^*d~B}(@ratl z=nlQyn&ODt6%5m1YpxLfr@(3P`Q$db1s(lQ% zU}fd!=0*HEsE*bu@S_QoUVXcVsHO;GI1-(au2%kDlgkK1eVoW}RE>)U81iyu^@wS2 z8i*Jx!i&TRfqCs$J?+jXcKkkwh^#7N-LE?FZO`dL{`RG{Py?apO-L6hKN=N(wr344 z0Am;V*om;Zcj}i~E-ysa^6)05Da$8~zkR1;vTjVYF|+bP&pRe`A}3D4FJ86@h;bi< z4WdM0x|m%Ow!F2^tH-~U2^zaU$C!Vd!iLp!U#0bD5=!N0+de}=34VB8;EEhjLo|F|>$<^n-L z|ChJrxfJ?+-tRZBX@ZuqBsx6z^}0VlU7;E&P0~oqCN?eI1JgCXRVHQuh!HM^ca zp9>d_F9c?l_4@rxuVeRnfBOGMCX$GbN9vu=p8c34tt&KlgafXT2SkaUMW3FCo^=ov zIB&${!mbhQzjAi{rJPNZ>@x}eNeIutSO+u~>uV;KOge-}ovEqyZxhEukgJ|je;-1Y zXd}Jtzj;x~n9D4`_+2o6#7hbP^(TEy^!_VE*0;8@cOd-7_B(pwms{;O>1URVnubO- zZxr%jJWJ4^-C6wLX+_zbU2ep@e2F;vIQY8~N>V>4-0mQRAN3o-lEYjhBhJ>nKi{5S zfu;u{{R{mi!PQ|JHIZcQqy6Q1k*z>6RrH6OR+|7p6+OW`*Lb_nUz#C><(B8}y~A=^ z0Qo@snZfZ&_u)D7>K2fw2U7I&U}9rq4p^g)fkE(W5rzpHrz$uke!}TJn5m2?wN>=3 z!z6J>DP9=Qnxh$CdSnJeg-;B%Y`m=oOB&56{cmV%=ujK8%ybiG;v1ji1DHQY)O_mL zV>tUTsIH9Yly0C2mnz>XI6y8|v|z?mEi2A(Z7!b9wR0#%h=FF#5XC;dL{V+8RA)4)GyW~*NfpQ3V(c7e0q7Cn8luh;m7&f%9H2a9fuDS zC8!I64so}IPs?9WpHRX`b?O7KMu`Bf4B<8OgIC4jWD29nLm>QL<|iThqN^&}ja`(? zv2WyYM6o*@G9qbne=1!z(qd9n)M;7dshDFnG|P|7Br0LM9s!!<%Af=H0cm!QXy=1N z_%#8~B>mM2FaD=AiCk80ZqA%%;Rz^Jh>aA30h2T1=9RWq@IkO+@Rh#2f zlASrFO5EOfg6QJ!8yG^C6FWVUo!gGV15dWOX~#D!P8a9>n4E(hlnoMd2_!sP)~_J$p1wMF zP-0mgbU3k8(cqb}8Ti-Sg`&w=PiA|lLS0{+Z9-yj?*@%Q?!=L%z;%j74rMxi+i&mq z`2TGBNQ$#RRxyJATVniO!!p!dbr25|yr-5(B8CP(SEEy}spzQI#=u&9CH}IE#Y7i4 z*kAFP5PM9YfmhjqH9DVAa<1)oqfm79)@n9wT7X$LEjr(}@!Y9yO~1+cfxGp=({aty znh_zEb9wLWW{$;r^M~gT_oZ>2&bw3NOi&*j4V&&Rw(Tyg%Pp94n{6C$Ff12;;0KON zV(>_g%LbVC0bgV6WQT1`>_=j7uVG(YR5TPccr;`*L^M>FpzeU~Hjqx$ZFP`N#qBka zPR(r&kWTq+f1oX*%MG9{g3At|Et1QhKsR9%B&~d$-thp)_pyC@5)PBM~_-+Y#BfK}pVhwu~PsNFH|tNw5OF9eX1#p&=H# zwZ;1MXEo&srpUWi24~le(9ScG}#$b zdMpJ|-ysW3LenyOECjLiSQ$*RY#Fl_-3}~5*ZR;d@1y|+Bdo=zVYH1N)k7jL!4X_m zC}>`(fm;?qlZe>IozcDeFA)QI^dId5yL2AK1G{35+P!IbSqzAdbSXLjo&b&=bA($4 z0PUIH@ZBd^$5xOf21{H{iXLdnwJBNC#0A1iv<+9*Z)YDY+XGY?vHCX9Z-z?+f(1g;OKo$b`(Jv~XZCBMOY7!^ z7u5(3s_(GSZw5-zmy(X4^-|GGz(b zLzDjnF~6D0-Q0mLeg07H8#+}f(QUN1N(4;7-Q|3a03~0mrBbUKVXRb1obCiqDalHN zW4pssJpc64uL=bUw6X9bnjF|3AjqXsr-hJn+< zHQF%~Ui>MWgk(YgE$8;OK5@FxYIdHCiq2$cgG;tKgC zDe_b117{L)o*BS+mFigOu4Yoqei!39ZZWZf3ah5Tl)=Q^OI;sP`P8sLqwU^Vb!DpH z1a5(bq;yG$EIDIAmh^((O@)G{T&`d?@~pKncOUMgetwEAQ$$$aP2eVKK+ag?g1Rcc z-OaV&`@RAhFp8b}Pv?F=6Jb5{czE|gvAG4~4C*q(1Uh#}_;zyMBxg4Kt#n|G?Q}p!i+|JzwH4fgKIE(ydPd!1O2OAXFwJ5P7 zCdQE#RvKVOH?gZPD>WvBfMJ&D^cC%_F#93&Kh})5Jsx{$ND@9_>-3VA7bKJy&EzD^ zl|y5{QUO%&)_jKW6`X3Ptp)3^PHcv8ex$}72(aTUx>>Ty+z*ppax%LjrR%zv7DV+6 zV&Z6pBoJw8Q#*}8|MbfVK}J{W)PxG88Z)igi3mTu_e|>jmoEF)-YoweiDAH+c;NyE25Jy zh>y%aDtv&sdsG9=5{2V1r+nx_neo|FW9)-Cd`Ejre@k~}a8xAC%!DIcOmaq_S7Lr^ ztHuvR#V%-?C-}$-Y9J>qIN!{XnJv9yHHu-0J^3>q?z_wkV!Fs}4FY?Z3})+dG|>`Y zjmY|?kWA;iLvrwf8et~;R9A=qCGUM9^tVy;2&+Xz7*ute*lm?v9oCZlrF2{|)(Y#{ z-J1N&P$IxPQLF@<@S*g@Ns0iaWKsF_@z;cBq?cS0*aN4^d1DC73ZAs}8Z%^AF?Fst_$bo*ddi3l1oa$= z$ya5z2@XFUM7%lE3qRJ8<2z=Z4T7-W?#M|ztJZ-ealMNQ^o*EP{P!rU%Q2XZ zVrJv+(pueS5kA9>=s2_(u4Ibh)m-jE%}`uD1)`R*D-{8X^)boX*|E;qv5bPGc|oeN zjtZ(gJIYZy6D=bT6?C#RK5@*QC%&rt`_3O^5F;LZr?D7{0b(mV-o;;4;v}Sr<=x0L zW{KwmCJn5n6w0TOm`YSC^#!bA7zJA7S>wX&66oOTt&X5K#Ce@GQ`XG*N#h7Tu7d=6ttefVbXb}<(HC;h zVY<2{;=0Rj^%1&>nvwVhCGBS-{()WX>SImA8B9*DWz-Rq=6Y%rly%m!3>FVk=ERpK zKZLHzPXAd68UzwXmfLO^6A&NB;_a@QX|v$>cK)u2Y78D3Jta*%=vIEd*cHssH2Ss^mig=N^Xq)kJ$N$9aKGP2KX8Mti}T!}Sq zY(1%k(>ZVl7_9+$sGBm}t^!$Uq3f;}HluEbP&WIAascZFe&&z=QfQIp??PM61njKg zd(iFlO$lPNI74~FH5u#jl6=6K!zF<@uX5q$@|n^B6uD@Y7W@IET}&7SY$fTz=<67t zvy3VSxbP$+#*F_t0O4));CQ~Pn|qJ#1iZr8T$M^u}skBBZ_4tF~37ESde z_oU($KJ_kpHB9Y~_HyeveozRhOvq<8ebOY=FkL6oWVHd{9o(xYJ9d6T@a;Z7iFMmV ziy!1>gB~?-8!Zb55CLuB%JhHmb75p>~+;+f&Xa1Ga8WoPr3QMS7MbIOj|` zc1AAqenkjJe?731i*;JG?It{Dp)g5<&!ITug~dLgnzzwElR$N^aq%8mHbq(N&WK-K z+NPe<2n}aDdI+epzu`9cF>(fJjolz8zD-*m%iSz)SDgbK>yZ(SrC9QGW@radD28_G^rHhwf z0BIX}wb&PIg+hS=n$Z2*qz8)?J&Q;5#dY}nQL=SDLFwS!TVqg;a!Kh(cZKf>bwMa$ z6S?9-_yUc89Qmtlw&{Xi{Lro8qX)9a_(QJ0T6LUz4gFR;a!VoQ@j$xy7Ex0tc-hWN zb~o%cn40*jx0u9|kZ6$p%*1hWkhEb=l9P)$7`G6N46%?a>0Qi`WztR<)|ygYDQptU z7>$X>TRNftE3O1!j1e|sS$QtU+O_sWM+u$x+W2kho#z+8FR&NoPBztO=k$J&^d9mnes+;PKFhu8O3UlAc zJ!P+WJ?@1=O49J>lF~hr;_QbX-mb18v;Fodji4nklj&%UvrTknx?@9-?1*QpB@Ady zbdmT`4q}?Bec=5ZN<74>)!3VJm*(LWv{)Sma zpn!Uc=b=|DrENwQV0ho19%hxHC((N@KP8>f-fN=Z$Sa!+T7cLd)nG{bIL>kxg<1;W zAjjkJuUXoP(_R}x@^(6t_{DdL$~>MpVo-ipxNMt@{sx0SC}*3+)@GH@cT%E8$u$71 zI%6&n8c5E*cM{v(T!?RUmzZgX*fMJC|CKFc;d5z6yrn$)uoC6DKn>!+n-aBY{f{Ql zhvrg948isRRTE_ghrmc7@32U^h)+ng`7_BvEi^EAup=ovy-0y=v!Rnr2`Ysnb`{S!||`86pdUO27^LvSbXme0ZXq^zS_0ZttqI*bOu5C%O^W`0)%&W);S zMnxnHTn0}_??AW!{1EQcM_IFp>g47ynhj0GBB_j$)aR$uQ(0wW-$M=r8WFQW;t|uE&M9TbTWT!v_4sv2`scbF=my%FcX#f*i#jU(<; z+{EWp<(|gsHkW#|AhVd$dt?L~p`A(@3|pPDI_#&4!QH30lU0bv*B0O-(n+x7yP`u$ zpOdRbMem4RpqQ3N>SWA0%LhAG_no~l-rSh%rMEYOda=B~=$JZIz6Zh>E&pA{RV z-40rJF>eKt&*d9}$A@*3PkN(o>uzHv8xdOP_E~$EI^~UuHLzAAJV`=WMf=&J&O%H& z&|O9q3wJ|E#lYa$4Ogo^sbNe*Mi8CyR^fybzkJP9H{i;{J}Gd|sjqYOgAb%>cJQMM zlm}d3=|6(xaXkimIVn(L>mQL8*d2{eLk}LM4{4-Xgbc^&#TDQVXZXSMUbxrXqOa`I zok_nR?6PDy%ke^a|5n)KNx5g+ek3mRA6c@<|4QCpWFNx6!PbZv&0jSraI4(Pb|W44 z<^_?kSD{*=eeKq$x-2)B|B}8!k$?$Zlj6DRR;b7Km$ru3k(8X?-d1Wd?x9_jwaR*m z<}ZzMnQ_^1c}f{dIZ9c|H9XRl;T((JU}&fU`qJWLTBSisA&BzTXNVrViMCyqb@)-! z5YqB0_YEK6bbSsdU zX#5odQpV@BoI-Xeh_l3KWA&6woq0t1b?K^~v{v>!^g~1B5&qN52;W_c5I5k%4Vwft zv{=F46wwGl5~?21`7Q6TX@TzjX{qmU{DKNR+xJxwdwm(cFz2@uP=1V z$0<_EhD?t?xJ^015DE@VC1qw=vpD4>%do1l^Zr;rpcWnYr4D0o?20g@(KbB+RG5PZReA_DdQffmfP&G>Tjey<~Y&-z4)j#qr^x0^fiO_H|bQ1zgCw5@uCKa+o8Y zA23nwLa24H!br}H`$S@1{;N4R|M6iX02r6RFSTB*|Kz^z<&4Bs{#-#GY)$urZoTeR znrG4C2+lgKx+me+Qq1?UXez3o$so<)ubA4u4Va3APAaW_ zTO#hX{dy$^hG6b~ZYH4C_CZE`Jm4B&>~%Kep-p=k5ECi>dr7jHXg{ zK`}CXf2ie?%B6Z}@mE;~GBhOr!5~YHd7?GV^$UcMu6|=&mk$Ux^|3hQV+~}{x&J(v z=d!)=L_PMdf+$2#qsbQhFk<@KP$!)`4 zvQ6R^ZpOM4RGNY{vS{C;3tyv;0h^;mSEX}!QIQ4BNZvcF@Xl!)9uXTOidaaOwBZY0QR2X0^xpsSi<_9GuN*jP8 z*h3>f-cq?oPw3!Js7=LspQw6|IYe^mOm}-!NOkR1fVWOk3%FjYHfCSNaJ?DMp(R(C z+U7}Jd>``OAUoAlRuSEO8a>&S=Snh=S;XF=3MG=)>eHeH(SnI{*EbekCaM*OGLO9C>!&+a{73AKmtbN)rvA+@c7HkI?f|DhLUy_a zccPVjks{fv`K#yAm=MiGmfst?1NVfv5;2erh;n0rnI#?KP~s01#rM$2&sftW9?`KycovQYVv+sfF$-y>6bvRYgQZpGE*$q ziOfO5{1ISC%Dkw}P>-2`_?AdAOE~3GMN)o*^LZ1wv14Tvxs8EAy1p6ZPUMw~RMLUyeS-#42urSIZl$Q{{ISiJOpgv&$dnjpfP5C44N? z7;lTs6uO?h2H5Y@HSU@0=(W_qELarr6}+ySSOi@Tiwef^5ZdP_9h9sky6#4-g_`#W zUcr;__2{?*p3|0fv5zeIF|kM@Fkv4*HK>C+f-3X=+mD2tZC`HUqYT3R(G2{{zw+mA zOSb=Bl>hMoSFlvZRD|=Q_SL9WB?T!HG7DpFOuMWtP^o}W`ogFNO@myHuid_)ak)6L zk?y(U z5u-7L{32Opkc1dVH??P+E%Wra(Lm&&oiv`i&yhkK8fynKjkD5$Z{Jj%2}73Dtg^n0 z{IfU!g5o`Rr#4}$lpCe3*Xi44%@V)%G4{2T`nml63iAG_TxTolJYU;^YmVkUhNQGO zcIDG%WvO`9rG3#QRdX$Bs-!X-zR{&lh~F!CpUv3GsHT%3Tzq87iycSdCbuSvn&lYE zIp&74l6e$_Jyz38@Q#%9_JCuF8nz74EY+~Z@mU3SNkJ7>8|AHD{<_=114f6bLb*Lp zF?e2P4LWhTy*eCA-86IwqUzvmfjNyW2V<@4#2j;tOC^WBie2?(jJAc) zn4QZBUT|$?Cx>BpWvgF?8=wj^c1GFiQaB*Ch1nILF?`kFo_isrwZQIpZdYT3q?!_R zUvFipN$QK52(Xu)4^BJg`vl=d%*>}Vhe^HyhisA~{ALggjt=wBBNaB4S>ite}TVX-XGcbHRFCUH)H{h%1?Sz-c*}o?E z!E(Mn97X}JYfl~}j=H4oqp0R@zXIw@Tj>FAk2~h*PYXih=yl260Rq`6h)BR)EBYQLDkyWXv0-gw7J#xIY$9W+z1V!F*Y<(1dqN<8d>-2zGCs zdlrYU)to|=J0jpKG--Ctec;V;>&4F{{4jZHhJpvKH`@rQ%0wjR)b3G z`Fp(J_@Nhk>RAi=s~->vTTlq4+Axzo6g?hSw3wTjG32po;Vt%OfogQp4+O^p-c1uy zMjjH&tx%fh&AizepleK@!QY`UBrup`BbF;?n1`g&+7J*9E=9k~2y7}C$|J|{y_#SQ zje;i1CO0`90I&RHnMS0KssV@-8v%e65yiKJ(!lhYJ01Y$tzXrclE3aF{<=Ke#ICjq zew5eukLQ1m67~Okl-T}hKF_5;c9yby~Hx)d{RJu=nLV<##G1N=j9g z#%jHuhRg2W(r-*quvDBFff44+2+8#^{qYezurN!aEbyU= zw3CoahZx;lbG29l!}a~P_9R}e#TRy@cBL~ql1Mj^J*F&33Eye1+2)M?^4nb4qC9x+WdCZcT;V4H0y**sMi1JhDO z!#+W4Z+Z=A5#NvO1Ku6eXi7=5JHX%p$YlttHCAG1D5%pxaA zNFhl`zM=rEOkeI>Lo7r-25r!}d%{*OJ^1AUJ~hl&lW(;oatj$S4JCo%rI#O~P~dxo z`_zjXet?mh=Qfpt~xYE9+Hy3_w&Yy;g#}N5k0b0c?0tx{^_AU3XALe!0f|HdES_G?A zex;}-u!>nv#ie_M3JQ`P*W;7tXCfVX;T{T0FQAjMoX zc+vN$Lz8XjLg`?c6u8>E8DPa-PIN)#0Xn3Mm_>jIx0hJ$tFI^9v~{9J9E+bfE$=$$ zluVwq`#%Vg^s11};)u9Xgcig)rRCH!HHV*_ zF!XGn%h^+G=EFy^^|`2%+eSO`>qfew$@iYwsF*EZEPxQpD>(^P=T!}?K={g!pC642 z=t&=ZCLjTS83N>SitfS!q%Vr5@?g9E>btF0>Zg0sf5ZeVcx^?}#tvQUXC3rz8EgiY zFmcwpr-YWzhmM-P=ZKGg@Dp{%HHvbe`6?`{rG0T)FVU5Po;@q;*{9;A&+uak--xNi zQc!$|3!75k-PJPj$&-n~z(~x)^`82y?AdGsKqX-LB*my=Gu6!B1a>xzn@<(*k)ybC z?BbMlG7Ul)FH;Sm8T@;RkdFSkP`iN&vA(nl`p!e5*}ZQ4t71J{rVMp_RI9l^-iz`6 zl^6eKzY@!szrA|52KYe7f;`ARyMYQ|O5-pRgYAVBLkxx$QI$7?v>KR(ynW;}@o(qT zJ){sYOXd_)LG9GO3>F;{=`F1-r)gH?kg}Sjp|nY8)%*#|&Dpv0Ky;gD+n(w1J(;@- z6a-BZ%rS;T_y@_xjRYVheh0!|GU3_ez9n~&arTnxF4yuBM~!P2=%C)Z+l$M_r)_U6 z;&Afgv1NJ(5qtl%@-rGApElfK>yM~_Q%ST~n5Q9>&{@8runWNc zw$PgFD3p~ZMtaU8!=#+wZD=%Qy%PJ%SCm;=Oj3piLKs(nMv5?zx(UKmZF>d0%Ti7=dyXEVjS%t4Kst_N>o7X zNb8oJJ2u3ULBrJQgXg<=h6SsKIWV|N6 zpH^!fgOCs6L_(B~@~3588pYjUe(J^5zAgR>GgE2~vuhe40zQ6CK`>-SG}BOS%_Qh? zaFrqj2{`YG8Two#43jxs;=A`jL8T&?WqCmu@Y3`oYm5drC@zLVCEEco%q zETH|@3;cg7?DmEhhWZXdrWO|ebcL2O>f%7WNNxzc?)K+gt(FCvA?B)*YZbOFD)ScNuG0Y(RiQ1MSzP_a>pD!-7G8uS&oYl#*nEBm*|z-DJkO~%6aa_o4uihrd5JxAU5^3 zjVkorFKz)s&HRcz!8g6X7Kcu^(-b7sZHi(cAdA4Ad?s@ZY%d{~Ra&58UR%V&bM+3Htp z%ulOc3BFTa3G)NLI*USJ5ryx+=xtAFVz4$oIDPpE{1x0C3mMH_is>u`{fcdcX#f0-)WG(iVvJ=ne%6wefk^}EYs12js zkyMZ(iYH>^&gLiq27^o-#*F-xnXrjdK?G0u9|(r=_1X)v=xqH#lzQrIgJ9+Rb2)?I zgS#UnwNLLN7t4dzq?vt*c_E?ZmDCABt7>a-Xp3_L!NGFSbgz5B;q58*= z`ERUP(8|%$@PD`ON>FllMifMPx14htHWL3*P;vXE9zWWn{$sNxs>S)R z%G>KFb~kOOzKTvY+BHBNz=CE)*FJQ~-DepHi*`Y~Zul6zLl78+&aK-#bV<~w4A5rq z=)0uq^JGc2osIa(N;I4w&fZQr%!W=47&GaIqDLDvIO7$GRyK@frP9Y00%aqJTNd|| z7`}uILE$9X`%B26em5YTnUf+9d&+&gCk%&_m14LVT+&6bXYDhToouu!Z{=tDDj$NJ zZSqZ-Y+l9c;3bd{vgW{GAZ%15v}F0{9z#%739{x$Q&3_fr0+JOJ(4vw;WvJS$mQFT zpjnXjQ0+uH16!zC3Q686K0LJ9=1fAFrki`lrb?s! zx9G0H#n~02@(%V247Jd;L#+~IGBJS>bj2m5fP}mT9D%1h>;zb4@+h@JBg*LU35|8g zK?ZaRR@3iu>k8kvx6(@w*+%NwM`TQduojD)`9;YyC(hno;V;#&%o`y1dJD-Ve)~8RyIrr@y>6vMv8)!oK7fvYTha_E+yU zuRwZ~e|u6h#xAEw7+{-INFq>`wJ^?SSW@Rz{h6B;Tu}~qs9<7PQk`^hU0V(}VI6NbKV9bphm%nthe4TFfPg^49=u{2MO^~%JsM< zx6>?05k!V7b5!;l`Yph^2oWFfy< zzhPnsS(=*+p3T>+0w7^h8ge7TOjj?KW}+7&w|)#HdUWniMWZrgt6UV6k}utS+qI(BuUtB z8Le`=+)H${Ws{dVhzr!Ut@HC7*Omqj>RnaO3KXb}6(dbc@Q^^{l(ikg+5&pY#nAdE zd*u8(nDh9)Wwz=3<`2~%Yiwz2ydyqST^%DGWKGa3XqgG?Tp|=S4gYsz&W6xUXe`w2 z4%iv^>Zy(!IPe$Tx%xfc_dTBBN#gaP2Sb|3n1O6z1%i&Qb;ic&Wg&fz``j=sAar7hhQJ;-LK`| z(4LrR?Du>LP#{A0k`av1{nn$W zV(8L+8NCDFryp1Zn36*qT0oLZRx(lw3Z!5kwBZ*9TKW(b=J}IZ04+g}=a_U0Xr-)y ziIJiy=!|rGuG3Wqx&lkvi1W|J+xGM0Qy`tb+#9c5Th7`BSMTP3KAb^@4@Q*}Pjf{`EYn4hcBhW(F>u(ij zZ$DzYtKH-Yw;tB4)Rp5h@AvKNduZH4jKx$Wm;ce+Xsr%S)+~B8FJlO8;eNOf^js*4 zBuQhUecN{ZPyp6Jwl}@6o=I*uV%7AM2IJs;!K}aYUC7OkxWje(+UdJ!&KC|+p}yPT z9J#1-8L;!=@>x1UiI7~UJ}>s4SzZV(I83fbE1eH_?Y14TqdtDGu5g3+*FaVs4(zff zZ8J)g8blTeU?O%v7~9==jFbSpLQ^O*?t$L+X+tH`DY^O(#?NX~>8k>27wN0q*ASyr zc+xBGNQNdQ3q{yuX`W$*>t{t;(~1ciAs%+UM`^q<}G1k zTn4bnP$(nBBfux`w6;ysjY47w-w#KPMY8zJ@1mbn$+Pe@OVhha?`Rzv{j&MI0QU@*e|Uqm+YFUc}2yK(uUwV6HQj9 ziqCS6irSBVDaWGgi;ftG#m1Z{mtcNy{VwzRB`8{D7=7)nFW4Tw2exm^@WcAV7vE|dH3PXGY8N5mEVeC<;*gToWJb@rz+sxV0^xaAWrjG#w%J*gP5;@KQMXtr9?cP=hg{8-Szl<}bqpDb zpC~%FpvPgttkT1qBw`Y(5)UQfVFff*qleHX3nT_OUNMuatN|mxTVvXemkFnjUQ6kZ zSD1hE`TiK>Uj^s4uN)!Wf2+{n0#?Sq0o4q7^N+zM64%^Pqv4DklB(ckjTr7|;WrVv z;6XT1Mra)|kyq9>?9`rfD~1iCqpDaC{~3WNquq}^$LERhB0RxuDei}v8Eo!T9SmJw z-alco149GdF)qw$nK)-JRr+!RV*~AA(2?w>ZKz-aPhIl~;s)X)@h?u22kIliljpAB zSkUPUal!cL2*vlg0$BjwmU4~HcTStB)n z?<1yxe1Pu9=1w>BJMf#jC3Euq?n)a0^xXtlH5zVI5WB;O^BbM+9ksStp4pS zc>~0Dxftj$-wxvOA1luP4+#GslqFvEKy6wT{nc0?21Xw)RnW+x`@O$ikrjR+EwSd3 zz5+77l?5}fU0l3ZTD0f%DSzdzwRR}ctB?TBVbFYM>N@QO`x)rw_Sr<=hf^TA%ai_Y zW$kR$<;;`*;c0E{^+P2VKO6^{0G1xKt7`idm@e{pwhuc*3)y;;4=&g$=6S7;%~$OW zyZAl}9^JhQ$VeC0mZ2?Pg0ri8pT0mIzRe59NI6%J!7XNjwTt_VJ{bc3^ZV#NIo;_k z3bekKdC+@$P228JHat|KEkP7kS8-w5UJ<4PtO=hjVw?pb5&{zg>27Bps_fu{6gzcN zUqO^ISN~vSG@vv*Gh=7B1PWgmlbulKhfP!yTTpOcl51M<6)K*#L1#i#yE%+Qki;9s zT8ZP+hB*)A2DTY$22j0IDk<)qDW9~Zv%4fI;hd8gwXGcuwauSugSz(;fzB3Llg`3f zyIvZRjE{Q5j4cqyTWmkuO#=Z?%!vEdL15Lh zPy%5(O)e76Oc&HN0NZap`o*`27p|R4RqXbu&63C?H6cj~#KEg!#KHlZ>_RBGKvAelnNMBrvl`<;jIXxpV)&*u=?0cn_vpiQjHcZCG5LBUmRYK zO4I6Q)TL98>#dw3)`jFw=ixjRfb33t$SdzWlO;U8X-xJN*l%bRR5?5)KfMdwd~cz+ z^&P!XHu5M9C?d{YyU}eaT-}#!gJ_>+P+wkupuch+Eu*A+ix{iE5s%GcGtPi_pA}6~ zAmqolxT4;T{iA?0$WXO2;>$6ILQTmiR^TC6^3FQv&%|KQ?k*;CS^Ie*G{cDAeF$2v zF3|)^L|qrW%utk8K&^>kLQygzt+KEcHNn|vt}Y3zmtv|S8mKGFL(>8Bl9@KffLMz( zQnpv*k@O(%mo25p&=15<@P^ZqGELo36{59CTSC-oF1Qe13QAf4D4=W5Q0r=i zHh(1|6p2$7%7|@bVLwMF~T2}O`us2)S*UV+I z9)ArGSJk|q{iOdaOrJf{D#^i8?%+i0UWJyQGxp`%i}>=7nL-B^{if1siWb90vGB=5 z_31`XeoA=)MmQ=BhiV<5Cn8->qFLzEbuL?cz>l_5Y(*#E8M-^y)y7x(aue zeIad^l|3byNpk5mFGLj!I>s;a#`v{a+ z--D%KOzW;3?< z_$YEk@-m4T5V))D^O&#joFYLDDJ|2}IdB@%jWH!Q4r}>gNNTX9NH(g|x#UDEr5zX; zfugIEiltd*Bvmk#NNgxyz-KmylL@tij8U6o_a%q{h_iVOn29+NcGJe3Uvv=3)GMY< zIO_(`Jn1l*%^7QBD9bQ32wjXXf(yX8nj$W#~$lYeYq?>JR&(C zc0)=NL&gHuCo%dlXRpb%{$v~7ryqBp(ScLWBZNCR| z0wotBnc)*op&F!BY6&Ucd>*)05HtB571!4+6^LySxVw?o%)YnE+uZo@ythaH?&ytT zkP(p|kr07^fW*QxeJEmxpjB50wT*N@DdPWYZMZ%tRS0d7HFMCT=uL3PR!}SkX}1O?a|dk~oi*O9|w9i~gu( zdcwU6q6kmyCe`5OSBBDvx8ed0bA8t1!W?>O-?5?KQe`X1#->gzSPwTehNaR^OS{vS zSHqt8Jb1A90dt}NmovNGB5rK`_Ve%4q~(Q41NGK$!2OZZNA<4}=Py<`>2E>9Z+~$U zdmAgo-%=LF|Ey)L-e#Gux8qe`QFAc$h$)OEs0jgFse@MBmqagX$jqH$B1?~RBtBGI z2=|4vI}rIs+dIPbL3E}m)taxYG}j;MtV0kQ5XJ0){X(JkM&}qe zs~syij%Xxygd$$o5vrSMBg3mf<)OAxjEwO&07pA!VB_N(93lpG=v;DXcgm(Nb zDrYNT3{12z6K;ThGr2DZcNZd`2Y#!6&xDp}Z1-wikdS4gc+7|*z*7hIJ8!2Vpt3 z&Sm-qTWMxQSDMU1qM<1wFe-6Qs&g6`oJnF*hPZ0#B4o5D1qeMkF5}Fwav%_f72G0F zN^TcP@wWUHa)hw0UGoKK^(9kq>#yXT3j8m{z_-C<>5o7#;9q}@i0j`PqQCFCmHb~z zQBSFFV5$Ni;{7CfFr+PcELxCxS>1eG*JeXPkLW11I>#ovBfEd6VHl^0$gb;~ny$8d zW&XOxpZWQn?1b}#o{v01nFW{fZVS;U}NL#b%>?r#8d((<7a}Ss;(n1@Lo;JL90DwZH18sU)dUdAI<{ z@;7g9i9nv1ZtGtzV#eRAS!B!oji=RM;L`)Nku4~T$e;C+E;V9g%S@pIJtWon>%~QHW6&g z-)Zk34_$&nAnZCXfWXWMNMS6_OYXEpW4A2`hNwwt2xJ5DGIqcX$)A+tGx8kX%H6wX zP?bX+3>^z^ds&>=2W3s2WHt>KBs21@Fg~W^eHdzGdGyixbgVjXdrw?QpT4#QG0~Vt zov|xQxtSfNj2PH)B^`_sG+$8fKvsJ#SJ-hfVDP1{1p{764Sfl~$W-&F82ZdG?C+^G z$yoYQAO*S~|BxE0qtR*6LNN_Rj063uiQX$=Q;Hs;zq5vW43&o%HhpbLFYBNX=Lmmt zQHB^0uRRbMd%5);VR0x+Jkw-UE8}V9yBOA*k$PJlKge`0;}gsrIszAE=|F%3Rw}_3 z8WofG0hTx;D=Gvqz@!-+qNp$g(mGotTB|0NtToOrJ>K~g~embj$>Y?Ed?PjZ$ zK8QbFV3}O&6B5s-W}gQbFTZn)%h1-(Ne__D9uWtGN3CN)vttt+&BQLj9uT2_ADF@* zXU^ug!?N~A6ye`M<-eTA-UOgB<_->TjQ{`a)qZcrml)7_>Ex%7XBB(<=Am*B3TrG; z#zbNPsB$|}>9_5vXwt?;mT?@=_bR|emYP8Gv0oH5sft-lqPfesnGRz&cI7r_UeAz~ z5ylbqcxN1|4m5VtoW7_bN!RV?om~{6a&oWw=%G26EG5sk^py9f=L+bUk4iOA)Y~b+V-^p0(G2poQKI+tjhyFO={% z*lR(xOoN^V6xQ&3qV>noMiee_(aj6yq_OnGlm5fJ)!;yL{%FD}b+e9jC#!^Rd##-q zMmy(x`h&ghug?*ZO7=LV%+kDeUGfii;@fg}_5Bz#UaytZXk;}_P4zXdNaV?OhsvN- zg+o-7=9A%WSq&hQ7o8*dUBp0_2u>qI)og$fyIvtbB$$3F=cx2}>}dxjLaVq#-ob>{ zNPz^F{s*!a;p8X{O&Qco{$sAlc<`3qX`e4y6yuq74r`4H%Q3N=)Wvu#IKucU^0BU_#l8RKhNRey`7HoOW1V$^(Nxg!4T}>hQ419AqQfQcs?*p0o z<>=^eswZ>v*$yKr0Qp*@Zzjpd@$e^O&CKs>wLyyQ5yTfx=U)pmFBSfO2_%kv4 zr&!eKG90sYDS7q!`IiXVqNbxZ&7~kRmdb%WiAu5VFM!{MG@z2ZzFZfwgR8JOFja+* zE5tu@L`$Xw!tXpAFndR}p~IIz6}gc?afS=cKVb#H4`nHDauA9N@mG^DOy`>emc3Gr z)bS)k)z@=@x!jdHqqH+O5-j0?X+=M5xRy4zH#ZDKcjW|7Hl2Qog)PVX$f zG@ORkXL$g9P8bFD2K%$12}F`7c-R6Pn|#n@7IJT~0f%Q*{T~!kQr0skJVCv$ihOEs zF#B0dDP9Z$FZnZT30lk|UU8veIPqM+7BS;OWcrfR4ouw--P~JoXEqHR7q4&l{B16G zXjE&J_YgB@=K#J%rseOo(wMFSN08;{GYG|W(N0V0V}&(8J9#s|tr2T=-u$&Fio(fA zoqIbdkbeZu=>K(C{--qecPDFUWBOllV*drqJ1JWI7NL06(c|$V7K!Lm!4iMEg-}0Y z4@fGE_Rm$7)2y@uFDpo-|yH72)y-A!)%>g@IM zat$E>;qu0PMW>DXhyS_}5g-W(>^EizD@6cNv!OOvk_6k~9^4!0cXp6kb0vwaehdBj zmWp%oJ*)(gBOd_L#=tJZ&WG(ejM#@crEV*F9&efEdtdS?O2FUhCHHI~;zC7Syh`4q zgF%NQ5daJgKpQqK!#_x#*<3*G63$vYtAn?pGx-PKj3!FBr zrN!gFqipYtDfFFsESlOJ{(vsBPIAH5g}mbk?-;h-xxJ`_9n0SYyeI zfdkxerw|>e>B!YVkXM(tFdg!E%oLVM0@Zc=(~K6@S4Kor+>=;e@PG{qf1@>F`Rv3_ z&U~QuxcK|02T_{}wxklYP<8fB_o?yD9C2#~Rh=)NKfEk;R64wlJyy=J##zngjE6R> z?61HHqAfc4R_@c;-cFCRj(Rdx1|^q$5v z=DTHgPQpx^vv-J<#(G@>EA1vQ*m2Sf=t}Ag!6}%7l{BRWJ)K+$G}D#+QC6oqjLNCE zWdJyWpz$rwvR~sz!!s4wuz_CtMhyBs$Lq(4Flul24b}TO0;#X}s zW4&nHmpnx{_>eg+c1)Gc2$0o;;c?V*&(j77n_1)b{B|NA_rGj3G0p1a5%)wTRyW~N zMuw_4u*W=Y!4R7Buk$98cE(mCtc>Rv0=_sP>dimgTty~V4J%q74C!$qgs2Co$GU>P z>_9Z$Qipv)*E*qu9m{1r`~X`N$&0GE$X{+%d{ECO8dPS;A#lk%nT;$P2X@K ztu^`$1_gTCYXS-y3^%Z?wOk`$;OOC7{H=&goxd(uGXzS&T_x!Os+jZee^BGe-D8-N zO%7IU1IYXFn>x`Va9uMTNigmsidB!IDcmP1ix{#?ow=(qx461|(ihAairO`lGji|G zlh15!^`FvQ8e4$BIJ>SF+LBf}9R%l#F`E?wMKvWn<6c#!ScctVT}eJ!B;Iz~=cYYq zjwqEgI7#(rlizafd<+^DGb_h-&*{;_a?fd&;~g6+sR z0?-CGompdnc}uAra#<&Yb7DVA4kp?h%~Va7fMSRmQKxIEN08wUFx>!L5Jjt|`%b(l zAXXGB`ePAIKpnPOw(g$xAo_@w2jo zfaFlw6eYGBj?HFzLaMz*jDDqlg?O2N0Us+Q;C1eT?ujmsG`k%S=U~aoEKja@Cz&Cv z<~nuN8w}2!{Cg1n%4W;Ki+xiL3+l>_bCca2qeuc!HAVI6-0&QDjn^&8p0R!^(Fs*0 zEqMV5zMB#Q7=6Su;Sf#yAqGI4kVC|kIkhMf?C@4f;{FKBkp8Qb{9p6u-#9>?8qAwm z4*ezHzXdrTc6v58M`euhi?}A3h$iferkMf@I=W-AdEa z7dYi<)uXnvBX5PXA>X)M$LzE&&#Tj?HutmUyV2AZ`WLr7&vyv-Q-Qc14T&2BpaMXK zi7RMB1GERoFm(;sPyx*WbWL6THe^7&@LTX`@Uigj*sJn+J^UJ@$JjY|S0p??>HRn;OG6fUGq?0+g2J{}NY)(MjAZxDyp4SpCmvJ9l%l3ga+vQ83O zCg0-ICfoATrrdhJOuQwiO}#}FHs}gS9B}=hHsp#P5!S~*zGVjx3hQ@8HOHo3$HAuG zRK(`)K9p+E=^bj2?UmIgGE5wAg^=o9@~y+2lj5LK#$757qfn5Uh@H^wQ@k-M@i)9Y ztjYPJ5c=uCtVYZZ&-$RI$w&31A)9g{qK3dl8EEtIBzS7EArZq!Up?<3XDm$P&5}A<=YuGldam#pogAlPEynvZBJAs1h>L;)?z*jc)z1 z{-gd1k%V>KolUVEEJjU-bj1o#%iPaD6k`)MHfo!ze%iH0XJToxm~B}fyJ&NlOUPli zTHH5uF<~5UQQ7P#H1Fqh)`Yw;r&KnD#K4faE*bMYGE|R}&6MJj(F4?$5__Z!XTu~Z z=o(x%A(?zyk#TqQp9%Sv=<0=CQK4PV?lLME7PcLP9gg z{YZ+&p_KXx7cSd+$4Mu)A*cJm7E%$_d}ejg=Db41uSPR@Gj+f>(oVa zS0`eP0|~4-->F8%WZh}_hS6B~v%Vud(HQ${)$J!Gxxw(m=cbOT!}Jm6CzIiE_gRSR z$1UaOqgsATC#b@ok>F0_632ivL(=W**Sz2q<=Y{^p-c_kJC=v(8(HZ<6>h!R_%=b)GOuHRyIHmSz9ftjU6!MfxgoK! zln&~?|7P}tYE$ep{G{*rw@=QvJd$9ULiVUTxW0K$z$ z@6GWH9I17{0BJgHr&6^$W_l;6j0+S`EdYb~MCo2btS+TpwSPsrenYnbI@nGq!|a@_W{bh7AV+FY(Py%Eb-TwD( z+CP}>^$3%bBB2ip@M(lN2LiSWL)coQv{j<5iF%~(PSd)Y2lOGrbE1paIW!6Vup>$d z_frHc4>(3GFQhx9CNgIg3C_cEPEcx~c&;m*QA?mSTQb|R&QIGnefp%DzG1Ev6rwlW z;uor;o-3egs<)$2AXQ3~;`U+Vy8PlL+uc&HR_LE{5j80=L=m||t=q}glFk`Vm?BUc z1~ubfTHT{0!gOpq!T9UvaOrpJSQI9I$5f=uWUz%a68x`6~o`MLNR4 z^G!R-h5DzR)xTu||5Nx)%JSc{yuRV@#OMD8$^IGX8&Bn$AO0sf!QaD0--F;kj5SjoDn84M!TZWP!}BObkeS=1eIV~uloj& zCt!b(&1t4b^v8u|b$sGNoUZJOa>0?4=o|PVxNQxUb(}lj#N4%Zahq4c9X^HJwW-m~ z3nDYMNqGGjB1DPqDL;SNB>?eA(DiH9QJ*JFR~)j08g=Jq3-5&p1z>PRU_h7Y(&(44 zD-nw1r*NjE=Qo?Q`Ar55ybGEDcly7#~jtnuUo;KNr8%I`$l2Fl->3Bg5(cIvmojEkQW?gi1 zc8jIDfRH%Pz^sR#iV0A`RRL#1KkAlPC?J~Lzhta_aXVq*`HHR}79J&4qviAMT3^h( z^mr2|AwN%yMZh6$#pDP#Y;|iS9EpOUtHpxn_5G2?%6-Hn(V){7V3yl<3t9Aqd9+n- z#x}sfjp;mg6Be@|+k!MD$C6EpP{GoxvvUIbm&zcg_TSp$Ca#Fy)wdr0&k~b=2VeX% z*$ch}m6+)Nr7L-h!1!k;q9`Nx=Dp>~OAQZCQrU1HOiY+!OytNwApMb?YGEpy{Cn`S zo15DldFx7SGC}7H`YSjVS~ww8j+%GiF9gGr?8FY>oRrF(yUvUXzqj`@q#oRz&@!Uk zr*4CwW}*b4Qh)Vy;h+O(csqX_lY9oy5F<`fxE6|3Z4#L=Ey6!w@nV%&3DH6V@$EyCXc;nZd}rpx5zM zGak$z9PH4IER@3da!|XWW5si^Nb-G@+KNd!k=qz$6U&(*U4nui1-9z0etp>9P{YAs9!i6osX%lgwU&56OTT@;b+iodQ|PE z!UeTTO{NW!&S}UTBunk_KVgS*kqZX%P+JUh3aBwy&O=EUgN?*9^^pOGUd{ni^L z{}F8^`d@wV{~hnGqNBDXiri|^&QulV_br!}#mSeb>H!&fl2kwf+@Gz?&pa!a{5i%r z8QN$d-WZmj>@^E%fHVA?y=?5>ufmMcMJB+4jO)1>tNY$)I`-GQm7m_P;CeXm$ZZHj z_?;;}gdZ$n?qz(a^u*d7-h+H}-Y2}%5|C&=1H*f7{N9?K7_em`DU4j-ki0I6LZqiG z3|%kc|3DQ+%I+d89wib2z?$$gM^R{VhoD4hz2^zDmIe_SfKoMJmDZ8KHBOYr zx3@1bsmFHP*=FW{aC*otAbYAr8m9{Q6!aj!wA}cS{Tp+m&Lqd0r~}Y0-MYvi>FY|? zF#8T$K}a{)6UDn}jH5F@%J0F!Mb z*@k!?x+ms3WrvFQjoU^9ToGZHDGncul8^O{Z_QC#&tr&9 zJ>XJ)$H}23yQwV-N}BfYn$oqVAZq+ll&kd>M5mb)2p^rMIjaUAcwPrQTUq36xU)>J zV~uz5XPPUq$e%_sLpIiwSJGa|0!yu(_`FCTR+WU_=|V|kw79q$W7WJ`MSxCqI`fkd zRGEW?gZ8oGhO%t#G3|R>y|mSo!ktsWuzNDw?KP4BQ-WyJ#LzQ!k@(#18a|BU38;OX zFhWzaB#FGM*3W)a#a=nQQ_9Wp`$3-D7UY=AXiEL*{F7+7^FD90^7iyo9a^!mRzOHR z2d)1HJ?wdaPa9Di$9%MpKTOG&s| zEGSct!>4}#dP*8)WXyGY0`GgMJ@^r8%CRPf+Go96bj4eA{p<(QS%sdDCkVY@M!iRr z2FD+Q-}Ni>jHB?q(R<|>IvMQ3aOFOy8@Ebgw_B6+o1o~sRQ8_|cbSOyD3!WVkryT* zx*@V$O{1gCWwwFsQm{le>zc)6Y>Uv>mc2rBwz&7*ACq0p@ygx85V-2s7k=e2WMRF< z@hr-k+n2-N$Wd$i4pW7DjoJSA7hQ&jnDk`p4Um(7{!{DmZ|V6z;n4E>j%E)3qw)AF zgXTZ01cjwHzBV%Nfz?Tq>d`Y@|7Ko@)WrmG(B!AHkh2BWFGey(?9_*98uI9b-KPAK zX;#!`69OhN%zMNBX^~@d?C(^hwE)!&*7}{DxdBDQm-}_mA`_yBb$(2EP$U$bfF5L^ zv;KvDR!gPlfK9`vTD8P1b*Udah^2lPPlF6|`GMN$?(Q#n(8KHppSs6Y;Dm~DHB2d{ z7Xz2``7q#}b&99rIo63x_g4}mkIB{Af+89T6&t5Hr}!o%(1WgfqxRz7{oTn>!^?)ITuQOWtk|z<}USUansUnCu}vZBwAff#i9itdsLD z0O1t|H(F%+ zmgu-7Ks=OE(TAI!f>uMR1k1aWR$dJ6`1|kOP+f1h9>*MYFD&2j@M!(j$T55Jo0Zz@ zyUGBW2#km)yb?SMJS0v{ydykZR%yLOAPh6XV8dyaFC~)7@T0mwS0C!>y{iu=;d|=` zvrTefE13=pC`LL_CV3F zBsA{Az?`NAdP~?MM8%-fYgahe_`X}yZW2FNIDJ0&ZSl%$Qg|9hH$zFa> z4itfheDv>l)xuDjpmKU(HBPFja=9=Exe?Pi?ND*xTj~D^eautHq|nor&7jw zLvDiXl2cRT74r2uWZL+WZ29>wXQ`@nnarWL<8k}P<01OL`T}bcb5p0kWt0CqaLV>~ z0G0gTVRv7JjZMsdyHWhNgKN05meQ0w@(W@dxeWL;tayuRS5idw zElos#bgWy7>k~%XBiuELDLP)Vf-auvFNFwqN$UCtph=bK-O>tuv+3Bt8ULFz3ZT;y zLjnU!SzFZ(*h33p2-}OHt;SHKC)ptbF$HUav8-w<)}aG&M{L%`iJ+f0{rzJW5mhb4 zsJNZ3AKi#^+6;1+If9t2WS>ZG?tY?yKp#e~lQvK^FqjV?4KTy#M*x{%5R`{Hlp!7$ zptjrT9s9A6Bz>SvlfJCfu#tKJ;`99JAOPsVb(N&g(>lcdS$#*un2}$Y^1RHq%VL2Yd`V-|SDXtskKedq?j1jT3@0?>bpg`s=L&@nhJ2 zm3Y%MdDq@m{1{F&PMj&6a6EO&TQ`Nv*fTBQby?7b@h4mPDy3X_(7S!;I`KniDN_$R zaxQMXroJ0H43frg=8UOk&oXfgA`c7;X$O+I8+Sxf>l{pBySgqch}S-|d6k-7U&WL} z46km`_>_*ZCTg>V$mb_CO@YumtWGre0vGi?u)YEJx+4e1tET>vGPI+Y@HKENHKCSr zmCXTt#eM(yLG;|SRY5B6CGGXjI=rV7XF1+LZaH|8WgTt3|T@I z&kEg%qF32a{mY*(Mw9k12+Kwqiw}#njXjAw@RT&1-Q#t@Xe^$A@mAcZCHb+OuKF#_ z^;g?Rjhcy>e8EZFM70F3k0U`JL(z$VMzzEr9xCM&dkNO5+$_o?x4-1v=-8}bPJ6jQ z)gd!>nS^3LhBcrtX@*r6C8H>@VgPkmaxS8Z=wEy#eV;c~Q@ z2yfAvSkPuqnU#swSZ_j$Sgv<3l4pnF?uW5gqJ33}|Ef^?yH~|Jb(${KxeHcFGz{vsh??N>l9^sxq6t7ZuG)NJvpqx(L*_ zU`#4iS3Tl9>+@4GEMN4my^3qSl|ON}#O-qWlilv{_OHk1XDAwCe^uziU)CTkdNmG#6sU+Cp=Zw3nounxypog#8ej^MBMaNHRhnngSjdTiQJc6YEC|UtHTvM>n5I* z!j>u&XpM|xjkDo3cD>Au7z#FpZ@h}$NL4oylx-t!9#4!+2iBz#d#`kW8J|vCW9Ib8 z8VW-Vm7!+wf!OSjl0u8)x;F!RZOvO(s5E$+d7F;s*Jwjk?BW*#%@*ims z8Fwk8f1;s>e>tIMnt5*`SF!Ic!1yij?{#R7XC(FdTTSNq<2v-;LiV=>+<%Jh-{ZQc zik8EYF#3zpP(n_>tihtKp~i&GF$I1o(_49U2x6b(be-na;DJ<;EV zoZiz1dlE8f;}VSd7X_%gs(*yrlT&PO=`4-+3IFUd)7$$2p&em)XafNu6vx^^eeygD z6vb)!9NbT*9SDaUgD}7#K3t*+MbwAAzSk!RSxj85bDc0S9Sq zAem$i&RhvUyxH`Pa~ghlO9#BZd(QX1$|}fq$QLfYXa?#S zrCNLUL>*Uu-Icob+KY6co+1-$KJ`0W6&gP5~TS zOVpX|508suq3RnS$?p4)TsIG#JO@&il^;9C>K;qQl_^wFEqTxDNq*oTFfhIwG%7-< z${>?)9=%9APw?I7u})NM;(`9D`kn4!Nn6l7UL>QndE4E}^iww8U>jzl5<}IYs|W32 zjcetACf$$FDlgc*z+9Vq1-&a{v0V6-HRINm7Zcze@MmWJM^omC7X|}5Co`zDfSQaQ zuzdvwtG>K|Wr@S?13T-hRugo#rTXXorPG-^O9GoRJxjg)Yicfj4XU#rh%+orp6{{1 z#V8cTJDY~Mmq`}I!G6)_`+E46LbaS6Q}*+O;XP3oPpf^;sLAAKCX6W~v^j6rFTaxc zJha$8&ZR(+P->hQK<#*`el4+N%v-E+I_1|7myTG^ChSOm$wrw(nkVZ&L%_(sIl}hr zcIQ?${^h~TEFFiJ7k*Q;ASyt#je_nIFP4ZMt08~peZc%4qbPel7=(=?i>xkDCS#=S z2GpbcIopCIyqCl@cLSHeGt5`)+HZ4Y)4pL`^m&Lwg>Z_+L^*AdbEJJu_v%7ck`Ye+ zQdPy*hh)<>5=P;_uuN{;Jrq}(o<3cBmQKI;$i|c=PcbKB*Tzv6p^q5Vzkn_@D4(!k zp#YS|nco|bWO#j2R{PB2?T0fjJYb>v^PcoXJ=6FOY6T+e%)Y~br`nNF_wgpau{%XX zX6>HU1F2yc^9F_0MOim=3Pu#S=@Wkf=`zkL#I52Z86H23@%Hqe#T+*p>iGK;b1RVv zLb5CF6YgjVwMuk{nGKf`5ZlZmDlilvFFZJz(aX;FSU&;R!M zgLw-D{(o2be_z@EA!hyi%7&|G*}PeEzC_qqRIu9_xak{q=GkfWIUj<$%{SwOaMNbj z*)W0yHuAWfBVuV&S1MP-!8XIOTsJxS--!stzbj%F$lie<^+GooJ&nn|5&E&s$;Z4l zI?T?T@qAU0=?%dUo@zdguB~uh=j(#bf7VLsZi#JdN;u|A46q+UVg@GXASXgo!Xey)%zqlfqvb01x zz+X%ng>DF&S9vC$U8W3_jVuw(tBfK`_rf%fjC@8MDqrxCCabi6Q06Aaj^U_sxeUZs5KhZ_{Yv8-R)OqK>@>3G zz6E{i=(aQvz!s_D3VNhpos*)^e>U(R=5$)9gujCYIuC3=w@olZcSNaIv48Me-OZR; z(X1-V_;H}%Xz$Q*Hxy-oo6xXh<>FsWuEUh}z~3sbW;J#YVhrSqZmjNk(h@8k#-Bqx z6vWu&=I%+xW}MU7*QS~6adb)eoPu=Y;L*g#&pbyxdV8ony@v&u)UjseziE*0_vZ=j zR3CFzHRe-@hzL_yjfhF%;G@Siu<3D7bDOgqTij>AYcddvF#_NxQGufO!~zAEU{c&? z7?;NYpe~Ck^rcQ>cRJ-?Q)Zwnd50wwUc*S94-nQM^uUZ{Y@GJ>HYz;7)ZRTowPJ9Y zg+SWmZ2%1bCGx~!s$e)Mie6BbgbPB^2Br&j$D>C z`|94p?TNBgNa{u4DJe*=F4yGXDCiTx(lx>+sgmAxi8O^z4eU!N5R$xtKkK>18V#z+ zqBx@Tyx;dUQy71_NqQ2*1>#VbF&4bw=)qtyQOe4&Ix%MEA*zOvui7Xp+s_^NNohh<| zl!e^&o8u~ms~giRktN(E?sX|h=qVHm9eUevE2J7cVI~CH$;x9Fuet;+w_# zDPBFS9+p^r7b4M_=@luUqm2{?G^)LCo46!cO58|xjBO)^t_F>ko>%-;UwpTf!?wF+ zdL5NI6mAIKwbnbdU7ROo{gN&6RLS0J&S4^R5M&)MclSX@l1k=Bom9jt}>QM=4%- zBC_)@KPHe#Qz9n{*SG5;p5W3g_sQTJb2i$Me3jjyD&ok~v)nZ7#Zl(bD88S@8romK z(5hlyc%-SBg{zdx@sqoqhEF5!GZhNvIs(75#ZJn-n&p}Z7}6dri(r|r{JmL% zAAI7gc$;ZB{y5Y8n=|@m5L&v5+e5d#E-PTew8}h4};FGxufRuB;qcmTE1No%vng&w?%-Z#8dyT z#inKX(3hTLd%h(~zS`agMvxu&BxZ=NeAG9uDeQ}!#QCSh_RM{V20qR=Uva@y-JHN4I@wmjjt3F7hX_y2B3 zNdATD{3k`}zX#8M5ZgB~l+>HUM%uN-)0@SHulNr=q**_UF-$>Nh%DvY&1bUSl&Vyk zrP9KmgBc%?Ufy}fC0T`HAsFd0C9{7Wp1bS&=7P${-sC_=e-KnWcp!m?cZS5X)S z#G#XwZ3Fq1uoi~oYYe@mbZ5je{K1%M`N`Lgva#?VZ}BA%k`zPZFH+9Uxhvzrr?)yR zU$G#7y*+UvFvj)R#iy!3!~=sNbX@j%QM)V0bt);`lw(>Iy&-*l5q!?lUv4$*oxcXS zVTZhP5!C);N9KfoyTSbS{R{v2ANl zVqqpi4wpa%)ey(S%^Q|X z7+%(~$IFbzX@Y#gO$fU*S9UG}In>2ZLZ2dyibWuqjLaH)xtSJOMHOo*MM4-GgbSV_ zptd#X`*{^Q_i_5^yZLrhEK3EdhbW$LQu+5$S}hc4Y9sc#Gl!$CT8UplKElRDOs(`N9x6=fFo?EB$lH zPP8*hrv5rApL|kArI#lweYuh0-4>`J-&3#W)g$uiNGwy?y?2C5-1qk0uckO-6&Jre zkcjs`@pe{a_MXOcexA)Fs-8MX>lAJLEKjkA&KJZ$iRGi^nY8$sQk+{?;951DDhr$NK~i-FA0Tg5!$-Negc2R1bmof4i4)p(|3m}Qxr*#9@z`Msw0XQmp+53ptaYomj_U@y(ZzB~8X9`GDiQQJ9BfSqD;yv0t?$D=Fco zID|rv#DhuLtqyd;$lGi;AGt$eabwU*>jVm3@QOthO4_52Ajecox5;#QRqZl;oq@*C zNwkzH3F|e(S0G6G?!tjWZUC0Lv>EoplvyewRzTI&^QY$l54|4s$A<`!#d@Q(O1*lk zB-){n)Rl(z*S~aKGSc_3bZ>p~*dLn%zJI+ve{2rk1b+Wh{rR6oRJe+U1DY7}%WMMA z(aj?THZQf*R(Q{OL)oJK90Ww$$@C_|*8|%08L%bc z*?`F#Y<^wqOs?>JUBq4ZpfhYQz5D15zAia2b2BsV>~Cjl->KTFe5+Ea3G zbF1>CCsgxguDWq%whi3kW;hS&Hh=;K0s7kLu-yx7vP!RFH71ZhXJ#_Fy}yKY>Z3M0 z%BlvQ(?Vuu2pBQuv{GLE!eadnq^S9KKkj%on6ax#Ni)n+I)OfCU&-k}Ys*dY zmq^-mvL8bSlOYyL5J8HIxi**qnW&c62ZVc zgNyE8>*oW)s(D{w7SP&b2%5?$CeBh_~)%zHtj=4$sB%Ftf;?C3 zq1l(%1Mc2jf+3XloM(${<*_oR#uIiV0JR1HEy){J(^YuPSzL z^52+6TLG3n|AYnXWMSKWeJvZD{i{skpW8Bj!2(M94qv71FX)f!-gb!>6ckhtRK^*U z)fp641QY>AI;*_DR_Hywzx~Jic5Lo=`wvSI(8}$%+1!1lrtx$o5zx$Ozxe)GrHLzu;K=Cy+`D;1RuNEAP!UjgJtKW1 zJySi<8g#Xv#7IC?e~t}+0Ns#&UxhjD>+|2ohJVg={||^m$k@)<#>m*l(Cu%Gqe6Mj z0ZSO3$8Zc&U5Bg@kqle_Q4b>4%5=|;DHdXw%^;dCi)3N>KBx8~eSj>BC%`yN ziR9_(;76AAelpC(tI9ylVWxg3I!NR0>9mne1N0FiHi@*|OnU5{#c&7oWt)m_JBr(I z=ayQEiRx&cQiJ64UDxqEunV)T^xvQ+`DuGNfp|%HD95i-i5JRIv+!;KA~2`V*yzhi`K;A! zoy1vQP)j}ua;{g>Llk-d?EnT<|5nm{P97P)ff-k-C}KKk?FN7Vs4T0vBE#ISdvdKt&b5R?>w zu#RB_9%=<=y#?$sWl&?M@j_jra;}|Kwa25mmzva;CRv&^;r2kh2H1Bv4U(@7?q9EeAD4)MGc8qUZI@@_XDEn7#{vi zd(W<&ba-omo?d8G`)-^cz-ZosdZ|TfoFC{XYaZKYCu*&->XSw8YN?mI3!a2H9^p%w zxsMQ&zX=Ms5!;%dur`l-1k+!XbGlh#?KOOL#bkE!Sd+!?m}7LX-=Tm!f@c;?KgT03 zwtv`Iftfn&aiNd=-n|FY&K;j#{nhf9RYR}PkZ|u;XNUc-rS40u{!K1%w7P?B_9 zrHAi4Q$4?%rQZ`sRm3CHhboV((Gn*;n5V?|(;UjJh3M0B8&j-7fx>5ka;KBC&x23tu>wCMwrI0GZB-srBJt%h48d_41gKTNC@^T4NX1G>Tq(V_irToj!QWJuy;#V) z?6nuc=X;IHs=W4mdN@sRT65~`rO9)Qodwn5jx-NQWulJUUSDW_4kZjj_uEMBu_4J1 zlh0V$FvsGv$loQWgqMtouJFt}Q&iv)w8wWVEB9#F*OMF$lt@iuOQq7c?3(16ZLBYH zMlx2IZ}Wp&33tGMbaA19OJ#zFxl(W)&i@#PbJL@VqkP7*t`ZK*%4VaH>jC&-bI~U1 zijC~tYrf2zZ5-R3K|9*B=B)KSXk_1&J5qX!)%nAj5fe#)9Zy5UZY>%Zo5se8{jQY3 z$&0h-*+_;I{F|_`bKr|tad?wABVpmn9RYXYYD-bfhvcr|#r}e!+lBPpJMOn>P21lY z)~%|*ec~;EBqlGZ5}?hr`D!=*B=p+8G8oeeMFNyh4-u5*UEVOvvR6%j4+YUu6!5WE z!ZG&izwl!ZQ<+czruNFETzRAPG%SV#2B{jhG1uyY-Osz45Ey=e#My^d{TOAcROQ(U zKjXHW@CSwjNk|ShceB9; zij*8d<;Q4MK2S2Rl$_9`6++wv9@s~$@_PYK0SB1MR-z_^Sg0ox0fz=2>ya#R#exx_~6=evWm2 zPQc=3h-lv`^O|GK5Ve#rTg&lntv|#L1=CeFj7em&wh5cmx75zf`z_a7r`z;<90^=) zkV$&=ZlC!vQP>>81%vOOE>iRk)s=W(R|@~@*8KN3F5Catu=JN<*?+*hzq4zNajgfT z`&^k!<~DPgED2+JN;?>rRhwoh%oh|CX`WE{^r^-E3a=I6CbRg%QYzhp^6}vQ8sa;Q zGL#|eAdQq-a=ly)eS#n=k6V89l%g_uHr@bWAcImj`3t&&aBCfQ@4i-3OI7mCYJD1_De?$yl=_8)B4fu2O64Rno0 z0#jGFw^yM#cWY#Q-LJ4k1odAUOq(&)wd}}*7}H+$$5au=Evtc1+4TinYB+6mTtDpm+^@H0Wd9osl%N6K9YJpZYv6Mc;ww)ujX)czG> z`X}!9&y}6OTEYOuE!jCa{Q7g3Z@tjJyC`^agB3{*h+P*BXJN{01B2EZ#zhj(HjQP6IO4K(@?4-M>jP}x|@ zyJz(b<@<27=Bh(MU>>tAUBTlBtm8AuZa@Tk>>i%6Y@p0-A-2i}GaXrbX&$V2;V>U$ zkVCWOomOcdTeK~O1wh63+h^*modDSy6Uz#^{?pA9yevwoHe^9*__a-7q>y zlze1M^ga`GURVQ1_=qj;y+ZT1=J#m`sgWQPbZN4Gyw9T#zEp9tn3+9dU#Ipi^y@-l zxd)8GKE&#~t9h6s2e!1jSH)$If9AEm&yCf?i_xi(p>y?@KjS{bC3F93wamzULO@es?-x5H_YxuxL>!?u-^Lmt8GpPfpFu4U zpGsb2V7^7(JG{YgX!}8#uD6T+?9#QjkiUzj(NblkF2jz&_ov|&%z6j_(brKM{;DGX zy@C+^qk{Z(-2MT&{6~SHqUks%g8UJ`oMALP%0Du^s1|nzMC6}qBU)Y5%$%T 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

  • wu&JW_~t+ZfYc$Y-F2*e|oAbzW2?GIpcL|FMlQ%;)i zPleuM98g0h4-)4z9W#q~+0-DRk?H6Gumw$X^24TAF8C16t@wCD(sgw@hrXf3^R{s{ zOEZXKEa$NQD%n{Oy|YQY(RxRd*y?_ml7Esf%jzks$RN<=620CB^m7}<82{+dXyi`xgR4goA_@u2MiAT``t6J_ zSlE{41%Ib1PDmn=izodiKghL#IFQ)?Zl~*bnZjl2=>75X0`nc@OkK)Y-@Lg>U$~D5 zCW=LgrQAYo?y4S-LreZJm66Rs%#b@s%Ak_ckqJz?KwWDp2QSX;PG3M20E$zB-icaY zV~FwMv^mHP2hL7(NO1h5xa1hZh5;H?AdAaD^-6@l86+_LWB>sUJus|b!t1rW(cjb> ze54H8(3Hfy&13QGh1Vsw^2Qk%+$C!C#K1gAzjc{s7O7ydj^T%uxn?98$w9AUg$eeG z(+JlRcUXYc)z87Du(Tk3p4!t)G<93I;}M`v!?EO`1=wMc^F{*7g|08I%Zmb=Uv;K( zC6y?EDpxr-XA$Bh(*tIbzMev>6NT-J+qLMz6#c}Hgw&mYNlL33v!?z4;VkcH6w2Bt zaJ>-?3|94gPNebDv8l%{1n}9HG(cTH`Au+Dh57(Xw)yT=2b*(URdWVNtF2@Z$cAmc zdz#OZ30>Sc4XihD*YKwO=qTMJ^Uj#v)l5pIS zj#ZlP$`e<}FKEOu!iE%MkZwCB$FnEIe6{lEVz3f}*w`^7zsO%uC^jFO1T8i%$A9L5 z80E$sQ3-+6DS-kD-?$_Sr(pv;_qE%JHz?c0!`4Bx7VbltYk|ZaN6Z&S2_77JSb8Y9Sq?w zU5$>aG($I4Oh$(ryryz%Cfj$cN7|+vtKi_mPh!oq8b^0bFT^dQ6=4c0(?CzoDNqPnf%)rP7-1YhcNpp{3Qm6n0mz zPH#74wp|u&TYcy7m|Z+PHKR??v06o64AVQQL5;Z(PXkS#AA`NlS*<$8 zCG3q>_Rbb*m4kt82CXE??#N;YeG)tp%{6s}M_B@Cjj67LC3LH$KT+?oB)B)~CIA-7 zMeOM8JT1e{%6c^+(%1H-;&_K{3V@>GhDEmf9-M&Zs=saMb2|I!>Mh z28Cy51uxkpqrNfpn(yQ1hym#!)xn2N#dB22N z(L&S0#|OHBzc9rUlTWw;*2e>T!J#36r?+H%)0V=w3%<@Ty}Jv^;fwi3qS3W<%D3J2 zvbviU{dncH2RCdf3C3<|WCBO8Z?q5|qgWT1U&5ixXq2b7zu=dO7QDNW0Fj%FV8^>Ui|SD`lQKQ8rOl-yrexk3xVQ)yv=?{PDwJt+xIj8xyP z9DvZj{CxW-6mT@Tc=R{>@BJhDBp4~<&~??#nvY&fehaOJX9_~e#DgFW!j=oA6%85{ z%56(F8kUx2-bd|^CRzUd-d!IrK989Pv`3j=@)MU}I`8Kv#c%X$tkQ>RM&704Y-4ZC zDYh}sCyXv*Z$*qgf`@!@&uNUGE#q6U&ufgI0pnXS&uxsK1>;+B&s9bro#UV6w|paS zL@A%@sL!=>M)Krg^0nwRVGOm&^i8D#x+u?_qA#SVGFfpaRH*JziX$BYm`xs z#&+9km&w9JjjxlbVTW^7QN3ziwNT${T?J5OM|YXj-y{){h!LWN?4RP6Z)NEn{^P`#CAo**NH>Y zNbJLiZ4&y~NNO|@J&5hoh&xF_)=2Dwh&yRQxJc}ih%XZRd19MY5g)~Oy~WQH`hj9y zB@ksLccY0jWBTi2U1borlKSC@?bQ&cBzHl?-{cVKBz8q(UBwaUBzIB7-_#JRBz9TE z+i63x#CD5fVTiHfth8z&Moi+YoJSJLR)|MPB^y0Q3dI@$M+zkxzxKohY9r#Ugkvkz zYB`8CLyV{rtn!H0%tly6Y9kV?QjM_4Rt6;-A!4m_jnar$zW$hTgjKj!dLk&{oC#Pb z1&*xF^tNXLcK=t@{ZSw5#b^+`N6PllvmLAlA>O31>|r0_j6B0Z4C<=R^ zc2##xyNK`JvNW)dNcQP#CEJ$k8EW#@h5EXc9kXIDIkqRR` zqAPAdabKk1#?W^`U(i@fy(*$B7RdDeBJ%3gcM#tsis@nRuHY`&t2~Hw zU>HbdMJmf>8<53XE@U(PEz+kFz_Vp-U|aaN^lcj8PAOlQa~Tkg=N}+4J50!SHFV&e zLfg;4rGQu~Jm8*@fN90g(w+?b9e_o(9tclbAgoA+Ol@$!bi`_<-RA3s-KYju`d#?0 zLEBWou)yJZ2t9~?hTxGz?3vp_ATp+pRMUe?RNFg#dI5mH=XJPVA$r!ph2}yEZx4W# z4yaI$@io9koT9bB?74#1MyQaFcZVpqjrkPysM^$hZFpn!3IV+zd;tM?CHcwPg?;JU z61GYFRKayfT%~>MTnYH={JdWXdhn3hqguJ6I)c8Jx$}Am`9dvkAv6ofeYZpI8G+=y z&jWsiz$IcAgC{yBIVN&V-{$bUM0yL}P6F=Y-@ZjzN4cc3LwLpN>4JBq;Ew}DyOoF3 z>a|g_vuqc?qV2(h;3ars1H_ZgMv%f!maqft5r@{5pW)5V@tUzeCuh4q{{B-8x z;2?B}T_d)g5`2dF6rZagyob1yKRo>S;NO&gj(&<1bRoTIicx;1OMDVM(fLJ#5N!kd zb&Ke!3IjkA0q6sNDxeUFHwR}Yo5A`ao-4%P_>>q>E3^1r6ElXmz`rS?#Fa3oNEj=n z3eYEPRfL+Gk;>cjzL&CxZsmlYe5occiw!_OD=8_1x#G5pLM1+$DNCPhl$CCil;!*A z2%1Gu8s0qp!XUh}Lpe`9QM)8j>g*f(u|Lq1$M=4hCQk{Lsts2u4G`k049oz_AgYg7 zDGwmV6onc-374`bUMdaPujhpxyoo56@0%9y0(*y1s$7pL&2)d}gd#mlm$HZW=%Pft zsVg(@V1}5Sy1+b^?izU)QZC+LDa{DHR}!gBwJF#53Te2dQOe}RhS@3)hvc?lt zvJ3Z?P5B{4^n~=8NXfek;8&$|ZR%Y{`O%xDIG}5;UNjBh|HgWoBs%D~TBcEa%47t#^LXVa{ZbYgj3~1N_>Hdhvu78W;x!k~NZ3VsO02xcGg+ zX`5|RuvA26LQfh{Pr83Esv)=;j4L2xp1$Y!%5|2yHaX1`L+v05?8J#y#Qb}S$mwjU zQY4m0CRC)ovvpyUz67lZ0c(RlKSDaM0s_R(oqTtpZx}P8{CjcTB+d_y);rG22kkGT zL>B@>8P+}|@DryE--lwjv)@h;BIXsH8(0sZZ!o`$mAHN?h>2p^ES)W;ZXN0YvlPFc z0wDLg6Z_r5&gp5C*BblN(`aU_0LPM&ie>(s3L+J9&<4_2#({`VFg@ILIrZk*zn@=d zdmqcy(S042%f>P;I*7e@b`d4IU`?+x-a(9C#jpvRZvX*|X)Wl{>vPDrNKwjK+G`wC zv$$?I^XSmNyn1n}MSc1D|FAKlD>FDM(%KGZldb3Y9sf$fgjqs1BZI3~P8WMeR?TlC zoiQ<r%E0woeu60QjPTWqt zKCh47bfSBgqOy7rhaWC?AuUOfcZpUL=U57~##m4V>STB!;=37YY(zP_1QEY({oEPc z9PVlNlONYGC3EZhg^b~3pT!U)YEEmQ@g{2)o8G|D^v^<89uDMt(e@2@ZAJXqfV&&1 zDQWf3^gF4kh&e}*R;@5b;4BSh?1nqsw7_D{_`Ml+RLw4(UxI1{S|;Ni@Y;g0Z>@7u zV}LR*soDp)PYW#?SVmI6-ItMWK(FuEoP(N}Fc~LzZeZAZS*xb7Rub}xuq&Gw9$hhm zI5WR2fgUi@=ep)16Vhc$mGgmXU;1YZtSXsxBw9r`q{R*jvI6=ubz>Z9ar0VL;6QtI z>d!0PONV~Jp3sa|xcIX$D5=6KJGE@?M7|J?-Dh9e4mo-)-2hLS9rx(78nxO;5Pj;} zeA%1Dig>I~i*j{)qCqzVpm6bYnzePZTC}9w7Kg5&Vw%t|i*0B>ey(kNOA7IoS=Yly z4jHRtv1s;agykeoA5mCmz_F19KBAXXyI^gS=JpA`%ne^d!|3+P-$XPWSb~fDb(yPW z8Ar6Me(l^014FfdVPF!kR+_rLH_}qg`~d|PD|?%Y;;kjfgOTW1vQ2&!TkA)Ud+Gq@ zjwuG^w^GayItPX+JjmR2$X*<_B6v=y{{#bs$#f(yd=nN#kY#U{<7~#hdI-Yd)(=g9 zCjnWMFH{G5%wr$&}*tTuk zs;W0@?S0N^yS?YGcJDVBt&L|q@+Ve4Fb!TQ7Hz85qS62nGA%~UAIT4R>!#S5}t*fV;2vmHu2|3j;-DHtq`o# zXY09gKVn?NWM0l?oZXwIyCY3Pcju?u2IC7b&lPoHag&bZ?!;C0VIpu(He7&Z?{#0h zi+}zaw9K{-V zNiEcW$pE61O$szxP=Kq3{;4*J(VLrXaFQJ>J9GwEp&zu#+R%=zYS&X69iXSvT5z~~ zUD_?L?JNkVn=abSx{7fCSsM}D=$j}lENj)(!&OT~1zmP-HaE7R#-J?(%FAyRdNzS9M>NgWUcdf~ zYN|wbg1&kE!or$M;mV07%_0XA&oj8H)Pg#@9@^7@nJZ7EY$9rYjvl_2w;8jkdwACr zv@U^|ua&0A`%QOM#5ne=#{4z;YhoV4_fLCjuqP0X(abTGXlNSXs<>&Ii}coIdVaKa zyB-;h+$FmUV$dHsuYwNj1Yxo>mmK^JQHvQpA>&wy3MW&w$9Zi(dDF9=fAq!Dw(&S@ z8biCs*H?33)yzK+&fWHZE`>@Z_*rSLz7Y7;{4$#}>W^MF3P2g@C^_6+yiK%XIW8~1Gn)aN%{hW8gtnfTPvj;y4q%Y#{`WFAk(l` zz4yu~$b+t-K{QRc0X2f~DI%|rs;UnRTeln-gD}@gBZ;Q1&PM!vYdp>M5F>$B+vNc615>j zDnfxBG3vpPaJOKBRD8+acw zvzgqHyBuss4iRMpr!#Evl@roZLDriR&AjRa2=2|vdn@{s)m6ki-sm*P@mUE8s4E0K zs7acFaOml$vVIcrH0hRz36a( zeYo;?h*f)l@JhYWMfaZ%V%1Ye11<$FcM?79byAqB;cnYsGz)O{dXVUC7O2@n=d0czSRrBR!d zBJSC;Mbi563NzZdSji}mHy;f61J6cs=p-kvdyS9DFUS=ANm8$Hxzr2NI)#NTE#L>5 zrCS70?oX2fxu3t$kfZrH`5XO6=GGv+R}YV%4NQ3OriGQWp%hjV()}iXiU66{13G9y z(7C*Zo7-%vdURM{l}f&};Y8=pgB(u%;v`Da!<2@vQ^sK>8n4L{G`&W0pBIB8TZ@Lw z4?j_JH@5;DBTo6P_VNv2o+0JroAAviA9}L#^WT@PmYf7#X%tk z6xh%&&XYBM`hF%SWzUnpVOEZjn>SvQCB`yG#h2so2Puu=CoSI0&pq^}-_)>e&Q;lH z({4b;KklJE<(r+O_?NvEP|fO9mNseMR&C+AxnTFAuXmVraTfz@_EN!;5#qHRiA9=G zY1{T9)ya80+vf6<);rj3g4-`ETapz94>=#L{PebFd~X&mp7iOr2w|Lm{@7%t27~@a z>xB`Izrsy&8Q|vwGlDOQc4PWFt$PC)|Aiw-6#)FxJfGMz`{#up>?)?uf|Q<~>(J*l z5sGwe>5bh_Xh44J9a_Ej9NH7AvCor^P#>jLw;p>O?^c*&Qw;Q533hWg_=aO5$AfDs zT-cgVU?}5o*g3S9_6}v!8G9W2w;5VqIh4QQm4)TaOU*UN+(!@4F3^o;E2<$CDG1Nvw8%u6fk3_ z_H8NZs22HqQ(pf-Jq18C-c6pVz_mZShUpl02pOMNbu^&Zz(UiI3E}7V*FBqXXo3#T znT4Fico%4?n2CyVE`{%t_9Ff!k<6tSi17~>gFo<(Qf`_;>zS!R*_zaPLZjheNc<16 z(VB9lNtCCD)_jdN%V-m}i&_y7lCti@S@F69Z-(xVeFB&MnI4|uosq9+gQS?0yv$E; zf+n}+9a<-1EZbM$Ej{&a_cX@uyqyUIqjd$-8q?u@BXUrf<{Y=aC&F(lemxOF1WKc^ zVJ;joJ^Q5X6Z<%7gJ%=lk<)9kRk26!M}dIPP#9K-GYhR7%NPfKQBZ=`qjtL^1@lxU z8_lx8650}B%u++qGIJ*T-#v^{-46yU;Ak~$D@yxC#`X|M4$l$sp}yNPW(&IR&_$be zrt?wQTaR^~1UYWrhs7D)4Y9HO`kLG&0*q3Qbu+@VM(K;Kz>13S(a-=6x+-;}r!I3NF-WLTXPG$Wx z^A-=Lj-QX|gSGeM3OT`#wcFr}HEAOd^K#-(Ao&C=4MoYpHPR*1=PAI0?VmG2g`F+8 zo!|Ph+{zHs(gcIBUt$VX=Jl#yz)DfKaS?tx!9`*m%sGqs1lEKO1nc;eq~uG>nO!nE z*5S?iNo4M5e~wmwr{E=uRRF7t)w?wgdMoj@goEZtaA45Urbg|%cHB7UcQ%Q* zK!par$`hk#3Bt^R=7w0#LH=s0yNQIJWf?{H*0pHT6n-Yl$?l5Qq=7mOe1-f8p%zD| z1EkH{cA?fg=mQ2o!cP>(Ex%UtsVKHC;;~&TTByXxt2ZWgUga6?fG9A2PWwd8v`p5T z3RM00y~c0!)bs@E)SkT4!q&Sdu?=Uz`5OmFoKACDH)69|sTm7WCeMr`3EdN>)=!^& z*d*4tOS=+^tl8sd3=btW;0~*6RR3(YY4>z%nRYl*oQS3hs%#4-NJ@=faUlG z{GLaUMtWT%d3Sh~Cg1af`qTUT(X1nQ!~TiryjS}%_#}KM?PDFhWqe<3{Y)GL}w1=M(gx;FWe(!G^S%N;rZ8V%dHta zE7lJzX9(ARFYxP{VOsra%8l;_4;&8-yE$lDToM4*peXsER(LGnFJq>QX z-M+?W)R}Xx)U2_|a-4H)PU~!^9H}3tM7MQZI|>)a6y-}E_BZJVE9RfrDQNyUZ)j+v z*c^)k;I?8>7%;NSrLTle9Pc~cL!IvLjaryy&21tl@1Q-@&~w=hRRC}+S0X8zpK2Yk z&p}>8fx3(DP8qAnoY=csZ_A>18`mb9TYNb2)zLbmeioBn#p}qj4C1Xx)dxc)a@JdS2KXix%ai<-2Ex}ZL7=r zLqmYFh??hI0Bu7Uujk6)X~49WIo-+&^iH>~JbIP<174ixlM4S6+hzT?M+#5M9Xwfi zf49$ZEwy@sfd#(s<-2_~ym!Nne&;#Z58$*Q&);H3hm}$#DrpF(9a27wN+mzc7 zk7ybFn_szueyh)}o)`OfOMI#+7+o-9P%Q*1ID54w?KKb#43ydQHqw|?aw7zJ=H?8l zQ>X9TEtuQzebxe358kuvL9JpnOr1JCQQSvu&MLAMbVVMI{>i9Pl)t7M)#=z)URxKO z`6XaWd1xD)O>LYhIdj}Nh3X3@DjjGK9be7xE2O$@s#%9|-h)}5Y6?PeH-eoIrd6Ot zfr5Tyu%zH6Z`2mB6diVmnO1NJ>EJ5j|<&2`TEnZTv9mSPYGx#!}I zWqci^hio<-Rq2gtyYQRuQ7!xducgA$wg|i-CO;L1@Au4n+ut~qnnw0@QZ~a z;m?TNH-?B>(I`dJ$T5w|oyFh9z^G%tE<>=(AWqpfaBgy#4&P3IDFWt%4)am~;kHcRnY<2(ffO$48( zqg0fu08W+w@UhPmkph<;Gt+j6a*1+CdK9iNhyHzxm!D_>Y*3iInY3>pN{@LC8sqL^ z=7-X2v_Ln!CMW2?wNJGZHs5>QajIYLlk(UWTy=|kt{&s2hRVJIJ){-MI>^~4nJXDy zUkTT=B&$61(KA$51Ur9xxm2T^`p{P`8S(7OFLY2>O}p(4Bh1g_qde2WTP&ok?b`rr zyf5+Ea#rVhCO|QdS>&ab25NqpEo9_@<#z!WKVM-x7s2(g!JA_Aw`EQM*N{aa+iNXg zY+@ykHp*GfNiJl{gVn;}sKA?xU0{YlIlV&Gj=OmFi=TbNEeN-&X=nARjzasfgGnh} zO80X$P82gHKja44VAO>%}VfRe_%l1=yr2%eo<(zC0%9BDX|gx%alLVT9O6X=jzxk7Vr;ktqWI zT`pIaRgG||aF%;cV<$tXet~HcCb?cJ9UJ(lY7J7&RT%(;*S0UZSuicgAlkUDG}Q82 zsby54w1$4xL(nZSTUdoBybO|N{>~Q4sHUiR4F}1%O8mp^xi|W;SGl;I@G_G38ZgJb zlNe|jhb3{y$v_qp%5Rldx{U76M&?(H8%6C1V1LVaM2Iu-!Le#ewzgf{1QimkJg&Zu zSO4My)5`8h>QwdB1?>>zz@u|iTQiDSMSsO8{LwU&yY`6pe1Y)!u@t3Y$#+Q?9`(&D zWHq|y)*hvaap09EWS5yeQ&1ltbM2Za7rd&LaIvkd%#tk~U6|B_5>Ub{X|onSI^ z$kED$&K@o@yV24Q0aw4GjqRZU8+U);Af1NZ%d`)U_D4HJM0-9WSb$B%G#{TaJ&gDk zY(&c$njPV;4x~K9xpV@x87wk-J-9fdj5=TQ)k5)4O#SpeCeG{Aj)CNB^HAmx_bnKom|ESF&SV~-y%~3M-1D0~ ztXBP)iLJ}ZUD}Be_m=3Vr(<1?YQm}^@DDilK;LZ;Mv^NxTqM<1MEbl!B3Hk%Wh|cL zlAR2jHhA=T=`jY<93fsjej*zwDb2w_XHI5FI>a*D`1qJUj<@BFr+{~L|5aawzpN?i zG7Cq1;o%wsZ6;v0lGsf%GlDgKJk<00PpFDnE3C@fEGe`WI14YfZ2cF!3P^6kYeMq4 z-+cLri=$-$b~Sa-x|MmnsCYI5^RHZLf_TCw4@CrSozUKHD6cg5kkAG`#$NR1)hddC z%tan31eH$X0x)kh_zsNkr$l^72nO*?i?ik80^|OMDe$c7)blk8ZfDq$&AD^Fb3XMW zIP(ep;LuT&D}ja)PI!}MeEV}gM8jx;1l0Z1F(JYzAY%G%`kh< zGj57>P!xkRN|=8p{w3`Q%B9**CI$n`SNJCxEA@X!JN^^c_CF$4w*P_!SpUUt^S%Vo zmWx8c$r%af?&|70hh!5`jE)u8nS&_2+AEE9XFt$wmfd>fP&#>`uaW(P#(b{nsP^@; zlOIts0!RBVH+LVD02RJj`FZ(S`Nf(t1KEJWom@~E=z&eKC3O(ua8?4S3EdYFMP_ph zP*a7ghqCEmixF21bq9Z{waW43g`!b6lt^WXYL79fkoOv>QLKr6(Xx5rGt+cFT6WBz z*zIBftr&#P>+jWdp$3td*{tFxzpa-wlM)AXtsOC za66jNwW=6Dqw*8?Yk`xt((8F5hzWMg;`TBbe@Foa=!f8uTlSQC$7}pvr0=p~my>#8 z%?63p0J2gkgoq(RYt>in4X(0K9mIRn508OqY?Eu&e-aEmQ%sH|FJ`4#8}6=dObS^FILvUdW&X4BAL0ZD3+syYsI}l z)m>3WSZ2eJ&0S7hxQb9S|jX0K7O1$C<|J z#*3&w)UuMZGG2oUNCF&- z>`oofP(mam$fVXOuX0`!K4M#VD-I}RD4GP#SH;L}Uo!qBYGsSX&iwo#1oZnyQS1MP zhW;>HJ6ru54W0M_sX5?;A>A z^OTO3&2WEza>y~bPSxv3e@w`7xW&I1fmPdC{q`HY8En6khWJ*KEVb4@O=P($P# z{w&jn?>-LH?4z);Xo==VMBcyQrS2SZM-<%tEKJ7g9c)0wn&o38z>K)&R5n<4O;?-- z1elfMGerEP4S!R4Ng8ELZ8E1@3Oe9vyZbKmS{%Vy0u@vCfRjZ1B2ddwiDgt4i8uV z+%6k8Y6Cax+yQPZ^((0a*kW5&JlE7k_bGe7*#=R%EVz?eCQ&*LlDLI^Hb3-&^0W@N z6LhNu-0PWpq}G#=HcPF^iTwzt7A5iL2ocA7CLNQREPeXa*L;^D?B#>-N!D5d!WHA; zLWy7B?e$j+j;kGFIe#eFjj0ao?z=Qd*Pm}3VzSmi27D_buNM=cE*C4krVnGIUuQd+ zGgi8G8hDsRSpCVJ>wQvX8*I_6ia-jT+=Tw~0$vx$)w&c5IY%*x!>D9AX`SenQsDwo z$#BVch<%S-&Da<86hM1FeqCwXQM7f1>DGjBRQbJZj1ncu8l&YZU8T;#fVaYN3R$n@ zS*TQm!vbSx(i--XyNLgTjChP`!&}O{1A>K7^LQCf(!&s&ai&Ih)?hX)o;8dSQ%yM3 zL5_29nr5Irf~mBx+ZZBeTF;q-)x@r3{>6#FVb*so3KJ)lnUkKv4aRdlCP0<%?otVf zPr3Fj0`b%nZBus^!qoa_>PPfSCK_mgGM>cyI+g-D`nWE6WYP(xZMaIW0fp1&zRDy7c<;cmU z1!^JoNfpg~vzxDfTfOPUO~cARKKO-y6bk;Q*F?tL(Mi_8?%!UMzaz500{FGen$`Rq zP?Za&EAR6nMF*s5PfPePU%^=VfSO+@Vkp>Ud2g%xljSZV_CE5}Z`RW62b&TO4LCg? zGwyUa()GH!J|H;VyixQ~lz88{v+S)L`HtKM6!xj?+Ygs_-1=@f0>M;!_0{15fZP!u zQEodf5CZF=`f#qKDT5tU9I^6!Z8#zr$g$zy`YaOstwnA=KVg5FO}%a1Tx3^jo@|hM z&|X|85suq7k>zx*G)bp009l)hb!<93lu)v2^tvmsoc!wvji$SQ8( zx~#EN+F;tTR`)s>v^$LFaI%Aew12dFoMH&!No0Weer&QDQbBnvTBFR0IA-Sx_)LCf z{Mzw@1lwd3ME<1pQ09b`7)X40ka_jSQV|VbW1Hgt`MP5?`1?V$Ke3m7b4J=adqSd~ zE!((sn9sK)DE0&*4cit7NIAs0M5MlReNlw5d`&G5hGAyr2Iu2r>k~E=4s>}M)krE! z&IT(!;RPAUmPftQFdq9{Ic>L0^xjzKpR#_3piU^zGYSP?{2!rQttN6fy<0u!YO4mr(}3gpxPl)M?BCPLDm_S~-VmF`U?5<1Ne*D^ z?)PqH@TMM7cqg4vt_*vM4G~Wj``6yp252VW9!z;tT5)o0rk_Jh2r;)F1Ot>V;|A}di!(d7~CJz-Hb!*Zoj7_^7$Vg>1&EmdVVHNcG z@e3&@tQbxTZWNB@^~$bsiT12|Oak5ND!a>hj8u|m^86o`W(3#t>y;3=4@>V&baCtOxVAPzrdv>0| z>bY1N2(RB1&m!>-Ei zbREd1e|7*jB%)snv8fjc&bj+D68NF{g@J0^f$F4gs=SVZy>xd;Cf-21MGiK248s5w zN^i;OuX$D-O#67aTfu$TWPmghH`~D=5*}-NbQHDtI#U1Tr@r-J4-}vbJ7958L|g7HXtw+wC2oGEfcE^l zCjZ3Vs27R?R~*irZu*j?vWV$9rN{k`#3t7?&FvBD55h|qgpS&d(QsHDdvZKWq}~gvJ4U(o}v2@m~QYo z=Jj8H`yqlVNW3zjf6fg}qG zsRRj#fm(?fAdKQvj6=bkYk<@DA%3aaffQpKADQD5V{Z?v%8_;j&xVH_ zQEuB#9XA!o4zn z=JYb)UTAk>S`*t0>%ieEvTdDI7Wg5Xr%&cA%C#b3cT01*u2afz6{sPL`PdO=tE|1m znWSSnR0icQdBB5Cfc3(`-Bd|Yr@xv6d@YlLY;aZT{?Bd4DNv5LAS_W(RI?t0Mh+W%SVYD-?}mWE7MAyAbk= zk>3TDM&y|iT%O8ha(~zbs@FANx}pBs*~dW^)>z?m&kw>FG{Llyu9uHo7*V%t(lK%Q zI=DJuqm&^nDqlMB28H#dzH(~fH8}m7hdqa_%)~N=RW$tfGWbPte*|`75<9Q*!N|g} zBY2ZnpP>r- z*~_5=B#vj9fM7FMB?&Yd;bsChpHaVpA6GYc-!;ovT0>Fv#y=o23hN)Nq^D(KkK^jd zqj_z$DpoAHl2E(6J6gk`mLCzkL&v|X@m8u5$>-o zfuYEzK6!5#ql>UMzlM#Xr!%p6zkN4(htWKQxC!9UEedfZdiy&lnm4NnRP^yN?ERxT z=)3Fr3xB~aX~{>B{Y&YcbiP$fE5j?q>ii=cdTV_= z`-51?>nqBqPsL=#Y{iO!-D&WgWDvcB)F;r#4|+q`VttSK`nev~Ou|y$P6JLjjGMX+ zha_bNWuP4j6CTH~C674DJhE3{nkqt0@+43C25IxV|4ttnRLqP$dbpw&-2gj2u6i;_ z@gBTa!Jf4CLhO(;zMg^P&=QlgS1>M*%f2&KIggb3jJ@v^Mg)#yQ1E+TI)`i21pZci z#vW!_-1q}rbE}5EwU+XSFK8UN12a{Y!hKl*o%EySq*diM635Z?n|sc7+YQcy!$ii# zA|&IQ;Q@jmJ%9I_K<04T7G^et$#RDN&(>D(%4pRFstGe#RU!LzJ zuPtz^0MFLt2mn?O>4XzFvG1&Z;)^@^8L>NNR2y-g<2dW}7k&xQN8>_g>Yr6v!e63j zj<+-mB&&5x9NpDL`YnW6b0>8b5p{aF$U9!<$}_3g;ML+QkM*u4t{g{pRwgxMDDK#I zoTnQvJxpY1d6HOLmABNYwPdR1T(tP9q1Guwj&$(cbfNew+PACLXH!kn;#bHAVJ_XY z9?X}1yGeK}2bFb$#ej(_XO@B7N-`-q@bf@YQd6xgIGa4A zUJg~zq9S-%jFb#aD%`a}>gF|8c z)_7E}i@GuWMDZe>_0^7;lsL&u~c7zSU5yM{HF?p6qN+ zy7bP?U!4_+9NJ^GM<%Tfd2O>)i<_N=4YBNxm3W~#?MDY5HTkm+XiJq9BZ)i9LQ~=Q zVnAm|OXc2Jo;1ClK3Y$5RlT0KKb&w+)dP=`-*L!!J2+-)BHqkgWe%A+$H&{_sc-HP z$JCUF+ytXq}fCRpVLmE|4bUgbumMOVtFV;ig-a}%UfgMNz9NGp0;pY$Oj&3Kx* zzv|gB$G*0=~Il{Or! za;4R+XT(NFT^F2a_Ux17~8#|e>FmL13R zHZo3aT@QD#n!dC5MpKQ=VmS&Q9~i)WuL^ki%Bg=*cGqF0gLqka%5P_R{_^S3XcH>k zTBMEe4>ZZ_2Rf=i%_B*6w%)3GlV`2gbR~j+%v;7&rH$E|ws%$r`$1>&)g05B=r>{; zfn!e1SF4+huGrDv>%OZR(!Vn2RmNZEKV^F)D0le#X+9Df)o;96rJGviWs>~tcp0l^ zLerD`-N4I@o=kKDC&%BT5d>b6hO0Zh<-t*i_cylG^FeTLuHWztym^2c^b(iJl&uaArFZ2D&25O~0&{%{I(yz>4 zLZyeSs2pzc_RyDJuP3QmAHL%mo%ub;AgZtCI;r|FL$4Vn{y}Z}jn#tM%Jy^N)wM%w}r+ zwo3)8-9pXEE;L)i$zqDqh9DQSYtz6^@wrU)F<;rMcB(qksDJUM_5&yp)+? zh2vG?vEx7vr(?>R_}oO_^S59LC4BRkV;(I_m0KKkVMsV18aKdgNQoRoWJtI*nlL}x zkps~z7F=t6r@%D6g<|z%czF6)lK%0X2=nnr4p6<5R2uZ$?Uo*jkG+kVb#%&#`Vizv z>7XQGT7d!CH`_&U?q2v9IX~Z|MwvPcjxT&yRt9cBu68>{^`1E6ak2lEjB&14BdZm^ zIylIdiEcBEQ21!^$tB&g&SS%Skm*O|5*rrgs|N_}!sOW_Es^D8)Cr|QFl`-IL_+6+ zvp=uJ*E@e)Dr#dqYyX5*7W{Jy)d%tGcx*)+lx}=R&pk|++%wwA%B8K$I|MIZ-}8Xx z6K-B3>%5%pbta`w@A==MK`he>0kt2ngn^Gb^8fZQh}t+?8#@>{{na%6ckH^8?cX@h z|4QkLRQgvJRRr18Fjnbb0;8-^!>P_d1BVGo_Z?B6h)lqy8Jxk!G_3>WUN07=gzVOA zH<~5OfViY0H2s{L_2@R!c5>unYjMU2##&MoBb}S_jXKEuBVkFIt$n(tV5bmdj7W>4 z%HDn{0_B=uZU6u0H+;Z__68s0XzuUQ0u_sDJ+Y(ML*-Vq-9pl)3rhWHMkGG*U+Ib+ ztn`(K;6=CDT3ZuGu^p6Eo4n4yAnd*JSqu@yh?=7H#R3Rg7A z!A&YF+Xz3pa%FVpjgKn{nvVV2W8?jg_8A)6iw|Ks2{_Ky*Hjrzh6njguL>qzAbyLmm+a?nrUsQV1I4yeyYyIGXxO{7cLXSruKQsUF&<-r`b)DolSozbVW z<6b}zK-79|Hw)N0ITT`YIXK?Lf9C7i>0k?!{*B_dZ3RNU&!lndC3KcCv}JCKL^_3> zU;8c_${H-lt73zRj(EU^D&p&;@%Zn4(Lc z=@W~QhS7z>5kx5Z`fJ465nN)5uR2p)9~q@W3q&^QG1*D(tgF%FwA^*a(ZDQoTlfL+ z9^?Cr4+i*kiqq%AzZWKmE24ToJ`Q!{|3h>1?JxN8zqW3XYA?dLOQ^pC1G5_FqUa5W zdP`}oR!{*k=4p$uI-DunUy{qA(hZyCuul9IbuUKCG5zl4)(W0}>=Zo-T>;`?xY?Lb z44Td7-a6OrJOB7MvC{g%qB-C9wQsny-W@(4njg*l;d^HCMSAD;b^PQ-<%#GD+5Fq$vcBN5QCIM=t}JexnGf zb^RGqE7ru2nY)}Qf-imz^VZj;x&GyXYaq}m{kah~ZUiGfnRdCSJ+mEmz#5LblzKTb z!ylKahzK<2+(N);5?9%$WEULfX-66Vk-^C(Fu5NuW0LA6HDG!Ji>cR*tGqtu@U+W7 zaiw5SP_4im++3P6l&-uU++5y5Sgmvewwagbt5%}kO{;>Ds@?rlnJ=WdOx-Awad(1- zptoE_yVp|13-LLuEvtOe&aG!uZS+4gESYO+Jn^%dv`(Qp zuIAF0R-qm>t*NwDT;-0tC9)$jKQ6k&yYKeNdC$SCqbuHM6w{e8E-Zk~TWg}{MZMWB zB27!3Bi&Qolo6$4joZf%t4oWIia5xGS(|1T?h*!alI*3~3}c+>%$29t31(x*KQcj! zJTSBtYuyOlP7os(CJ_$_FX(IQAysd&kOz>rLx~4hP^a>U1T|_o)F^Y5?3pC zs`RI;jq7J++~uW_2iXqv>M3ur&3U-tXw4-iDD>)PUCgE!rw-WzIFv_Vuh^6}l-h)k zq+C(=k~XT1hf%Kr3!W-PJXth^>$o}hhLUN^r&Y7Z4@yi=B%Xp$bylf3>!F?B9S)o} zG8!iBS|7AsDaI;5H0*hXRp!LVB$izBOTpMA8%mNVB(njLmP%rYWi5JHL>z3Hjz%Uc zu*vKhO_giX&ns=ZONyF^=5kyo47BkJRt#EVb0grkVqI)HKviq8n_UT&j0uYvBw;ck|vcHa~5Qm`Gk$~W7 z%*97_#3yR}o-aV6HtH^7`S}=F6Bp*;mgPw~7dT{KHgp}2=5j1LkA*fWtQEt^33u9h z9{3H$Qc>c`)hhFVW&kVt`HcwINDJu{Td<0p~|lD8y3qOOm_3ZCZ z*s88e{I$JJj_;p<0P#Wt^U_;MkN1)S`GW#&?$FMe^y74_oVVC6f|HSGqRnXD&^sXy z%bY|XLsVJ#<5WjIjvW>gsn1z!+vJdL?Ve#=T&rqdkGM0;=(m%|t#B{80%ff`;ki@; z98o$op4(ZurVl%OcW?svV7HEqLt-`9f`35r{eY4UUlwTd?aN*LoQ19!<|g)ua|ZZn ziayVhfh^4JaF}lu9su(ab_@gAj%*HFPuTdl8YWZ=8#x)-+Rx8L$LArLE3Wbb3D z9Y!?gdbQ%05e?^(&*+Uit2I8Krf_9L#HYCeKsD;$_I@qbL$L2;|GL_{GlLBm@oiTT z@AZ>jW`bwOv(tH;pf?;dQ>WWr!BPMZN4o>7vR}gHtR^H*%Yg-Fvz4!vmo5+ENkcuQ zK6<#f))QAL8H#Lhi8qNGLQZ3&8~x!Y7J>8In?Vg28b@y?J4GrtxqxYZ202aW^eG^G zS%_KjH}b>8EYRoE+Zr5~eE(fImjm;TmdGBJ>C5rwwl~cwN{i%p#XiYgFJ$_ogW(Tz zI(cb!_ICap374o6Y;`7E;B{Ma`yqQB+Kvs>Cs~c9IVbeok-|`}pAr^~ku1=mOOjvY zW_7R<(|Aff> zV`t-FY+&@Svi9E*n12Z<{!aPH(SU&un)X*QZ*E&`pTYQ+3)l1oMiY@3GcE1aqrSC! z*Mzgab?wt1KfxZo?WIN35*FawMC$L7s3VFK2x8%LZ!XuT^}E}Qr5`V+)m@)t{GN6| zpmq>D;u#JH8{f42{I&cC6l}}vS+&mKv(o|7K+#$x z!Q(IRfGtGx4QGjz3@cn8#RNjwP0;@?gfNY~RR?w*DnsT- zdvd_x=R=)LGXM9YWIorqO}Ml|I=e0guep5&d+?=u$ZCvMbQs67*eWZmiOZNYm)9?J zLoU|t>90n)53@h_RUrBbkxZ&Nt(Y>VR;q@f7wWD#mBX;}GA-C0Mkxbk$c@liXURFB ztevf2gcvMYL>4=`oBP8mzXrNjQmn)EaeLT50tRu5x!EUd$Bim99IG-`=uB;9xiZl^ zuV&=UT=>h2LU$Z^eZ981Ag5f+t?GUzl~8XX^ih(MSqCQv0ZpiWr}e5{ccfh(=;kX2 ziYHEFpY=Ak0{^Vb|I*&uefR#_%PgJ|?%h212@`>xYe-NUhvqkaXJHQ}Wx@!V#jmR^em zp)nOwFQuWydG&st;#f$z(*CtrWWk}xQKZs@$8#onZ8&DW_?|uFurG@vDn|3XvRn^^ z!XW3K7ZeZe_Osy?LCt%hAGKkYn1SN=*LyIag4jOT-jv-7cdZx(uXA9J;x5V+!S*bC z;9Zx&`3@>b%84`W6|0I|4zZ-Yz-;LFGYfEc=o3S`|@dtw4Dy4?4)@VyxF zjK&XPW+hvq^XnAxrhDEt=X{^jXa8e-uUWJ%(Ow!j(idc?C=`-otSrj7w?MYWXfs0x z!hDE_%3}Nqug&4%4WGZmDPW@fGyf7$WPV%<|L5*j@IN*r|7&+^ZES7$u_h$@|E;k9 zn+*7`P?R~ij}3OIXn{P0;9}c`YSJk>H8V4m7j;Qb3`(|1vJh5t{lwpELo@-j$ldh6 z7TCp=e#dc6gqN8ej#D4GDC;i|Z)2`tvUHYiIk{yY3cWNaEn(r0yFeNzgU^AAhP5zY z2NXpo#WOfB)h7!+*J7zpzzQWZj#Fbd^cBAuFgU>!D%#Ivxv(0{&*`$x6p_iE>Lk(+ z6ilE8Rj90)lxSZ3@b1p^9dn(OcGYvUFMsq2F(4UtQuV+r8ZoANLNj*}8NyVU zvT9=3A+5zi@vA4^ecG{+PE6N&_^kJOqlDJVqpj^pa0z!hMZQ~@H3UA%wDMTE{DJPp zy@X0&cV!n0;P*&HcvVdi$x?s9yZ~Ie1Vk$*Fe3LNW2m=qnXtn8tK~ADh%F-V&!{9I z4EVhD%XgNs&2F49j#GTVDkoOZVyD3Wd>8|Fi2&p*kG#F^hmX45d~i8 zZcFq*F1B-95Kx4?icQZrPYMJ!^hy>^D)~L|Ah9c)nu{)j$D30v~6$+^$7Gmzn%%<|pl?;IXG11GcpT2nj6N=x^B#R!^Gwzjfr zTzsmh$(^SeY^#J(v{S*XY|HMijVg$blM(r)0w>TN)bc1E%Gvns2P6xp<0Yr79GC6Y z6eHNEIvW#Ef3Z8m&mG(y9G;qtS>K=7IuFYPhudg2rPmE-ZkXjBBl$Z(EW8okd&D}r z9HjH$22eF@ou7mvl!vt@BwU0!-E!hDE!Z@auyGOaDF0om z4=8;-mEp+qwLB?roIW5h1(ZpWKjsyY`jcL|^&|eN8_{3=wo>Y=7~wwAFJhvbfe`u*YVHPr<{0aFA5 zHEF|`yrB28PWuO6E;LuJ+!vy!Ti^TuGjzifP#%5U6=@JLlU4wSv6c_bYnpT{<6ows z>F%rG@$K{y+1uaZb3Wl;uN~_O!3hzlUi5nmA;tt$mah zfIsvQUwY$W?D!$(lS+uEi`GBWH70(x&d0D2q7faCUGAPW;7&=s8XP}&b?Mr=`jBruS^fTfw6q0wzf+FWPHm_mA||35 z$k2-ti+nsR* zLs!{gJlsO0<@o5^V;&Nzt_0VGD8EO`@k4KLN%A3*$BeH6<7YsLFru7f$`Bb=wIehA z4*$4>JQFQ(=n`2$OiYcxD|v}}T+ARZnkshwCdajb zg=t~NDS^Qo*X~Ly$`kEz<0W&evWaFoHf;3i^y(QH>5I2tYH>mv6TfTL78t14-e@y0 zbDOL+>r2(YgY$`AF{`jO7IxIxSp>Dl# zll%IhA-h!J`1!P?f>l#$=MDB+20&W|#!-oJKffB7Tim$dZyr#5F#-8p%&wD3Z4_U| zJtdpJ-4=AQZ@+ro&zv^RYl<W#_U6fb5cl?be!i3$i?{EjR8mHlXp8grzn-e4S%>}2V=u~=o}*zm-W zqS(047Sd7nqtfp4gXokhm5@!8nmrPSDnRTlK`npYKKDpPS(#~#w)Cs#L}E|cZ4~BE zhs;eq47D}6h`@+)1s@H8N-#`EvB}a89KQ~=*`t#Hg*m2SS!Kc_o>YU8k357ySyE*M zvjvr_EV+VyP*6)A-b@~3Jf#<E^lo2$>sPr0Vk6#c{+v6q~STRIf^``Tcdr(!yG zIxM5GRfoXPEeo+(V<^!r7kto9q3Jplf@~`-Us_yI%Uw3Ab-V7r7@hRm4|51Z*;sf%r3(%zVl1sR7L>*&m&oqvrNwiM3 zk^o+KmIODUzZ-R_l1kNR)|nAm9kDd#zZ-{DR2v~t17_eEtbMh1QQOq4eKAf72#t8K zr{6YFeu5p-Ywy8Vcs(XI@N*vMKkLL|TgE2^Gh(((C&gf@A_{FBJA+!*kD07m=gz7! z3TYij1%=tTriIwJ#FdKZsDFrDs!7hG0niS zf>*8y8a1_hhH(W(a6^jW=kJlHq5dqX2U+=NEeAuD)cy)YSgOPRsaB=`R}%SG?ePD- zS_?eMWn^vO6hEPuJX07f zglB{?WrP$2DA#&RHpoBJy{5hl)oI=U{Sf+MTK$f0sPQIgjYX>(74Fb0d|}RHp@Ls`@_uOyf9ZCK3>ot76D+ z@gju3?g((&ayiFdi6h#3C$3VWH@nh*8C)-C+Rs!h0IjF4W}2d>AHZJI$Q=R<{j_2L zxGbAP(|SN%-xTq&UDKy3{Is|2$o{7L+^o65{(e zS}zliy+Jvgy+O%r5lNKQzWM`d2&jlLFgXwx2`epVD~lAGgr>-*aX~xJxtEfrrrFf5 z3Tq%>BZRcYZqipTw)yPc@}2?~W<||)#R)XowQsKndKO#!cczC^(LaAryv1!g(fix7 z?hl=HKNX{Yzujg3`YNX^o2gY$UYw~_Qtm2CyRrx^TfLa9r(DU>tfg$l;*wj^Vs2Sk zGQG0MCTmk0gf52b-Bti6>u!UqWnT>6+%^dTtNvpCV8J&zl!RGxV}O_2&oIs~tj{EZ z3CDt3FxWmYuxfTjVXY5Mf z(F#pt>Wbbu0MzQclMYm3eBBc!7GDf!7^w6?IXwU%h9<+`-EiTfZJ+S5?3MG;7(2qu zKa>r?oGQi{0qD!aV)q`>gEV-tu4QoTdS6_0!mdu+@bs2$5Vj`lxd%1@@Jz2lBYM~3 zq5Nl0*zb9J2CoVuKW{$c#%%+?-37qUxf=}MJnC)bj*5OW7u6X0lkowCATMWCAn;$R za5Fhg{p=c3_xajLcWIAJ_oPi;-RT-imvtMaBQr|dc_}>0nHB%>%<}U@=BgC4wq`g~ zXf3mM+|EmfrGfI%_Iig_aF)wb=*$U~KekIVn%;sc!nYMwh^$lNkL4R7bl;-Nho{sU z&uduShux@-(-C$TR)6_ZO9b{~OX8lYO4+BR8n!)h=$>D}7fs?9GDHP=F9WWVa{KYi zuZU+q_S3ZdHl&Q_X=I0)PG~4pAeJu`H4rqoy;B?}k0UW7gmqs=YM2J5ON4I1lNyl1 zBzWQmPwEhvJARSlPRPZZYkn2!9FoWy)b&C_ybH#}WQ;>HiWs$@OlCx5o~Yq=ja+`4 z{~eI*Fey{KI#}m2Smocsk7&Ba72!H6lTJy!(M6Fnf-8CuP9 zWF_jc96na0${e4aY%wS;$~3l?TW^S%bT|JsHdUH8=cbS5YsoCm4`RHBQ-OLFk{W~v zu!C9kP!+m>Eb4`Lv-8 z)fCu%sp3n?fKfPjiAkM)S5u@_=Z8@U&Wpj#EwuGFp#Y5t5tbGzg>Bu|^G(MHbp?|w zDW~9?EsG)Z%=2bsu|epgUHauzIHLdo9*U@PrO>FMVjnCfq}9{SjD|f&JWtStLb`8< zU0Qro%525;Y@BwD1Bu<;N{6U$lZ_DsB-V_Z@RDbN8O>Cf1>MXU38e-fGM2@ezQg)M zN|_G@|0h;s1|9wV^Oo}g0Vr<2dtZ!nf~ssDF{{!gIDXN zcId{;NwG9RkKdG^bZyrbwN@)J^@+f&Fz##!8)@%;IIoiFe3@ozi>zHHPT|gCDx5sV zio`s*|LPeOnR$XlkF4Z@+7%+Zy6=xEKMW|fHsiUhXCZ_B%CY~F`yE7-t0#zg!SoTS zNryp4gL@@)@70gGko$UYCTGhol9~Q%%%axsm2!p7RVj^WG_aX?T*ecJ1dj$?gUh-U zhDVcKBh%AQUZvYEQ(!2EZTK@&SZd~pIu_JE5w~{+D)#z!C10Ezs47b?HvRgF2e@CM zdl0#kptJ6&bF(Wu3bga5!$lk=i|V3{=l8#FEY61gkaXm?CUM1?!eHDDA^-R!xD$Fo zQoVe)=ig16!gQ?dbh-XZhQ+evb<-%nZ>wiiBj3CCIO<+pn0H@gFd8c(UHFiQl3NmL zH|%>YyUfJS*V|zpLB!${VHPi9LVQR3U#nFp%|DcBrFbPYN%aa{X_&0VaMkLR#b|~r z*>t-H!ceg`#9XOG^MA)n%HZ$3FUG2jlO-J0S;R}?^EuZ+=7Wq0w`VFkiDj}XyDFpVe>O(IP3NEk zHX*XR4NkUY*w4RT=JuHTxfPWvY_Vg&W%v+|f8%?Y&iwEsNQ%xVTGDEmML|oU6AB?U zseOi)j#_S~jz)ov!Z9$h0C~Y`tzkPsSu?}BhMq7r2M?J}bTT4OnX)DJ;aD$yXMl(D zgsQckoFy!oDs&dIa7;nY(k=QLrA01rilIjHvaOGuJ)q`EVW*I9($UM0z%~gRWaQ&K zKW$9S%9V66-^quvI{VW`E=g1vG#?hcHPwjU7xzAF__t?t?v{Oim!FUMn|=|yNU_+h zclR=vE1e{3EN_4JcCpznS(jTQTQcKB#NV{y<0G_3*K)1M=r_?fchNVUMpIY#%>CjG zRMq{44Vc$&Eyds*-$#zOZYl4MvXBm9t3^_9uU^1ER zYZE?7VD%AVp3t`h6ZIBa**vK{JugMhF78#HXbo)C)a1E7ZyYnB=TlKvLrZnJcx)l9 z7|LGrElMtOc@GxTktV5jp`EAqw!1;<&|jvso!KI{sWPz>I2>(HiC)U-ckkPh+tM<1 zx`Sww)Qo58o|;y&9cT1n1w@J85T>oe!M}al(idM(gnB9#p&1@2+GdmLgWc8@vPYMY zdqsBJk5f09Qu825qIGZ8cT^Y;kS}tTTwM0<7)L{|85z;tVp3Uv&jaJ;BCiu-T8oa0 z-2WP(qaC&xjgQZxP)N|`W{ z3n5u#so_xv6kzmRS;_p3Nu6qh?}P$xct+ZF~_ z9eispc3N4ZV~)9_cB!%6bW>Mj@w6T0NJhV{*%W7D*ufyb9(?UJ<|)t08a0PzgoPY~ zL^sk(Lm@-_Wo3^|y*N#2n98Qg0eq?ux3unxg|4~)T${ra`emfPXjX`LhyR*yrQdWV z@I#IU&NX_kfZ3d6C`UHe;k@9gGZUqc!!>SiS4jG9EN%GJU*n*}1MY<>_SXVOolpQKGHs?;S>1&68Vu)l38+EQnA1Rq!4t4v84K|)b zLHRNs8fs20avwyALZZuNeh8!8!PpMOLu{955aK?AJ}*#ZHvoRS5Mhtw0BJt0k=tx~ zayoW8?i-K8RPu_PKzyqN=D2+AE2IL{XtkTI!ElV89T@a>DMV&rxvW z_Xmn4%0O!3Qgc)n1{cJ_Xe>K02CLGXFgQq!pzgcq40+(P+slo}(l8E27F*)uGf$#d zqjwF+jxgi@mQ&`t!KHkaN*@O((b=t;$|^1M<@$}sweZUD01zxGX??DVO(rDBMcCeh~l22*rs&VU1G}`Q-QCDo%V?xcxWPd(-hmKLKp$YcdAjL(6%dZJ(6%gx$ zW*{QNHO_eh%D?}bg)Jg5n0&AXPq~&*x%FP(r`>X=lPO(bU7vTD>U zOK}nEKD>Te$8y+AN}G>5y~k$p%;&f2$Ib0&0&p5RPprx$SP#ffhoco~0=@7u3m0FsK6vDt));JY`uP`{egaWGoa|!q4b`*uTl79|4_k(b~8RQBSjuxfAO+fY9#J#y4H?Nm|#j3XY|O zqS~*oU4`pxDNAg9B^9~oZOop|nAr~no?k-cDcxWL=dYK2%+Vi>4R7>51QHTkapnMz zfE^pB0kX5nvLnn{1`m-XxQNHLrNTMmik}vQao_Y-T8kni<~K|5=F<|` zGfPOMR$lN-C7r&h?KP3(yHYruvPz|6TQ?t<2|0FaaEW@JK_X8yYcLNq)XVIDDbGp4 ztHC8!mR{Ul9Yh=+qrm1LZqg|$#dsmIxbasA{d646A_e-3iRd1Lx%e9fx1e)r*ckrM zyt)jES2`Dt1DiZk;KtuWcms~W6U1ko6$EN&v23G$rVkgbkiZqhz3-D;SB6AJl0gQ)PuuPsh=S)T^yk_; z5H)*MjP=Tt2r%_tP1^MCJX*Iio4Mlu_8kOcPz48_G+GKcGL5^^tg?9XSRNONUyg}s< zI+%QzITYeBL34ITXQG_rwA2rvRZeERphIortwaR~wL(~sukbjU(yY+kLh1Hm=hB;} zfL#@Y9>pH84j|B5n*Iu@#2gHZY80x+VkuJ2&)syQxLN8t+n;EqS)oyFs1)s?31@0r zl8wrD`H;X!Hj!^))OK6O$n;^ix+b-q|3KrvmKO*m;}_~qc~Gs`j>vgfWxL1#gW7v$ z-dJW8_B$zG(qqpfT|xZ52qo-9R{pc1s#{dJQemnHqE&ZcF0GA=xGSuNQ(BJxL^(-b zYYvchGy&K&#IfvazVn|@F- z_V1$O!pxbwXIuL@2QlE$P;Y6UnAcS1G7GN|m^Z+IsqoOd{)ytk>(jlyf~qH-_c66+ z2npA>VrIK*3TC$thsJK+;OWh$0hyxcM#YR;Jy0H_ifsSn=^`#zuYP?Xp=fxg*nY1;nw$61$w9vBnz>MOp&524-G!$YY6}=OQ)n0Sr zdnWpWP?a*{=NGlk-tv9&lT;Yq{u@9p=99EW0(3dDKk}L||K)c795VuL!2T2J6K3T4 z1rUb64+())5D-BIigkY~h%t+aV7v+T{M2nXA(fh(+7xwD&ytYtehtLR?lon`OGK26 zO5Bd;%1`^|Mg#6gs!&pk(o&eU8#RtuxE272F z{Cn=Ty>|hMd1k=|MLM4l1;+U{gmH8aq_{4zOtpF`l>pm^aEQcebdqn!0<>c#1>1P} zs8xe`S~yxkh(RTXiemQf5H<^=SkH!M`cmFr(A6#+q7@I&Z=7U}GJFA3f8(O7)~>)U zfHTRyKh7loAM*A8IAwka&!Vde7vWJ`p&s(RWX()iZ=c#|57Y|*iwMzpQQXBwMJ#H3 z!ZVwG#(Un{`lk13k?}}>yss$;6T$?d5spf;XzrUZbUTjdGmjL-K4a({4;kZVd{?vC6-}`QTa!XctR97C4uzl(}=|?#hoj@R(G1?FJuEFpCMDO$@tjS*V@= z>{1YI4dpfw7jqb}D!q7A;u7k(5`baa2|uZDCRgStYF<#`(qSJ;H#RH0B1afS1f2|A zn+VXK8$B+g#W4m1)Yf_wl+u_yqgsD_o*pM|a7&FsY{0zjrCObLYi9C@DoM+ob^CyK z$@Yd1YuX7O#)o*6kKX5_DL0f~AICAw?syG9D1Jl04bCac{?8 z*fzok`u>8D6dIBU{O1bhI?|SYQZ6$$@Cv4r+0o^<@BELSWc0QA+5*F%@hII(w%Y?8 zv2;fz+lY25g3pj{k!+27?Ky+wprCY;?3sf|eyalVii8I8g1M;x$5a;z3XBm3sLB)- z?$L;-^83e>w#+1~^w8}yvw9S2%Turm_A+i$8goGK07rzY)p)=0;lVBHef) zB?tC}M1gfQHj^lHXgpm$_0=7-;h-3)a4<9ukwD1#vJzKgo=?;ehdPF8dHb;!cEPX@ zk}ice4-fzd|DkGTx#nv88_@mjWEaT^MY?Kkt#LO+`Z z*!Q(6Vv~e$7|Dk`cXyZ`LFW=>#9}fr6J>thL}Gr$`8Ln8@UXEoy0jljrrz|H!itC2 zj=R^e`)9CfJ3T*Wjh(zRtp}2GHHT5YIYW!h^mISHygGtAVOSTS^o!T?w4~Pm zwVpP@=ZY~@`=Yp2fdYf!K4qM(q1eXQH_t9sV^!3VR50iqQqU42EnMYy>G#6NER_M! zeiFty@iK!l+TXD})PqUIKSwVEtIzXZ(!N>>&536!Jj{_XdGgJPsLi;sCM@5LktzL# z^K6lu!zI1!ee?UvEwbab3yXj*f$~Syl7H8Of3xHNH>K%cA|*D8vhvuB2tIHKNhZP) z6yJ)!99KX~o=}+~Ab~j)5QyybPIY4JkN%h&j)MFSO`@O(k80+tva}ix3afD&ebnvo zOXKSK>WcqUCL{+M2cH9tqY9@Q2OoO8_?$4@9$qNpp~07)ILc&iZ{Ru=$<&9&DAAy- z#j{_b=AwBp1u_NJEhY5~Qw_HloJ$U~=bpQX0ewesF|l4-&j*H4W<^eK?`QRr5(beN zyJ2;-V;0~!=H0-3Jy$YxK9|XnzdPFaR#_x7{^o;R|2oz>bC9VLV>p(3T*v!tPzWXz zanAOPF6cu`=);@&i*PYHRGe`^uo^qPovL0h39Fp$xIOi0o5-LoJ8Ql2G7Dzp^krG_ zyIGR00PA-ot`vwPmS-1oM+4((m{yxk>{%O734&?Z0AZsT(V z=qG<9!UYx7h#9obiQPfMXN?nO*jr9w^0OH7x;K0)y{kU_9bUtGHf8-#!(65l3b_YE zG95yL*amew9{&};eEUba51a#>!++HF`m0{yDa0mmagSt#6$bh7-p|E9>tBYbd4S>eXlTk0IS z-MzOYxyTJx+vKI6C;9e3oVTsp3+tyJAz!% zko}2SctmD=yov~n_s`B6<1yDq$5!!KJyJ22o1h(L{&9{KHZjB1eN)>bkKEOa$&J_d zXBYvP8O~h$D*GyjDyLwa84fm{XoGpsk>oh&%a<}jNQO91e{l`Cy2z7H^7VrXo7UM3 zG&U_WgYDtTF~4NB+F~b)jxUmDhY{xcv&F7>K7F_&pN_5+(mGpcSV!5cP= zHh?+ElZ#1NsB$LgR^1l;nQB^>45?K;eUQ>hqQzYtnlp7{8Q%t?v~MB$B#UxH7F#(ajB}B)L7-K#e`0`6TH7yfLv*V}&!tSK$u zk=6ojV)%~%uiwXKeds8p5)v6{cOnBh zk*|Zwh<8M}5$<$`f`c%KV2JdE@%B+bFer4*1SByfaUeF+v+a0%JLUjE7$MvMwC!GQ zAdMJD5yyt=^{0wJOt`psHHIigusB9Kg2V-dRV_x+jmePFy|XIelRYS=FE2!?r4#QKQL>N&%CKD@mRCDX$(HGVUjZXo*I_^( z1-l1Muk>!*s5%;lsmSV)FSz*bzqvvk)86q;-ug}1nyH&&8@3_GSjI#8Yj$I7tU zXO?X$k033Cl`Xi#{tZ%)9*uZ#he$+HLF|BYPUJlTBOc2jM17sf5H)A|#_2o@ac|~F z9=o+C^NV^ngRADUb#AM)88qo1l{a*HGtfGqA0Yg(UK0CPeEc7hsp~(AT0lUO z^ZzB6{>MjBlyv161yFb!sgr4=dT&X?x5bCv7-UkP^g*y-C4@l21JaMzn*c5nZfi1T zqr0_g}9_Gsht5b@3Dy|~N{w{pFXUSA&$u>INaJll>4uNjVZ0>V1NO2X8@ zx-n{c2VC$0L3~mew~As@;_%>=7=6~L${5wVbi);GaT!!t7-q+?N@m3d6f4-Zn8T-5 zF%Lct1nxx6ldaW?TwiIP_Cs-InGMIn`#v`p$wz zb@arff5-HUxk5fxkyGsFJ^8&K#q=K9&{v^t+9Apf4*9IoCZrwrGl<{c1G_l`KDt+b zhfCdk0ToZ%-y)+i_|5)T#qKgkAr1$$!0Seob6Ox>L?mbu1>$5{2ibDJ4X1Gj@te$|f~rp+2auDhmqoyc7;vw}$^XWVo7S z9x!(Q-t&|16HC2?!BBri2rJAP8XJv@4N1K|F(`K&k)z@$GRawtRiI<{LTTLM&N$~@ zU^9^`i%HMQBjMqFY3y-Tr`$*yF2%a9cxiZN28S3;Ur(-yff2TF(s!#hp&NdyKPY%K-;+2v9t zS|YEb6p_W!G}tJuu9N2D<_1^DMK7_FQ|}n7E9Mv`$jo8^6%j)}U9`oBGq-RDu%<9| znKA|bDVi+PB49;<3Gz4a`=1Fk)c<}G0~cE(^Z!#VS=t)N*ckvv2>(?mr6^tfrD)A- z*V_>b(pd0h1Y$W?14af zGvX2+d%$_Dr zm*s01-Sdxz8N+DY4XGpsJ_7nTfANjY3s@*X@f3m5J#B@z6HB`Fc#A%RqxjhXAe56n zdPr{hLG(8J1rgEo<9=NKT^COHfhG)akUE(tKmis?xpgLLhl%Eg{d26~xEx^gn>-FY z0OPmmEH|ScO=tfa!8hG^t$nwOlH$v21Xh+iLv11`50st>UHXG4NwiV}^)~od-^^=* zO|nj@7dTy!Wvjd7vXq4bhN|qZ4K#QY?)*`-4fhjZtmX+^aUCIFm059FtIM@)G0Jx+ zwnO=N{s-vcC|S>CePxkE!QM{rqpf78^#? zNg!e%s5y8P*~i_cygI&Nm+%+Xxdq;;m-+&xGQdYlV;skS05Fz~n?A9c!7CH~HSCfn(X_p$%&$=d@|L2!C7Gr|QKn~Y7?I^#!+ zfW9^ZgB9WDiO`^D4w@etLpXvZ3f4Lc*r(7`UbfO9I=?Euh~>LW)@eT_aX;2%)K(0A zAoXnp_d`{$Id&Yv(6w_BNi6m7M-nWR^pMFfX1akn5uSYVCIp=clj`>#O20wA zDF0RgH?0fzm`Y{ITJU@wHNruss|&n$t0+OAcV6VW=d7u~dO>7BgePd6|45g!o;5464SSz8<=scO@ zdqXU%{dv5*c*iqGi_pOB+pKxk{@RlnSG(zq&Q9nNKxyHM6Y!kwg$PSa-nN@6E>V64 zCw^xmUvdeMT8RT$w`gZ9jMwoeL`UGo6NT$j8OwY3fq7dB!rp53<9HryU||k02S7+l z?DeC!Cd?k1YU!>CXF7(5H?-%L8l6G)vSf^;*|a=CAQ^ ze}=vxn#OcC^=p|CvNFE4?-T@>ykg%bsxUR}_TgF2G|&!2R&IMnm{JuHaqeWP&Du!j zH7mVu@Sl`WzdDTEY+*e~4wx(zLvx(0lNtflD=012O4zCL3^v9vK{}kp?ke|UyRB=U z==LT9l=A)6Hk)EJ`M)u4fc21m9=E6@?<=HZQ&;=uVl7CK^L1?$ zD%m12QZrb`Z1-8pDke*iZ){w`wI1Ex+pWs(Tb|AcY!(G~(z3R)`mcE2 zuxG_2@TcIE(DM0pU08-)5w^g;-?LM&rS#$zkU3O?H#eiXoJu_KRx8Q-?IHBt^KrF3 z;c;=?q3m8*c(-tb4=AEJLHlfQI-#4N0xO@J7He@c~qLX*#5s}5ir zpC;bAS;M2Yg(e~rTytI!NRNO55+NA`j*?>6x-Q-6(z$+a3-yZfhMG?CB(eBcgxV-3 zsLjlhF*}^*bUQ3+{Qlwn@_CyLEh$}U*8r(ea+0(@4|o#c1sab6C`}y^SV~!Ttezkc z#{xR9lR!p16%P&8O_kVIK@`)&qJ*)?5O6jhZBfTiFvpyPb*Zm0pc`$OJ`4-X+*Y!K zILtb^M<)F7BvLck+J4|zYfjtF190M_57$r4KU>_q^Va-lV_C2NtLf$v2D8)5qX=n@A9=o?Xqp6EW)9oJtH{=%7gJsAIKSsVdTI z0;;m$=tWmPHam6vouwrMhon~t(+>J^7-oO&#nYlE>D%h2L+j75<| zo_(M@q5zBA6%h<4MsxCl_zqgAuX#80=K%&!GQLZ3YOk5<8e8?Es#4>#4r=)G1?Th0 zXw5rF@_?eTw>Z3j3pU5q^@W&R#7DeMR|;EL5VL-!F_WLU9u2p&ja>}EZekPJBj~i+<+z%h9@I-_ zMIeru?@Q9DQb==SyU)oDf07@iQwn|;fX}zE`G~UR7i#?``j}W;D~QXhEdD4xTTJtL zttan{x&zaf@CI{j#HbAmy<{#DU?OvyrT_YO!c`{T=1l^ccLU=}{yy8|X@Y-zMZnn`VqI-tS{+z=B}9aUL@N*_%LW<$&&>44`z_W|hT7af-z z1({Tf(W-GlO@o{7E6xa`SQ1s&+gT8Br00lF)u17 z#Dc4!}53feE z{eAqq?X2*K_}1Y&EWA1h3nG_>a~+EQXM=E7XQ7SZC4{Ut#9cf!47EL9&)zuqg0@^T z`bN-%^_;uIdR8U*v5n{#I}EWKQ7QhMBb^%(`eY(qG_H)4R5Pi?^P^%FWCIEbdO7|{ z<}71TgQVm%F2C>E$8L^VSeN14bmCUY)aAuK#cS5)klI$M~cSREj(NXjJhVmT}c>Fvlp8fU_aHqr`va->kfSihh zdWJ;m=W?l@2S2Ags>-O6MYU;>TbeBG0jEy!2d*{Z%m7*{xPkHKSfWq}bZf+wMY(cf z_PWN*ve}U)ay;5k!a?t`US4ZCWNU3=SOYHeXk%A=uIKj8sF(WfuCNiy1nrCx-wU6A zp;W_h3wpi4cq5TR@^Ry_LGmUF$h3tnWo}iT^y$A)zxGkm;eQ;w(juM4(dAPks0c(4t#%FJeFs zRUy12=n@na6q&R>`|9YbK%5G$8Y4+@+(xd;-4-~|R_Be8f>?+$MwDoR2+>i71@pDA z*K}>X$DbPO)A{y$aKbHf8%FM%b;N028*7&%OxN7M2pkP3paOq*BHkm(TZe<5Of05) z+LHBD{<@I63w)GD3{%31`idn6=8Ah@^?($&Xm8ANxuBwZCFro}b;eqW;B4geh!KTYj!YEVB zGTE=BE4aIW!| z`A=odwfeR-Z-}5n3c{qWvYaWk=V6PmZ^S(m>LBr6KfhBrD1n6>GBI;sF>?>RJU_gD z-UdLht+D;GK2I~qPY!hi`?B5#hXX;oi0fP@UAOTbKkNKb*zS(-mEwL9J4@qa^;s($ zVePVnO(tYFWPV%+=~jJC1?hB!OLJ5M0~L7Qh(Jh?;JuqmNo$J?MsN7BA3np{*rqbN zkOXc&Mk*pZ6UowVF^4}bfG}C+o>{=0m(FMJS<$%5FA6Fw<83bj5_xP(;~X?zn3&bS zQtTAmEXoK%yGrfZWsvg#I!GNQw|%>i6K!RRu8>xRY4&viT*~_S7ya5^H`>wZw(*d+ zTgdA+>`+>C{^9S@wZBK2x@_A%L_il_0Dk}T{Ok{^-~WY%R<)5|H$~xvLkL^^oT#9s zw&r5B3>Jo@AwB|b1S-VdaXmlZQ17~lGvc3MBpw^oLBHTT;w#EdD~L5uRgF*efG(@G zbivc2DU78+%CR!@;KTjM*W>Yi8^`}y#*jXA26&=x4;xdPsVD6w2p~M%5{--5$u#+O zv~4Zws}33imA*_b>*qa?TM)9{@a;{Gj%zubO%P9e(%3ThNFr)|rfCJt&sRf;11}DP z`Ou@3PoG4=?w%xiM?g$bX$+asaMB-pWl*>+z5r@TH4mP z8!4EaV;`WF>hR0eHE-|fB4jLD{}_3;Tw-#OP43i5$Mh-Ib<6Q#rf}A1d@r!T zu6&Rhv%iYGlsdNh_R@|Fk#_1_N!RS`98h=sWBpKW^C802yLM%mXs4gjDW0IV3iT2- zjR|QNTW)g+RuSu}Lan+1R|L2g4NsA&El_1fMw&i0khYBorR=aW&qZ9MC9KzNrTHeQ zdgr<-=GrV)skXI$6S+=xv4XpB*xGt8!TySPLu~!srK%U2p9IYkZ0Z+GxR$}G#!zW; z`!t_A>q=dBUfCoGZyHqxV+$T`pDRq~Ilp8cLX z(v>`V{Lrg*044h1cu_QA1XBryNkC~-GLlK$Z|?cHXM!CV!$tLX;RT2%2A1ab72;$k zmW#9Z6Lgs}?V<0naKu#Kz@NTZ?<#@5RebCof&X@tS1k`9nR$zIlK;k_6)0l*KEpbw z2el361Q)*rQ$!g?k@TbYf#H$HMnp72SfmAm(k1+O`SbJ#cyR=bETEJ`2-yx-zbAZYhMBY}W`DchWxI)O;1>5{E3Ii7vL9$w!n zSRkWfW-_ABzBzgV#rfF?g9@BaPyVhxTsA++I)yx$C%^`14%wC6ck~Z&PM8N ztO1mPEsXGvp-4d!4f>VSO+YYQ7j-@|C{hOlMrd$Rv}Ux^4HUXtTY5r$a3@nLBjFx% z^teV}SG4NBNtIyI4Z;Ewo`KA;kvG*Iwaoae2P!E37=;7GKyow_ADTVqCD#p6)S2E{ zNijNY>Xa#%Gj&kA4O4lltzCumkd0}Zk=azG?NAf@*oL2Ri>x?ni}SjvnraR!E!)x$ zg~=EgBgfdAU=Xd8K9|pxTWLC&c+(=`IzE4T>|BQ*DNBjWa_VSDp_{)RR8~QV$N$>@({X3#BpnpweE%9TL9$%})k}s_`!fY*h(y+; zjibz?FV=G6(WR#>#XUP01Oe}Flt6Oo&m~q#Rksqp_tgr(2Q*F_0Y74P%Ccd`6|@cvv7p3U02rg zJm1ke6H@hZ^QAc=+VqN%fzm>w_ks{w2nRp4{Y1a%%nORtMOZ+#iFb+{!^h)EprQFm z^NHVckCuOPknUx6CIO{KkS#fF%a#8=d0wiAr=<4jtxXgY6#R3wu06J62sUTzFaqTZBd!}U6TIpq&aOTndSf7}SAGjA&cO@sgjj@;F#oBNs zPMQxkXCeDg@t!UGQ}KplFSNG1|H7vGA{vxJzaWf`f2?ZEf2(T$p9ly4E_F#?;2jb@ z689!ZFgB>$%QqoDEL&%{$cV49ghWh_{tGDmq~(&XGH4Bb{`Z%FEG;gXKtL1fDW zXpok_Bty&Z)6-M0#`lLme`LP7O*ZQf_G$q=L2s~L_x1_^fdIuoPolYI@ucBP;vq1kmh zb$`*`MTlL>TBR)@*W6Fg?9{~IlcM1)-~@c4Qpw=k8OT5Ymp@cK{`^9@4<3-t+Jb!?SAKCrHrLzw$2y z`KRa_C09749B#~iDY4}<$vwp64Is+ZrY1)CY3>fix_Xfo2kMpbP|4Adiq|-D%gM3C zMFtg_)N~!Q1hiq1&chxBEn6LBWbA^0PJEJnKR8MUSHdeBHsY+I-Y%pY#{^^6KjB%m zOS2ylxVh;a>EmEkCR!n%TgYBzi>gO@5kzMrP8B8?>E}j@6Dt&Cx6MJK6t~yG&IZ;$ z(O+**eXP?zn+(AhxDq=www-ZBKr^0c-J{i}j^!3>VHa4*!58vL>7KjyErjR@`_5m_ zWL~23%g0g#Fs;%W)VIC}l|G;FvT?$bj*2?$)9cCE95Fp(`X~I6+p(lB=APB@B9BsN zv)H;hBpZpxj5{*kDi6g~WVugP$~m-COwQs+xTkmg_uhsyP`88?WE<~a(>iS|RJ!HY zZ1wPusNCOYtN#h#{0DK1`l81CMX^_C)@r9xr^rPH2~zr#XL*)TH#msqR*tN|bTc+d zV}M;h$pldI|DX_)MUYBlMet6%`DvBk%+;E~?Q*r!W@2)+R%65ajcLvnzO{9KuR6km zrkuu{=1ho0wd$e5-eG(Tyye zZPC&nNf0S@f)wQdZ+32@4va=s1+qPiBXaw!PPKI+k2-;fA{Eu4ezK)}#q109F1sJc z9}xC2S<~>HU?Qm`A(C;DLoeZLGs)js!J!QORsxYVvEi!-MObTB*@h-vYFkE9U(~*| zxGKECKCXOMkZV}U-v4rk{CI(*`fCt``p4<}%l_$`q?mw^w1SY%zZ4ivihb7mEO1@t zlt;}FJMDmbc}JN>!4Y7?G2${qFO9D(svq&DvX1`u=YK>o!xp&j6>%-qzcL28SOv;5 zlOo0A?!<4XCN^)T0LKEk8JpCbUhnQtP~9JR-D=5<6o7@r=z_F4vx_OFI4H1vo!^G0 z9O+q`8Vgg5%TI}PB)7&4AH+P9_kGe#ZC#k210KM9M z#wsbcR%`wV(qq8R8)lo=VEqlx=hA+UphJj#%s_LpoS+L+^#v}p%>jtJv%v>LTMu0xQPLM1 zt83r#5r>uoAbgC`;on(E&9=~s*{IkS_~k+t7m(=uXZ@`Q1CwY;#w7Q{@SY=SRvZ9Y_CN9K@{xbQOajaX>VNa8hc{+|3444!7770m)ItCe$ZV3()XKEy*O00F;RJ^C8XTu3^<(P5=KpO)2!&i0~O1JH%)=N zm<7!p@<)9TSj2( z2LGxxZlcY%5JWV-Ez$01MSLA*sG=ZirjkGHGz4psb}zmw^{>K4Rq3$kD*5x8BU5g_H`{U!hD z`EO!WVaTuq^W8)S2jZH`1=_~tV=9i#9v}bW4-5Mo3#WXY=AnNSKmHxE|9?xywY9e~ zFmm{BOmcuU;LC*}0~iHUp`TdOy}%b!^jDKBC_XX~QDI^;x{kMyM4aZ2Eat8U{PXOV4Y*}j95e$2hdBh@1#1* z^&z4%yW(J!dbmcH;Ara&4^SkdmuFPTKucs~N!+Lbj{}czj9IM|%ip_1C^{sD2X%vQ zg`_alcU1WZe8nF3Z?=X5`alK!XrdjCg(Y8vBB6)gCq&PAwT)7NSpUI^%<-@;vqjHX0e-DOQ$!hEs&?oT z+}KppC=Z6;fT~R1xtGE#IBLF?wsG#b?NLmLh%sI;qt|SdNrt0g$kdFp+?FneLkQ)B zS?FcYfHkPfXAp`4t605}Na>dGcU6w=)#Z+*Pl!iE*m*vV#9to3QuQ#CQMwku6|pV= zjqROAAKlyH!VmNpNR-ci-Q2u*z9&_F754gn1nU1Dko>=Nvj0Pe@}E}rKO77R4Ss>x zANi_7`eCU*5XyPfhS4h@`yldBvTp1Um)lbi5%4QRnwU|4?n&6i_bnziPL{A6@9ZLOSGK*laeoj2mB zAdLBn_O)I51tElNjn*0^ge(b!QjK1!JKYO;1VN!Z6vlm~0D$t!af5&j+SKR@J${YhiMg<0K0EV(y~fEgcTJNK)Y^1~gf2>Izw zZTqrI{3i0TnQ{1D@^_`m6dZYmt@S(fQdfO$%JZ9>-Vd7Y7Kvsv^f25i`;hITn*krB z{^Tjc7swHuMW{?}xQJ+5y{5oH3)*5mKj9*XqU=%3Ydq1n6#9`x-baOlHf7CmL-VkcOM!x}; z3?y;*dHrvYgP^5j-}Av`ljXv`%@|TW5FZ_CY#t@Y(A&`gw-ap>wm$re`@czA{=?DEQnFIP6hiU_jhC#qq%M-xqV#gGLd+e^ zRTisFn$KOnmd`U)Z?aCt*IK=>X6-pFA3PRIWVd#xwAZ&uuaij>oVJLd?+xR;-7qw$Nmy2bz3tSkVvPZDf25ee7|5B?4b}~fEHqzzN?>waC~h0J&u}ey+MQQ1Zp${}fVT2jYr<;R7_w4e`V*peO%`^b#>f>P{T%%03a5UA(x)z> zPWR)^d+tfAJ^O4|n217s0r`w!pt;-Prl2v(T8hg-6^2_w_H{r`YuL7WqVo(BHb^H0 zUSc^p)E{d%<;05bZ5E<3eRNv+%+DsZuK|;vw8EoV2zuybaS#JwoSH~HXC!u?omKhy z2bOuhOs}M=jG%zqLPF`(%LuJl=UJK_NoTdCA-RB!w7r@Wxox6&A;K|U8$1~4J@`Q2 zSB+w%v5;iKp+GuzLEBsHh?LV0OxKz&Ch_#R=$PL-_k>}6BSeT&`yV^h?sT_SCo=a0 z==B6D6XO#R$Rjib)78qy^35a@$6{w=AADPA-s|}U_pg~0 z=k=#@=U0Dn|Bv|xe>WxEp`%Hx15|}T|vKmUBzA4V8}LN7YN(#XY{@o=&iY>dFvS6&~@EU z>;;acRNdBby0L4T0jFS0=qq&9VXlFEKjumFXa8NE0TdQA78uVlvK*rX{xFaer*))aLk8J|ahQVkztZZ--H{9cfCtv(QW~)97io?|e13n5 zdBh~I+qlOVizO0HB}U+mU4yb`$a2t1`;h4accWo(%eU`7htN?c&~0h&l{lnHK81A+qg#{*4X{X{Qjn{nJS zlfeTM^`IXn@40qo&wex2J&8$dJ>(yi3gHtHb48pvUsKIX*U{GlhnOqV(KeUKG{#zk zP*yj{-m>K%2TIEUbUKC{jYn#(KT99pe?g98L6eJ&-q)Miba;y)v0R#5FgX?Kdp)P7S6e~rqWPLJW^6*f)r1>R;%EnZJE zz9Au~)GPdEzK6TR;tiZsMZ7xzH+SBH1EQ3>Hbk~rip|~id{hR^oJ26ZuA8%I47Fjr zHuD=34I07(>>9ES$}zwu>-4knb9sb99Vb$3b*l^U!YFB0=AZjAZ4WBE4x2Vhm)rE> zT@*u^dW(`AEa_w^Hw~GV1_C4y_GLvG`8^xoxmG%Pj1cY4=FKs zH}m-z^LAK%tGMEAEV8cstjvVtcD8g+M1OI( zb`U3)9Vlr<9Z6}GXfj5bj@2z?POEQbL>Hr`3K8vZu5W{f0^3M5n^BL{HX%e@rahAH zijzupWNsjL>}bVWY`rtItWP^*7npG`TV)DJR54Wai;SDvptGl*7vF=?J~3jxQF>H} z|6+Ky>8d_HkUn;rn!RxP{(ia~VKG5P_Z^Y**oZ zB;A;AP^M{BMoz0sR)B6fLw=a*G@1k=3RY}zxK=%!P`461L{Y3wtZW_4q4G{tl{_&? zHVfr(TSel*(4IcV;q4cXmwZpLWY` zQte{>oK-$k*KYXymhqUrvjg)yt`8aI0)%CU9z@eExst|9)7}~(i-#w^MppF(j~RQO z+G$RLQ;9cEv05CAqfWSMc|^&Dil+MfLk7km1*WwDCUlJ>xT}jfGVoL-pe1^4Mv)dz zx~@kImuQ%YpL*EfiyUkj*j25h@`yRO|8)JL$qcQnTlk&415T|{uQd8*g2hKBVw+Mk z!n0SfL^Rcs<~`jaSULJJ=)BcoNe&}7HMa?@F6Xa5W$Vt`)xcLvjrNb~M~uH6gD>wy z8QZ^7a2@`06qF?t2$hgN(z!TdrV)QU(w#)+1_P6DoK3Dq76A3b!2tW|0pltn(##6X zN4@9v`t(mn2_-Q{)QMv)Qs81%-MX|o6i~V(A3KBbi|(hE^N>De(mrk7d6AO7{LZ=Z z{&o$iOz5gGSQ%O#$_{@O;fl8h1V2HbEa*wLmq51H+CQaa4cdmjcQl07PKPD%EyVzw z09OF~eS>ReN`hYSh98<9+V;*8OOK0G51Q8Du02p}Batph1L1O)8DgQ8GB45&;_B82 zCDz1$`l#ZqB`bp*JO+;VBL zUA_ErH9b$kbcaci@ytnie^-9ZxY!(%wa4B$!)~CIG3gw&yXrLEOXQbr zzpBX({liVK8s`YZveIa6Kswz$jMYM37n$c2xA3p>{8P(ivr2l0xD;YMw{F(z57@&$ zG>hELx@^W#Qf8hueoPV7)@94dE>kDvR<4@hp^J)Mw+bRv3a!mU=78jd+=&s3s^6v3 z!|?Yc^Xr8w4doq^OifNKGtSTwgw;&KXmWtf92N}VF-KeBA6ZttPR%M#7iYt*yC>(% z2ij3>OD$gYYsL5P?AsAP3bAE+I*N_25JRR84YQb2rC$^oZS#wK1sNGk>S3&6oaq3U zG;WzPEE7_VP7qC*E!w)p#mh}IT!+FOjvR7k<<;v8f~k}@u@j}f-mumj?N!XGW_~qC z*j{lc0)n;@=yobqoR#f-Kt)CN6@8|%!)B#Ue1d_V$uT4tIchw0nleD+4+~NRF)U4AH#%aFBLqDMMxMB*F-@s!xK47#Y!P2E+|Y&j8{ak zmzTcguAk`PcgHjF;k$)P&U^=L1G1bT&z2(F)NTe$ZKZTK589Boum9dB5Ypzf|69K!{KfP?;wrIF_jvau_J3JC@7)Fpc+o86-q>lw<- zSNh$nO;`zw>0&NJw%W^Ek+`b6xEc6Df-diEHL4v(3)V=;m{A>ZYMJXzihO_ng z)&R-9(j%hV_K7wqafN8;bC6{pIcd<+wqfj;@sw}e<3i8`Kz^H*jbpm(%=zU*ZOxR| z0**%FQzXm9UruKqD5!T2e(I0K<*zsz6dg;&#W$nE(;<2NYIm&jSEaI|ZmiM&8vKd> zf5f-{cMjiw+1vbArrE!%SCOKb1EL6=*S^J#J4|zATO@w(@SG7!2HelI0eS>K(fYpO z!Ht3k-QabBCa@J=Kb`MLqe#{Mo)SvliWJ=S&;^jo#W0%vk$&5@ z{k=x2pw+tby;p4JxuN@@N4Dd=N|`LzVA2ftsoq#&zItS76g+z|ZHKltL+N&|Xh&$y z*8Il>zuYN-uZW+`RKqI6uSAr$BDCPIMPG5UoJbTRoJKUP_M3dmGN28nt}PGaY6~>W zk@eX&lF`;Tc9$)i;`&S($>CtJ^wo&rx$jdPC|WG*cjZF4HC`k=kq0{Q3qn9nW%bB5 z@@(e962z>9S-_jGN&yh{Gzv`GcEEMC!~m5UrnX}bSp6BtAPsmvQ|W_`Kjixwg5%76 zLg>Q8+EsTgQftxohK|u3yi2Q&Q_mCrdbMNkrraLE*g~ga_LcT(vTX|V>bN9YtkH{Q zO3Be;Gk~rlmf-cmq1+^6T5Z}RDNwJi7$6PV)6;Tm?JABLv|uYI?}pmI z@tn6W?u`N4ru`9Hvc%;#d9h?o_J>Q|rjE-R3GIQSX{u zbZH;j2Pj3A{?FSiLo~Cij?paDvJ#;*rma7wxAB9ff~1R`MR7T@`$@|*9BBlg1MIKo zBd;UcD=BcR}qKT|xe)SAG&OQ@~KEZ1IfKX$tZ_$612W(!r@ zE!nxtlU5O?^~|;eM;9qwyd_R|4(M|H`5AUkA*|g35&Pw=aQyFNm$}f%O69K>`|}@f z3I3k!@^6d8|0|-_Gc*)5vNf_cG_w9;82{&k zaO%zxLi~4p1MeOy+J-`lj7w*|4;&v~xS>JV;lJcb54(rG4$M@_dPhW;a zFHpYN7p8xm)>6~e`D@96X3*+gH&BI9li1-+=wkXDp&w86qIxtEs?sA2D2`l%UfAcZ zl>@zm*qE-l1)?(shIEq52p*KDLl>Gz%$%)w+p`80 zKT)mRU_JQbcd>ekDtT!zAO*L9b$Ks6O=mhIK^{Ki3}_DA9LO!w8@Wkt8vR~b7mB#WhNrid$nuZ!psI%g0r&=wn+E9cPXX&URJw~#hYsi$z6L3Dj((@D zpP+3l9DZI675sW-?gwghZ1M`v1CeOTA1wIh`^h-a0eMV6(SxtasBa_VzN0s?H6oMG z#uano;<~Gs5HYCs|G=lbXOZyqteaRplCBNwkKIE`C(GsF=?wv%gUukV{f_aBb zd$L1O9xEOLx*DZ1uuJWfu1Lkpp;qD1FMfk)Na+dl;pg!y`L`ippwOc$;#V(w`nBi& zchd3SozK4PZ~xn7b8z^wHu^ui%u06ROaFt!-BM##A~SL(EQs1*s^xgGPJonGu@?~j zXObx?kx798U9M-m8W`2v7nj($SO^aKVB;Xu<5KE!`VxYAC12yLKkJ-WSJuA_-zgNoy4}u zB9;-{-0?*V1jkIT*dzv6$8fvr0IZ%)E}dtbV=m_$kHlo2U46|yu223PW!QA5y?8Tj zbNTF|mgsz|jh3g7u3#(px@yfJ$=H6M({5oDFX=Z-u`?y_JJGzXtI0$O{ZB^pam<--%@T=aDNSa;rcLZ_~9s~w!Tz@?`JTMy9I51hR zr8pKh-|>B9W6*F=Zu#(Y;Oy&hSa;>{5&3aYZ}sqJmJNWsRlsRCB4bVu`dEWJbw*szXZ1fbW>v%6SH+%R!*Vc>pSZtONn2cNWm&_4NIj=Z!@otv z`Ye-&^RAQ$6tnJ6a17lTWU=g;a99odoH#;x=Pp#$|7Mx12jN{RL-Z+EVEqs((W>94 zQCp4jSuy+eS-gP5ZVLM$U(!{0#uWOwV3v~_2WRrQPwR-$F9VL}`sOX%yv-X=X{4an z581<;0i|J#uTmdxjBj}d(O2$EWjHM50cUbE#Qf39y?r4QbLbGn@6Kir9MN`Ns#Zx3 z6{Ff#XSOUlH};jWNFB->zJ(O7tXzZ%FmJFG`V&- zW5hTz&{oMV&|x(?=XfJqO%#HWW~-#erAe{M;l^zgUD&`NhB_dO@5y9>y}O3uzgGhe zANt9qN3s9PsA>P#II1n_rSp*_dCl zTSj8mMaE%AIT=Or>miDGqqO)u*QQZT8^9t_v&64aVmp_mP7k(66dT#Xx#h*7LW&U@ z492%f20PWd)o~}&TdRWv*c6d^4I0)dG1FHhQWuX*s_Im`hB~p{>W8AnAO}a(?P^w| zolhP!o)+SVyZ%mBv+%ehFEDjJtS3C3i$5H#w2xaeP=yYba)t)AYR{>yEORT}6Xrk# z#7&19Z521o84GJFv{dY?wBEEN3kyq5Y;91ltFSnATv=PBqrV6QGwW`tFf@ocEKJ&} zACNqmLo!y{5vPe7@R?vG&Bz#mQ5!|F+Jhy?w@Z|c@!|DOrYu< zj0l@M(qoUE4m#;vU^X^>alXC#m%qGOhdG8!^#3sM8R&1WS8|~ewf7t0xhS@ju1sK` zh>SXvCmuHCKt690>8L=i;dJ7ANmLV6KjqSqtz4ie7J*)g)DsAoO7XlHlC!5r4(z&% zb`nZvbmVj#oywY9sdULB_4*?(<36R=eeef|qbJB2RZj`B@6ud`o-mFR^+;1`i(7|< zX^OV(k#mgsiC-yi(`b9+Y2l#=$Gm-XfbF=9a~tvWa{56^;11S_yhEzmo`%cXUcC?y zcK7QQl^O>}0=1lb_KeMiQDoARErU{-lk(E^Hc5X9X3E$)KfLAE1CS(|2}wld_|J1GH39ciP3FAo$_@pVFrG^s1A$X&1crOnUPMNzw!GsastMhjR-dx@{X)xp3G&^k1h z)@?tL$`B$^un~V3+|P4~;fC5m7Q88(KH|y>o18ilo~*|j?MY0vhfP0^eyMm^GJ?k9 zR%x4)aW;8PHX3JcX|Y|~ZM8XUxc?gMTwt{?{4;*sQv>X&nk#V?I@N8!nrMyK_#r*W zpMG4Kg6njDILkvDhdtWC)QtsijA3+556~0H?g7l6R9i1lpj@43f*cb0mC-kKBj!NH z(p7u~9OT6q^AJ;ucxrw=(Q>w7@ACO*AjYTzVLFg04Tx)CXFN!rmWoxQ8p%ky)QT%u z92O}tLLZ@g>U8{>A_F^lwa#wdu#jrI=9Vs(lB6Xqzue$4WKBxf9hrvRR2NtFOWNC# zi)nPUyMT{SAs!apXmU;)n_dQ+bB|3chdIXC1WJgI(&ch$y=C>}5yLSCwxlE+){2> zG(CyZw%kxmZc* zCkf*TkP>K0qnSp2v($I8 zAFKv60DC57?bfVoktI`eQ`&DrR@m*;);`UY&au_iK4olX;IA+o+WXvdQgR=w?0uD; z#;K3t8Y-HcKlazk{Yj{vcE;FBYAq31+k@=gS)5UsG5z9LIhLccx| z1`B-O_D4T9CCcAB(mmfaVvLXmSNIx3O;r4GjXtU(JTWv#-F_*KESfMPqi6-$bc8y$ zglEG0BV#8Es z4r6lf=%I&bC7NE3^3Ilvi+OMVw$`2>PSVe*k>Xcp}=E-G34h{ngF4T^zI24bu0y+eJfg%y2#DltY}aL8)mV zhh+~gi%y6+3aQKJdk`IPhgroboO~Q~=DHp9L@d3pEbehiC^iH%1b20OPk-aAA?P+` z#chVZ=E6z?J(kcR;)nv{Ix$P@@3OODI-o9Kozp&cLzpswGb(umuBJB7f%*hVrVAU_ zMNVMQW(7$gNvoq&CH>=!?TN9wT*_$Y-32y*Mpj_*W2&zmq7n|mhC=n&Hbn$=Euobb z;Z{n|vYx|+x`21?(m-PITbA#zWlku0wAO>AP+?@>FJCbHp*nJ82=awzW>^Hf2P*GN zQCb9iCk&CWzB<<0P7-fW>aeqZ@~!=tW>7DE2c3MaJ1)Opip6|V(bc?uLv!P2m#$u= zJhnzVRhV5Q3z~L#giznGH zZ{WNLB(3caTQzEeatuTP?2_+CGgza_b#ZLd@0X(%+aKstxGWQy#ml*;cZ=}q7T(sB zz(^^*c|*e&%Yk}8Va$f(E$cJux1zA>gON^2Zv?jF0$ue_xngBpL!)*rv_?@bqP&4; zJ<~VG<7y%Gv$X%@EJbfo0Bbj+ygUeB5OB#$0i6>uyH|kdIDy{~vKg^dYoTC zE1WUF^}>Z{whr|U1;PP&a$VllZobVm0fL_KhW5bWkH51(^j03DjY-s^sr$O+QK_L+ zozcoJ%=y)HMTL_Qwz#}*M4JV&xQrp)RfSx*6m(WD3ZN)}oJX+3HMz(_1P}C#;OF_S zIWAond;JHH6w7)5MZp4z-I!&Z?Sd2gLz^d!#KUCE)A75id79T1bwgYuoD(6cGAopj z{J_lKL}b3oHSwR6V@KkGu;WJ?5T$|E%u_w#CHUkqy5eCp3AGF1pP;v7J3O7=QE+zW zJYJlhzYX6|Meng=8Av_tN4RoZ_hEh^xsgiByq+f0(vF)T*>k#xCOkmQYbU=rex58MWfPOy+$W|ppypAa29R|S0f6pX)8K?A#Htb!5Yyq*NpFSmjgV~l-Cf!lMRiC zGjm?4dkfO@Y~%NA#itONy9ww_Z{vS_ApU|}-*4a0BffaqGG9X7|9!B-|Jz{qKLMQo z@+On`a=U{2P#)x`_7A-RGY|a^s~iog?@ty6(PuhWv6h@vNvxeJ9*bh%A8D%XIqN6d=Vu(ILE>)~kcEq|`Fa*_JS=IxJdJ!YKo zVXGD)Vor=Hez>B|YJiFpK$77`FgE>c`$}vXUsQ4?i%>MCZpc$w>x02M13SSRkLZf7 zLI0^x23rj(5K)^`IVs1ZnaOG<;m8%#e`;HiMkI(7=SLi6`H<<^{*uH}JV7X7R3Sfl z5CY*(ya2l5>Lq0qlKJd$!9&+{Xn0J54~*B^%DoLyHaWY~lKIC$9B$75p68MF@>}TT z?tf|Cp+rr}t-mI6sxL(3e_uShzb)SX8%Ov5T5J3tU?fUzTLP00?t_G0I}%lD2KPaP z*<3-)@-z;g;sNY{aV6AcH?`+Sp+}Gslz9k9Zi@z6D-d~3*+{* zCu76i)zJ*IFUQt_+_=G3Uw%M+05RCaPi@Xpq*wv^pgN>sC^X@@Ato-=8oE<`5L)oG zqME=lbE_-COb8t)hYMD#%E=p0e2pq>1m%j)F0Y@`hNi6MGUWUQy1JxD>d$(G%zr2t zygBO#X6tp14DuZ%hBLEm0@cxtSRn?Yy6GFSn~3^0ql;>9y;LITx=SM85*vZgaSP=G z`5DFcWG_n>^K2^dq-BcC;O#9>asub~U%<6%nu;TnX3tb)%pTm{5yrcE+31yl)iwNj9%*CyEuly$I!^Sj*LvuY3km)q(`a^ zH-5YJvwPL{_Pa`)s?zG`#)ab;aRFs)`Rl+=pW0j`f311^Kd$-TNe%zj;`|$b$x?Uw zg>;PU1B+tpf~JTvyA$V{TivlicA_sSyNJe)B-^lFh@SjiI62D+Hs36xz|5ghDx)B! zYk{!XDjoyVnZ7ZsYo3LBq1bGJkfrOY<238BD@|`LD+}ZT_s#go>*{suY3olntm|3P z2o9L#A~tdytF&0z;F4!G7!(i-+QQbRDj>geRUA8&wubx@2~>D75nnSUzhy& zknGKYdWQP$L%vf3{MO{#1ApD}<3qZ48|oSC`zP5>4)9xpuMhln&5v%foo(Q^4&P6> z>zp4S61_f9&$!=p5UzXpzk`F2AtANqB0(hgmT1;O^8EI9x<*HR-rnlf02 z@hxt*2AY=PEo!$1n2bS`V6m4}ur#QFV6ksRs5E3m(9|~(Jc^E(A5k(KsiG#V79Pjs z8GpbBO`D*XAkP1pI#`G9E#Vs!dN{f!G?b8O5Gk5AoF1cR{B8z;rC%Yqx2tF%iy&wS zxP~5opu}zoaV=W6_7BRP7Ghiax71x$Vp|AEVq5e~ldmsML+1DU+5}>=RjwaibF4PH zZ(zru)WlS8b<&nxfO9}PSUT7X`VYE0`YYNi`cFD6L$*oVKs{eQ(SAfUy-pqEaI-HfL6?NqE4_BF<08JH>+Ct`&)MHA!PO_ zAh!Lm5xNt&T~RNSt~XWjcRgz5`=9g!0zab)I z>xIP_R0YP|0)V&h^blGkQu`-}GY4T1vW>z(pb35h)(pc_0&9vzVS&?&GZ8qJbJMI=X?oQ%7c~U?AZ=y(9 zFB=OGPaLMGAAa4FW9Hg0K8Xf7jMdym469I269=!|EW29XBQ)$-zkhPI#M9=@HM1MgaS|nFWOBA! zR!w0^1t-TIkHt`yVpTDs#|niWw=)Vs!I2L^lv_JcGap!>6$wddMTf^xJa-9c{La2u z1|kxsd9r3JJ2C)nlno@StZ}sd1dUa+L?FSc;bch6U#&Iq ztQo;e3N&L7vJ|4W%;jUx3NVv92N1@^Vk{=;$%tr3!?#h(hpgepB&F`oW#1qU9~iV> zSv07fl1gtK7)u6_mMAX>xDTg36Sl&)1{eC>aoQ!(LxgC}CeKP=8*N$qS z5~&{MIaJlK&R;v)`kL6#RR9d?>dVDysmABh8poup)JBVakAWBr^VRdxnJA_WFkaaU zTD-h+4E22m3+R)BSNrIO_ldt*I$sL!ST>rWWaH2IS2_!iYiQqm&L)ah9q$H`F^*hW zSJ5ZO2Qon$?79ioIK}>O4IZt(EKgSL??`TU)_MiJX6Koy5LBXy25{??B-P9nn?O_Q z5toCG8LHA&YROqmYHI~4X(jN4EM>ppOHC}hTC&zOh8E>$8X4tuz)VllJJ`7+Lv}G# zLu2R*SEP4H05X%70i$+3Q=8W6mTXlctN=VE0QkD)T9-q^RC~wTy7j$g>tuDC;ZcgE zyG^=UeWbtu*ECymNW-|KR~CcOaHw?8WU%-55#0G&)|z0b%ZfrPSMT-P5{GUD0qMI9 zYm-y|@bE;-v64ae`3V~Bch#0D#tQZftC4wx>h514&F>@)VygE~?nkJ{m0vmG3J2El z72FP%NLcBF4)oY#83k&r6o0VJN0+j5cte_0!if1b&z-WlOx*wUFUe3gm+2WS8bfVy z_?$B72yd_+!?&yrR^`9n!E<}|g|y(z?7A%iJZqdgYbm*x1>~ot6H|yQXMa37pT%*+ z1J+I0{aZu|7_|DIS%ERRP^=m>)56ow)6df3zp${#7JN2m(*Q_+!^Sv{O`QIIY352t z(qYg4Vb#wA@qu_EWC>R-)ND53Wn6|Y5hMDv@?taP!@f2 zXcZcXd>g1s@7m`0TI3=ygj{5f4~|$T8){M#`K(n*;L`6!q#TiWk7i+}u{Mj1z{u~7 z84Vh*tvC+E!M-8z<5uw_{8BZ8w}cJZ6XRAsZVF@~1dTa}%B-xE)B+ly80DkW`7Wh$_2L^y0h2P?O2DXb(1=WO?kfEOiIMjR9DOp<#)0+U*^5|IUnwbWsZ-M=4*p>0i&Ov8K$x>2NQB{3s#Z%Pw4i=PcM?~%27YC2`=l7OE1de4)v@9q2G1({6vXb9sAerL~+Q7U4J z3qgES0X+5SMUz{6(8<$$jLpA^6C)Y#vql2KPuB@7s!ie|1xrAa)({&SQRe66Ul1_9 z6cu?##Zlo5f^krD;&mwpigu$1F2Q768<>vjwu*Th)R+k(4KZL$QpIT$m|-rKj;D`6nQvD@__tnf^FpBpG`v zm-Lju%Gb@dn0h{)ni-Bik`mw-LFfr>K@n_knrdX7yR(8x$gqi0rA*e1%}ncN6 z9b_Aqq^Lz5GFDgoCNT99!g7RbK6cCQU`hsm)ShMJh}ggWR=_A#P45x@>KDY4{}!UB zet8ifk^H$viXK?r84^y_0X8W~(GtJ@;#;S`45g}!W3&}f&(MdKx^-)O0(L0te=M8q z0HEut(`24JtuPi%vx<8#OiZ3!SI&d?lm!J(3HWCHK}h?JoAes={pB0U{c%I-=?o`* zHSf%R>WAm|m^lab#GCEV?{Y;o$Zx1}?kH}!L7OF~Aa;qhs-%~s%YSSm#@Z1e^L(?V zYi6RvTDQi%WRI;G+gAJop1+HCb*D4dQbG)fxdtg6#M0C+A(aIYAp9sL(s`8byZu!~ z3qo5X#=nDQ*%GcTl;5&5AvL#T7*Q2wXoaAi9N3z~n|r5Yach(R4dw1WY+L8R1-QK= zmqb?Mu#|#AvX7eFb%}k|SjjF>F>^#bW7J7qtp zavG%2Q3fGD!FGn7tZ5ur>}WtW>!*gFbQP1K{^g?>8W$8Ug|7pinh==Sy|-qPw){Zm z0vz{aZjc84WruB47yi`mYbQ?Mf&VA+;rG*po*lIENfk5niPcMUPvzPY$%k9d6dDdGg}UAt z`WS@fPRk$BTSqlHq)XD{gwsK`cV2l)Lh2h1Wy{)HMd{R5{bit#R6kPi7L5;r;ouC2 z?~}X`6c2djmZig0_fi@fZvzs~fT))PMT(nDkQH*;!N_Fu=1TP2pi;4N4dG_tlumk3 zjq0u_QY);f@-AV4lN>H{Qaey-Ri6c18zgx^m|Cva$e5Q{5>mGJd7@`jSwV)hXV#~* z+XGtdixR=}L@#9s37`cA^6=#ID>c-IEUf+m@ng1f4Wojp%3dW^HImZh6J~RVN?5>v zLL^8-9x})w2xgQ3;R-|NHxYc&CyeRQOYK8;-R>|Q^p+uSUmeZe->kbltm9RI4U@*e zNlK6J8-`EEC0Y1ojGfd{=p2)##ICDG=gn+yk~N>C4SDGTs#*}-jG<0Rwa7c&k$v%} zy(9Lw-!BxA8s!~isg)h;VU{TY`pwfk#*0qbU2xzk)mYj2x97tDj> z_hN+Z@rpM)E7S)C;?}Qo{IQ>vUk$Zbpebx_79K73*%zO5{Yv#O(dz@f$C2 z?dIF(!w@M<>=$ig)09UjG!*53OO^raMKF7?W+&Fn8maX*z)tV!as#M483jkkSDVb((UaGj$1 zH$>{6=)|9b`6Pd#6Sm49VxEeBSW~asHS4TEqr{)6jWwB;N75r0i+D`gN`lEUTp70W z{zyEto3-nFp$>=2BQe?>$2ZNYPsXB#=XTz|_hn96*6;5A*kA5ZhBAd3Lvn#ag_0+! zBtsC@g6J!Yo{C9}M-)>F_ZEsCNoJzz_dr54LHTK{C5TrM1mXnEVu%(>VJOuDZHB_r zOC#J=V}g^8FT2LL2I|}H*H7yoW2n=DySn!TJaFR)>)tX8Zl+8;7kBe)w`?yFo^UWc z!WHf^aG{xHysN6V{@rv?UAYF;_2j=!u$%3g(CaOTtlvzre4oqMzu+A=3|!m<^g-m$ zwT*MxUu3w3@mTISm!6-vPTt(96ICF^tE~C`URYA0>@a56Zl0z6qRTkUt=DXOMXUWu zWL$0RcW<7Ct=&_n4A7+S91Ga4<SvbfHc!4lL}s(8?Vt>VN}3MG=JLga8pInO!;&GDZPk9av5u${mB8N*v`outG`& zafVaS^i=hGWT}MJfHPoGYH@Ez=@WkGbuHfuNgznQ3Y)md4T^4nPy(RS^vd`)FEEsX20x|+~Tz`WQjFPeQQGPLXNt#Cq#9Sx9a*|cm{^EgU@!w&~IvnW`ZMG(nE z+o8bqg)4!G=$B97@5ubcNBC(QlHl&KhfZUmb2;b-Ce>u#jL^~ZAu7_YszHG5jkmg& zA4Zt~zahl~S!SpoPcq<}w_uqmQP=_fG9@G?+M2+~f#&YWE`vF7-ZR}@!_Gjh+eB@- ztovs0YbpJjIa{zNf_VgeMBEf9BUb3e$e2aZ2)4LVH$sF;hTnBA788vwN?1PIoi8AB z_Kv9B3^~Qnn`?@kK#@*=A^1s0ZbfL|ShnQUA@OiUH^W122as`M#aG{u85Y>CQTUTL zQf>GkDmO6B@vI0Eka2npc8Nhs_1UUQSp&5B6}EU{=-xc-j9Ep`eNcmEU_S79YcZ~1 z3}V@-N3*o+#Q$k-_&~FebueXt9D^Kv6WatoKDY06;}5KLYKHcD`2%aM`qyd0e@dMG zxqmL|VQl*6`pjRqTFjpk#VBtjw5M&A1k}w`^A+Lk0hJzt$l>q^vKcH3kc5n4JEv|I z5ce~u#as5jLCPZdf8-Wk3^GecrrifhPJE15++;gD`Mv$V2mOJici2*HtTz-CONu+j zrS_*Y(4|Hn@`vC-4)&D8hugV#uZ6jKd z%9&-BqsoiP;1xC!yzYj!#{%^jdy z=Gbz~cb-3V`QbojOmSM705{Rzfex8W;}njwT`GF~CzUx>IuK_;?kjjSYN*G6T6U~H zV`=KS`@@oGll#Q{I^1S(VOQ?hdsA}xmTmvBBo&fxqM86?SF_Y`+bi%~m2*N8k^ccZGGLJZGyOEHF9qul0;Y?Z@SN<`bm zTO_pLQKP3in{ROkpOIxWQjB#nzx(hFsCsFb2CcNR2`Q+4MkCfZ=eJTjLfO%G%oa=+ zgNkMJ1UZrJF-MLqlzEJ5*zi#Q1$RWs^9b`tzF;JQU+sS6i_E{~%Rk+IWGtOsWDOnu zdqVX?u;6bN#`ZsAS+i)sYVAO(aQ%Q90qr?SPX;n1XHmd_|bn1LhG)6NpeeUbw5w%HlO&}+5P_V2$dHBF$5ax3KwS_Y@Ad$s7PNJ za$@T6lhYv0UExP%Fr|tPP5!YF=7Ub9QO|%@*GeyuaIKnuUyY`9_E;B+Rz@|)?WC$f z?)(rTFjO)^q=xl#ZoKDcjRyB??r&2ROHx?H$x{d}mVDm{hE8s+WIa*yNW&&nEZ6uu zpOh>KePkue&~n?cM@K9PTFi>1ey$)kM0P07*ZWYD)WI_!}btr+#K2*kZtj>#n(UuG9G8xN`eYd{MRN zobehipl1$C!jZ|~3DpT}xirUoPL6PWhI$rPX*iReV!uOX0rZ_T-;$`^fu1oqyO_3o znfvwl-rzlbGTcfW9plX<(jK+WGeGYtNa+fCV)2FHJU*KY{%4YI?>OQRmLtkAcg&eR z1$$RtXIi{}uFq%pcaT34#-r3nf9)e-R{tN!993saJM+KGCBp1`P$(%R1*v_sgnZ$H)*}? znidcp%auP`XY78h0+Tb<9y3O6vtJLxMt$*_WCL1juy)NYyJfUS?Fsw!Ygz?o7rMt4 zo^Tx-Ly?!q7L!bx-X@-FwavqR^jEXtjE4kVY&VQSG3M(awFIW&u%Tz!o4nz?9AGx~ zkgZ47H>lp!!&Jk$wyt}8NL)Th;r&Siz|q!yS!li?<}EjU7mp*cVgySc>@>PWgTqv+ zvrnUmqyj?(i)hJ6Cdsb2U+^thD>+Um9|1GLuH*zV$ykbcQ5(@b;2~6@xc$0sjkH80 z?OU>b9+`tg9)pcb+|%HG7(#K`(*+9_RH>gA6~c9yl3zhx9L5>!G%6#U1j{?@AF=z= zPh02l5j(4YjUDkn$k)G^^MB}-{O5&G{*cA{uwzk|YQ|rMrz%rbOaL*})=)$#hNQy3 z%AX2@hS+G@(?V<3{k4YVach1F<0C3eIxTV~itL~4c#ejxwP29(^HgRs$9exeTfdv% zADki3{PVB)ONCxyC{8G5Bs^&kED`pnpvyvjNT{zOEi^((A>W8}khi=b(Lcu4sRdBbTTR6Be?QyD{@!3GNRgcL^dQSxDi0Ky!+aeGVdjEcDpT@(nXOK)A6af-loD#Rx8*hEz?;B56uSEPOU0#t&vSDy=Gh?*JANS zjP>GG*Ok@@Xtx*toS&_qw({wHrAif6h<3Z5zhk^Gd&#<+Vfx(cLzsu}-C^mMt4sg# zB22?9$PW9;F*dhulWo4+e;uj0Q=XM~%&`{u&Y^ADT2qsu|AF;Inypbe8wLyNt*uYt z@ST4Y&?79_;A=!^vn$4ssqCJ=V|2f-un_xeF9N*6Ra=x%9$D>{NyK)aCf1$nrF^a? z@>jIR6C~!hS4*mrfD;i6>kZwNoO|PVNSO?neA>@OGgJZf{iZY8%Q35;!Oz974>^ubu^`4axC+L_Xbw z@i|E+Ch2m8j8?$O=gb6=zRf2Y?UO1Aj8C)`P#eSwr;^kMWh7l*6A+Pn!iJ<45BIll z6xG$H>>s`i7-JK>1ON5k{$5*a`pl1Lt7qAAn1%sj`r*Y!ZDkAe6d&6L<=ht#t%!&X!k>_S;)2z zHAwUGgH}#{Kfhgn0@!E;n9>=i0tum*%ND6~>kiM)?9y3}8ohAj5)(~qA zh8hcl@(}G7rCuyMCK---b^v_#eZB{|4r*H!L3HsY$g<5)7i36#wiUl(z8%ZAzC3}$ zoc0r3DSk_)pK(h1lG6!_vme;hTP!bo=XS%!klwbJVOmR-g1ahxOvurLkcgw2t<@NO zvf2nTuvhIq$vzZU@@&Y7ZBooE&6b+heTG=_8tckI8ypy6$Z_1j51{ZexJ;tM`T zBopGjZ@VEo2AcnkPpVrxQ(`uuJ}~_XoFO?Zb2TXeDex&xmZ2kBDZ7mLi0^Kj1j+B( zyb~ae9&N$Qf!=D9f0Sj_Zts!C$o?+rlI$=z<@ck8Ny-8yWB*J8tK4$eAD4QqUJ?k7 zgb@+eRX2?&=^PZ&6b;_iDpzWGof(FUo#ibV*`Cpgmt>uI>Q&_z`Rit~=A3WzA}auw z=HN?<3y7bCeZbN8G|quJa%E8J5b!#chnTUX=JZVk5qTa#Ulg}Vj%4A_dw-s1vfI0D{JFXF*^S0V zW2vFW)UX|hRKW&ab8PXW+8EawkZou0G>PBxt)-ikn{tSA^EpYbq>Z#>!6Crhha+=^~Ih+vIQ~d zR*q+FM2r1k#Kd5h!m9Bp<|2&n&i-UH?$fLN)E3uVbA;O*5(Wq8b*o5qolUc{`p7ep zRaqaINs1k;QBf4_%u<_B*r=x^yEW+Ot9r7&5}XzaHRhp$je)7;S}7&?sN4Ht%;U!p z?42!L?43S5<^THe z-+?Sv*0rByK;b>*0}6Suo?_DuZA@dPq-?y3=6nmHCyTToUNXp|`~|paSnesa zp_B>Jz7-LmIAnZhwy6}i;+=2-)OlRJyXF`v^qchC!eM5w{5CN=BP_-(v%tD3c69M8 zeb2ghUve{~iCDyfJ!E6AQ$lI&B4E@YD0Rh=_x#2|-PXtaI?*B=j+kPitUTL3%b<3| zcOmi#F6lc9anQU!3o%2S2#IdfkgHcB9K4YzUv>Lw9K~$hBABV=e;-*hM{X9lFgaLras* z<}EA8zwcG3;H!gl#gOi4D(c@*mC!p?*q#^9T#QQ|)BlYsogD!JlcFAVa3B;fyQqO46l|4VYx_RqTlB}`7OqMPPv@3vmkk-ITT|H;+=$97yAEF~7- zp=S?Mur&#NAnNkNrNT?6=9|lIt%pFT0HsH9J`MT$S8wQS=4pb=U)(P00NW=Qp4usOJfq>A? zaLqzVqs>4;nIS{GkiUEi!z{%=j#ik%)bBcs&VAiSf8FhN;9-YSSUQ~8ZGFf&$a0?a zm2-M%_V0TB{KM2IwFegQL1a$!7kVv$kz#*h_!lC)NDq>2M>M4M-AlAErZ7b)s17p1 z2u3JMfD^`kp2V<$h!7e&X9r!v4K@y)qerH2F|s9KB<%+(+{IX0KPx8wjxeTb84h0} zW9Wm4jB}ZzcFd~TqYZF!KYv7He@y-gE^L)a%|TV5jz8b=q0h z+gC_nv38k_=dMR6TPE1Viq%D09Xr+(6L!+Yrg0y$`keDzB>o~l0A_Dx49dF6yb?R> zoLl;56<;w*VCNdw^`95O7dv>A6W?{#t66+ujZ&>wD{{?79bUvE{Tj3OOf zcPH5;wOo9f4Q0}KNS;lE?QKJ}&2l~DFvLb&7nw7?`Z5=Jsb#`xpf3iy1+Fj&;0Xlzevl^REJnUz<<80_ztGZ(iqVY!soHwBdu2%A-3%4jq7Lxkp9S*VrW z*h%IxJzCf0^2vK~=$p|_u19HW?>bA*1hx&U!*q3uL>VHTsH_!TBt~yH+5H~WQ5JZY zvz}L0#~o5ekESg(oq=)-3uey?Q!bIZQ3jr4`f-y?e@4t^p}wZiIL{?|cMEnT(^3y+ z46}P0rWgLjt*p3m$xe#j3SUtw58FwkAqt-?xtS(q|(im6iPn|-4rEa?SHW`0^`FtoJuVx0{ z(=fM0D5aI*AaFu(x+s!5!$ebMsu@PcsCc5H0W?kT=W3*@%G+zqBCE$q@^pC^{T}~S z^MQW&qR(V4>~J}F;yR1gav+`-;*?hnuq3Irc^a^QT*v#Fd(aEy9_eJ;rh5UquJg0D zLFZ@clKTXy^Zai@chno(^I-Q0lE6WiW6f)oW}Z%cooIf7ok@XYdAVC^(I?Uc>^?lZuiU|tEFI)!2_{UC~+0~35K>Eu6vKL~ zV577IK>-xWq77)`f)E>a!Zd;LE-N8>`hhUGree6B4ukl69(I}{0_H662RS#{cYf*4 z-S3Z&Py+lp6drk-`9YH4vmcz#PfCv>1HGOdrsPdA%pi-@0%|az8zooKVKNbt5MnPO z*c6GVboh3!QUfqgq6&q5Ofi}b6(<8l5!Ics$Oz0`<(F_=q~amfpo8`RN1N)eAzD;q zB^g5nQ5~cDL({T_Hsli{X@{}B`c&Ik#<^UfCWjdx!bUqRybr+bVJygJfT8>~$HnG% zj;V8>D}XTs0D`o{!sxjH?cyhXKvMc5&a&>LO3H6*|IO#8&jHqaa1sOZrv+L-y zQd;sM+*-?hNH))F74LKOVc6WC@prhBD=fd0+l{N~6xFZi$=sYM<4ScFcS89PK~hR# zW~C$2A%zb^Pt0MJk>rr+NDt64r9{Y#FJ^W_nZ-C%n=z@PD=B%HE$#5b)JtXQ>2MZn zhy4Mn2)8m4>#??XFZjjLQ2?ffmlPMER6E1%lo&K-R2yj$Y+f^$VX1 zG~`~8*gm?Fi)*hGhbT?_)vu>4Uvy3$#ARi{D1H3~Hh&%7!enXI+v}#KNt~ikEzhh@ zf|(VYcdVZBTgXIOSK`Zxb3g^C3jM5RZDn_`l++$}2%G{tRybZLsJ_CAcL1>SaN2wn zJ`ZNP{y6*A;*+h)_iP?q1*r1;aJf3x1|<`2*L`z!tdg!!&s!d=+h&SIctj1(kh;X7 zJ07bI;Wd70x(({Q1T7XsCK)e82){p(z?4d(pW`1@5A478*98Bldj3?h{QoK2{`J(l zCRu+dXJ8Dyo9isZ)JcDkZHSJ+XQyP_v-?Vb=PXjrSJXe4w~OTH0d8s9v*T(v0cdsO80<*XF}sqEh$|}^z4x2kX*5ZqS#5a zQkp7`(o#w*MJm}zYsv`=63S`{4p6D7{2BsACMMSE2`pirqeV}8}K zzjJM0CPGsH_@*8aKBijO>nOrCPkpRm+jq$ zT6@q2!%bmvav2$L<(RgSYRPpi`@1f!(=rwX>+~HK?0hOlygAtZ(?Z0B-{^xO8tC zNt-9LW5_en2OsTNN20_SjQpQ4$;FZ zn50aA{=!c%q=1v6dO>CK2Arr0#W{iol&A^OX}WwKv~dx|q$w|&jHm+g5H_P}ACxmh zttioY#ak4p-|T>CJk`4Pf$w=&-_s6OqT^T=6ZurC$Dh)6ux=8zR+8gz7E640TT?F+ zI&rYSgQLF&s)z$~v;*-Z63BFm5f?0@xNJwKNXP=c|5A2n+v8NH{zyEXf2BbGc@ry} z8af$U{0F&I)wci8QGOe2zB9EUFF|i2OHOQXwJ9BYt)mQO;h5VTq^yqYm2Pw+yHYz@ zt68rC*^WW?*yiN_Br25hDMIg4#2Adk3q^nWn(fS>^r#fy4(Gut^MTLC3M(#^=Arl1GZN~%LDjj_NTP0NYhgn$fjq{)MX-r1R-5;r<{ znpqRXVahWqFcLeMa=)!-SUA`FLBe8TaH1?6bdt@WzB_fm#O#3nbac7I8dCkqOk#aw z3p8&0JwdzEl&=ZwEWYDeY-mQqG#lo{!${4C#Ti2a{ixZh&Y%`AalUTXJ32bET=N#% zLsc2a@(N{>%}umiSo#iKHKd-!nScaMtTE8&(?E#+GT>0O=GPKkz3)$rAHwnHnL67j zCca(KUh@vQ ze=!UBO1_p*Z0tU3}ttE~~>fDn0RD8y}0cw~yB5 z2Cb!)H%rTz$~8Q$83r8L_F*geb>WWI?q;l)0*WnG>-r^@La4=K!)6#zh$l0s*~W24 z5oYEhq3QTam~_(}Hjt|=HTKO3o5!K7@R8b$FxOG7I1vo(3^0vHHDR}^?Y;LUEO=X= zr<%Xdn#EFZ8895#D!c796?L~uK5(evNqL0Wh%5@S%*B;}l(r@aKtL}?zTC|UR1D_} zSbv^n!JG)%B3&7u8nuvJ#NS+cxDDMxtxNym8Iy;bl^gIDu7u+Pbzqb?z;URZ7DKuD zn2kXJTZ3^J(Nf9sM0DuRA|BP?r)%#M{2l8={r%I%z`^)N&!T#{4{xJD%8?~&vH4s> z_1t`P#i1u}srh_XMoM_^ll~@tc__c)N?(vxOV}vgM1?0N?J-aEHG!6zCxWVyw*;v> z<9wd;G5?kci1#p=usN42x#yIA1az{fzo3>cCd$UjMLpJCdSy)u9sPx~I~54ojAK7- zA>P)Ci-yn}xoUT)j~tN?$u*8IRyRSdo^L?8x5qKxXg4^>`&XB!XA)^%lNFcXrR>Up zX;B}-pd9i%n-(4WU!vvf9Z^QFd?o(jo=33D2zqbme{^gkN|xQxAFZ?azlxsX{^KF} z!zBKHP}a8b@_%@s4s6`Y&lF7$7C;7lxtb|4IMlq#J1qC8+4e>LT9xMZtm5kT7U0ybyOlm|OsYcF0G_!inmRp(Z}uT!=A=211>G)UOwgG-M=rm$P#eTb!|^;GD&6?`FM z$IkAp`9q`p*HL&kRv%(GSe*r!G3!%a%QZE+-Y(sL0$?TPhPC$} ze~;O}w!B3D>ks?~2=*U-E=D~&AN3x%psEcO1oyd=7o1DmxavK<)NwhE0@v$0< zXdnKV60RThA+o7Ww2pQa8@>_#SQXA6@mL?;9scMIR*$F?wdn=c9kuy@*n!>Sjrc+s z#4mRF9r8^#V%(zx36_DX7Q8QIDY6eBjuFcQV8Suxm|*C42-+g)gX}{F6aw(gAHn*P z0LY3wkgSoghvvg#wTPHNoIZ{|Wks7_fn*|rM}(&W6bM0LZ5E>OJn<1#{@^<3RffSl zMw!9*AsN7Aui>K$p!6~ZLykGC2Zk|guhbcEfJw*jvHV2THGYW;jAGO=S~CTH#n3bJ zNWsVto0O8@VN(7Q0pYCPIH>1mP813df?93o$zpkW$F=W!W~)EJ1npni)ZNFL5$VCF&xm zFO{oA7A71n1Dv|>`gchIjujygM7rNv2&xcRZ?oD7CsS>Vgx$<#xo5m5eQiWJ5m%_D2v|C<}1a%XZ z43{xhYyl>UH_3@C`dX4Ev#{MEP|4*f57^W^JT_B+z+3tRiaw%gM!2ym3`C9;U8U=U zraaxuTg$&5jLxbVU2FC!5U8gyKu!5^#9DWJ`vYVAtDI|Nwnw=gN^Uh4r+Jp!d?QQx z5szI%#?Pi%Pv&1!6=g5$h|xE8b9t-K8CZ2p*^Vhj)t4?NTf8PO&QfSyt?p$M-0sZ~%m63$pFdzWVva%R6DbuvzkRwGb(p4V!@WMA5jK;im2F?~=D zvGmplBHw;s72QRl3=|nnqb&qdLDl-@AY=S616XC@^X2mNy zCpz9!IG!MA3@&RHD)$bW^JOXK??3Q4rcld^PD%+=R`T7r^bXHziJY%;Uh+mnooQP} zqg>IS8T75FwI$sN{CYD^7vXw#dn6 zK6?cToEPS**TpM^j>2fBnCvooZ**04MOFR%1M2a?4Z%K9z;Es5%ZM|Ihv7ZC*0!!+ zq)~|usIH`Vz2#@p=hd z5|v5JC8~QSFD3Z{hZN-~KdoARi{0Y04};h5F9Bx(G&gyj_~twgCpkYJ^4w1P@;O6?rYya0e^?wuE9?nKCBt{=K6v^M;n3&p_y8IW%?V6x14a$fRD(|JO z-Mrvs-8lzMXN0N)-9;G*9i)IV{gksN7E`%gH!gLjuBnfL-*4`p%wdhLXm!Jol*Rm; z$&imvf8*0!t%L=l)j2>h%g(DFE?mHF?S$Hz57YBZ=!$JE+IzQlWJb8~3Ys zR{m7H6i!%kEaXJ%j$)6^&T3t-f~6<*sc$#6maGv1yZe4vAawx{a*2nJ%Ut5dZMB{J z^1=<1>?xTUQQ~|gjIaF(z4O4u<%h1X1g_=LH!fkz;T^!9-OzOE&hyH#e@~rOFLFlB zz;GS$SS&4Y{PXzssQTCZI$&TW7c7l%u%X1%W#X_AjwhGw6s5d!6xxZX;|q!@ijg&E z7em_{+REuXDiD7A%EdJrk<}L z_|k9D!2-dv^{X#4o&7@A zj2S=I{^C={(o{{shlN!J&=s~31QMESX85at0DXR>Xpl>%qi~IKbJ%T;79J|-w*x>r zHXhrX64>w56UUxD;vgOCRFjoATH>-sU&o;@2}Oht1K=s2le`0^d8HJZS`F4Lb{RZH zaplWly;thmx(rINDzhKts?U^@1TLNvTr|Um>S%D|I>m#w=~)F?%b#SNj;~5hw%vqA zB|?1qD~-t^NnQKoNPcR_2an?L)A z|EI6(*GjI>f>LV~F(6V?s*QP6pd-Xd7}Q@i#2gn^vM`zGhk1vPR8aO+z{H?qE(o=$ z25sCPP*+T1T_gukM*oav+NVuK z{f_ z9gd9}+{2|ogf!`2zJ&m=Bj&a>8RbJg_pTXF--!8ypTXIs8P81opYUBVf+Y3QWN@U= zhjyYur<8#(4GQ?>WdqPrVt}f;wFrifIfQbVU(em#dC+6QB~xk>qAw)6T7Ask5{+D^ zgvE89BMdg#XJke#{uAhqvg0URPG1`~v>!OSS>6|7y;b7_K84KGI?CU(?}V#*qH!;r`FzjZxE9M^;C9gD@h)gy^5n z`vQ(a1c9Jz+sL&P1-+yufI<)3)R<%vFl|UCXQpyGzkb1Pt?g~E?^8twQ@Nt{6lC4_ z{gt=+oVoQA8Tjz_7~cngwf=lsKWXyT^{|@zscGi`Ar{Dh*n2|Qf!MoB*n!;JPS}Co z`%LJA(2Gt~N2G^%RS`Z$q=$T!8V)b&L$Ym*m>d46(r->w{oyv>uZp-4`6$=_K;(yT zRT!Sz8*YFf_G-w$gC+(#$H2Dj2*rAsScCM1w6Zr33_MOkBZe3$0Y60{jGXgUS|iL3 z>MpAT&zsan06t6)PTs*IvX2lB6bDXv_w7>{AEt1PDF(;LmL*&tPELX&99md30DXsv z2kzhsCv299&Ol^Pl*Cl0mlBhnfOJO`C+P}lhDi@dwTZvz#%bI=V3%VG4VUlkL>wyWz2JA52(rucs%VHP$f3 z4Hn*6ysCBvFh+(;Db91nf71ujl$=g~k_X&Q~R5Q?sSpKG&VsQo+hrXrX*A(&J$C4 zBZ2zrx|4>d@|h=Fqoj0iq3+OQ@frvIE9AqVT*}E!A{g|RJeL+(Vd(>BESuo<)Oxa| z2Jo3e_q@}S*x`rV35Z7e&FcO$YE9i_&FyD@ z;S_DdQiGm2<(cdIF|TwX24@uM*k^9znPwUYCNJ1O$2cQ?;hDv0fQ`yYN3RYvZBBWJIHKjK03|% zHAro5Nad51M239Iq^H=iO{wmZ)_KS?D?97L4hP#Shsr?-1TJq?+|+N1)~on)j0{Vi z(Yq*~?zp4;Hi<$J%Y`N@WO{O2rt2WeCsFmNA543+Nwm+ zj9EqL%q$gRpI+o!dTQ?>JIzR(q*33IYJ>4H;$)OgB`By*SwPHJ3=dn8wPGEc5Rz7c!3bG4ZI{s}w0duZ`QB19LWiY$3{EXB5 ztgHU4a?Sn2_G8c}kY6FY%w$}cIlgq7FUB@$DI30~aM%c2bCm=yM_bDqTPmngCjV?A zt9ap2w0ujnoD!vYWy3V7Em8HrU2}EB3bot`X%)GGDayM`VvDGlKufgHvfr)B?Avt# zYqm`c(rCo7nd1=pyrZJ7Aj(M8LY;(Mvce)|0g=p3eux%-)L2(Zj`cAN)(2(wt=gOh z-$IytH4hBLFK}A^$V&#>=XLAbwu;4qb&^AsJ)n0{0niH_-}T z=wl3%R#+Fc!#)2WsOdCQrPRv@^;Qb_x1#Z%Q*ZyX%=q7Jz{e`bpElPY9G#n`iK!C< z$$!izLzQinX9W=asugBR@(mDC1@#J3(IE3EpbK{>d>pg{DmZ*(W{~5cB*|qlZsC8U z;?y#4ud|5)fEz(1(76PsxHGRL3}A}bk>LBf_)eeMHq7m{VhOAfOXoSY>ZNj zMN2S`aM*e1=LgJ{>m6(W`*pFc^CAn*>>86=m95qLnKwW_30Z3r~~PLRV@+o7;AFyL{c{mRyhY<>FUvX&ntjPxvsj%7&oFpP8Oe@ybHav z>!MIcNABv}bm}VZ@gO$b!PQJ7qqVK`Jt2edwu;tF!BQn zt>p8Y*YDdpWfo``M;1CxPH+|t&fjyyvT?aC+%p&hpYmPmxJFX(xx-b2VkJB7lFL>k z;_wJtH6H_H5=6*fSHyKJ^1i*mm4Cg+PbI&n1WB&WWsya!s#n}KzxL@t;B)6EUbkkv zfHakimqU--NU$SlroRz4#6_G&9ASjE?md8_vBsf$6WE807>B6afF2MwfkqBfncO98 z5CUEMa!?~9LaY1N+QRmHH@5pn^<@07d>`-sb9g4=qd-PW~aa& zMyVvFsE8QaX(8Pk#I7eG1CTPRYsw&s_*&mL1z4W2Gc%95R54&7$$0$4mwW}VC5nhi z`pN0smCMP@+3n~54rPEI7&e5rz{_)WIoAga8|T76J1*OdjbqyPRhhQPiz$Jrg1|Fq z3}pRfZsFcGjrrc+7jmM=5k6T|;|5@oRyZ=~A^Y=>QmXIguI~J^vpD$zhRlebvBwg?8{JGDlsNocSMZ z%4pfn_gOZ-3Ty1rG)ucLl$WN#;rqIw#&gyXTlDN4keRRl@}=7c!9ms{Klo>n(EjgQ z7S7rpI^M(YH{%@A$qK1ty7`7o$rjDOZY`cn=W3(L2&q+Q4dCo*snIhpt{|h7E2LE- zu9fJY0*{BPzL)t1y2HND9681rr7#gtG}W>++QyG8zpO8?&v`AD>5otG7wPn_{dxIu zg%7QleH7LIE-8okkrE$32i+e)2i-?c?C{~E@G;KIK*z{P$Ed5sXxGO8_;mvfkE=zH zc>D=BgZ~yB_+`-0D8J4kye^?(cte`17SW~7qoeSgg8i3ZfSa-^Gub`L!~BHbidVg* z0S;=Z%ep#!x6iOzEW#_i$rdK14PvGY$`bSz*)1gKIW=XD`1)SlOvh?lQ)6QLr=QZ^ zpYUx3qf@`qm%~H=6NmxpwV*%x*4}ILZIsiXSK#)O~uAs4h$s zJ<36DSEcyIfpA1Co?DZ2@2Z)-?It>o%kzaxpyo>i<#2zRjXdbOj;C`eR_Sw!Ynk&} z&-*wnWcuWh-yC^;P2cx{C>UtIZiKdTK8i55v$UFZGce!aeP1g zk0nL^15&7IJI|^iyrJmtE=3x1qdOUa1PVCrf_>GqKx{xs{6t5XA0mXjq9m$_q^Rj+ zk`Cp2Wb6-W?@Rg9#Uqc|Rbn+=Pj4mttxV3H6cKXaHa~}RBd7k^veDeV^M2#~<%K#x z`I1>9-cfxJoyi6B8`Js7qY{v|kgzXdYsBJ?D2%F)rSAk_&(Ja8kg=tM#yeFfvDrwI z$dM!pJ%lO8)R8Glzdk|~g$zic;mv1~VVuK|W9%5YWb0eTnMqS|fW>FfQFFj{1<&0S z1|MrFG5|-%xI<`boXC zy@O^aA)!Kcf&@XGbn|fg>@~iS9kY^np*b{WdtjGCIhz4frIshrzH zBDWrA%wJ#VSg2kzHzhvHaZv`!NY9>oa6kH_QXAXWNf=HZ!ajbA-=1?{sJ+(8i~UYD zbE&ump1xp{uU7Rs}4LA$Et z+BE84?S@x--P`(^J?wYUBb%WIxh2FzO;g-P?ORn=0{C;sv6(QtNAP&eA(vd zmfQS8l~7Rb(hwo^;g0lHud0BW+S{`+P0mShLlnjI6~r5hXHBgw6YVtsm*BI73Vm%? z-EOqT#ZT5We9NhbE~>t%`!=zI1|UpK3Xsw~`4(@f9%$fB)QMswmRjJ65ZVNma|twg z62T`tBs4RY(M2Lm#pg!1l(r;3B582R=HXKHHBnE*PdjC1G>s_YYmvop{faRfR)n!q z%v?aN=)#%)xcvRxeuX1*BH#-8!JTsldeWj(mOA^sRyzmAL5g3JP=!nSv0wZyRO3^& zOHrH88T>AuRIqY4adSsaaG#cw*B9|dl&z3dGMq(|UScia<qkt-LRzxjPgT@fZ$~Fv>lMps6EpgBjRYoig^L_;Fp++YQ+Aj;1ig1-f0}R@6 zJVKsjU){sHL=|y1*OrKfM}{QG@Cp=)pTF^MX~5$VCqbr^?pLnyb5u@$4MA{k95%J|U-^I_3Z3OeHhUBtTaW|k@jL&e%OBXhZ ze0zgI8r#$T|CoEHFgv(yTQsRkd6sS4wrwMYlx?PL+qP1+m9ov0Z6{@(nSZUd*ZJ4o zJLlYo`+fIezRb5ddmnxD(RypG11<%4`lfBEbe<`DIQl;IVe}~m3%6q!xpB~K>>sWp zv7m*-l__O7iPOyFFjD_b-McsrK`#B;6p_)^8=Di{`WQIrWE!~HCa=#Amm{Cp7tPdA@{UKdB=5cUVOg$l^nDCTi)Un5@`;02ml8 z`a@xAoALGTW@;G|~o>de3h!G!~wtSgW#2 zb;%QRLeZz-WjbNou?&8KI+eOHI>Vhq#MHVqHWtopo3!XLWbH+}NfdvuV8hYdj+=E9 zN=9V0-M(GX7AFjwJ4bGxpeQ#ByGh&-KMU+iTBo#OpXceG3e!xrqGkm9j9GwdKG$dDC7h}y((@KgK7H>1$bjSPc(zRJ4u{65*q3lt*0K@$jV_UJ&Y^>{&?!r^gT>5T7& zM__3ho3VG-On2j;rI+Z<2t$rgS;zwtka$GLzyx%7`G)?wRJFEC!);W%(1V~RVF94?$)`#*MD#_VHu z+ZlRYnE|kuA&5PJfz7?4hT5W#mn8~zib3JjRJ??g{*mE$k&-PW`ck4YUlOKCwN+;# znn|^jud>5auByWMWGplWD+1+7Xv|u|rbzc#Jj0w}btNCL@GV<{;8{Hb8!ji7?|sTwDXUlHGKZs3s`sOA{0U!m z_!AmO-#QeD)_W3;m9`3mD``2)LeM$7R9To|s=mv5rUR&z!B!n;-l>Gm$_Sn0+Of$3 zQeCoSEvc^npL@?YR$O13Ljr~#-q-x(gk(wL96v9KhN%!YV9JC4Oc5trA>kh zqeI>45RG&D>8FG-@J`*DT{Ow$5LzXfj>|$vWe;Y@9Ac=_V;?U$6!>$9#c2w7fWw6;@%fmgb*QJy|I58Tl7HC-%7=dnqsYg$oMC^5>8k}NPam1oylx*M0KH`*i& zc8`!{Y@b~x>yD*(O;J2J`Y9_D+lF7_kzzqhVb4pCx-Qj#axLQpZLYutznRn-^I!z) zKJaF-;TV9D^LmE`mzd8Q=NTLB(9X$w9AY-_jc~E^YL=?|p*@`Vk+jTOn;Awggfa@PpV-id6W5HdPJ(3A_L z#^4nQKilLFj7Qdia6PY8k-pf)FHN3TExl8hEeikAsU@|iv(Omg`q|9A@Pb$A7atK3 z>*XQ*OagaztB5+-VUT9ehb$;D1;HouNFKBmWV7c7K{M5G;TS&u68}@)e65%;JYG5# zUP1$+FFgou6UW~~{Ui5ojKKO(GuMZH#D&Nm6V6;s6VlsLX1O1fCo?T0ITHil2`vIX zh`iZu+>&fCmVty^e*91=@0OcE0R*4Q@|hS>I}Y{?&m*DC-E20XvZGKXNZQ z{`NTg&t(dZhWb|e|K5lBAFxUga3wu5a3wv&ikwz3cZn)pK5-L4r0BI$XqZ0XQo>(AKt?*_p2Y3C8=QZa4Ln0y2JNm)TDFyWyH?xLNELPlcD(vxCc>=ldgYCp}Lx z%i64+R$qQFH`awk%eU>WU?FJ6Old;DU*-%(vq!z6U@0h(MU-5bVmQR+`H@sDxrR8( zTmYGq)WN`LOe54ir*W#eepFocq-6CPUnubP8tqkQu3?7w3Do9CICkrc`3Z+D$!}f# z(^(VNj54P0;nph6L8vtQZ_bP~*-|&OHjW8c%SO^mjK&3s;2jn)^%P!7?CzQy(!qOm z#?s>8@EAyA;!;)vl~BPw`4#SKmX+;|(`cj{1iC(oT=^($WxXC%qs%f{3mqiZ0qOE6 zn@Tok09X4!RRP1_5Yv&QTdl+YOs>iHn&eSWh&Zs~SVj+{Zwj6OynAkBE`|7?LCNpC87|$O6WCjCHB%kF ziP~NVE{A4YkESnw?!JVUw*Uxdv8M!)x6*DpvoSvAh~hAJOhXw7r^1(eZ`o68z+{Ey zm|}2SDzZH1*qP!3V90fXdvK*-aEVWvsM32&p@uB1q2woQ_m0I9oHm?1ZZe#RgUVA$ zPO(0BIz68lLO?@g8{nnH2wfg;O2plmQlBQxqC1`E^W9_WBnee0Ni@uOggRjhgH`^7 zeTs;@L-q0)Hf>;YcZnNA5RSnJ2r>$Zi^O{;fIXcO&gM=vh_Rw1&5Q%+aOpDYW-^(uO}Xi<^jB4G5!c1V*fV@{ud7Z z&-*m4M+!(S4t}?`nk!aZ{h=nxqrzK=s?9_F<&(T1eCY=JF;-Kdq-4{MAti-B1b%l2 z61la2f*^c5^YbM;Gk5(Zezom~=vp1|C6S^2;;>3^z&x#r-in6k+#t9n0Qc*bY=72P ztpw4{~w#<>cqR1RZB z<~PHUnu}7AFO%Q=#>K!!rr*US$K($`Q_!m*>gi4m3XK(rVhA%wie{H;zK1NOW(WERd`#X z#6hib296v2?k<1e(jtHRF?G4@Es1rZALyd4NEIcQBDd&d`O<+`N!I z3guHO_G<*XB}*yLBusm2<>_kC)U4|%|BO_(Phvz25#l5$?~LmY$joQ#DlgAVRN+3% z6F|=&vLZ(NMs=^kde1)HWqQj=5&Gd#;5X_lX(%m;+Lvda zuwNMO@^=J*<|+Nnm7%$*eKH&4hChcr-d)|mrn|#~IAMOFq0!K2YSh(O1?bOh3O@xlV3kus3^1sPZ3EkPMalNM zmSd7~01J9GTs)xz*Q~-t*lvTj4Qv z@sKIxTEv%u9FOR~0(Fv%0Xr#;Qkg_}>#m?4fwz%s!n0xKy)=w=@)}Q*WeDUa!%xUR zU->o1`YC0+{*vh>uU_q00mI4i$8fU!--pxI&dJ2w>c6OhUW!`upufi^M|DzK_w6EB zib&;CB1F9jmQ+YF^_DDiGN$Rr6jJ7KX~vOD5o^~nhRJf5bLU%MQ~+}t`nfTpL3 zA^Pydcw)SfZmCxc#{oU}GuZ+VZH^Q~DSaI7m@5aPA%J>EEobjh&;e)KAF!uH4rBY9 zD1GB!^%p*uTIL9u`^%P!P;prX(LqMp_q5HB*P&@$6wY|8H3jO(X}VO2NLd`KVb)RO z=iE6v(qM?a4}Z22)wU0QNuD<#w+ZaUyHbc-sZRePMsz$e-IF2fJjrm&x-VBTpB=XX z)oza#W}=jkcjX8h+;xv8sH!oN)FDwW|E(~pF##5_B8b-1ZI8_mmo$iqrIXqGa>OUa zMB|D53zfaBRfTl%v0RB>snF2)?97pb(+@VMJYO6)uf1D@93Gb}J$-y(k)k|IqYdPj zAabMVCfgb3BuGYqR8kmiB(%8q=@iKvLM?4qv3uShGq4>)kJwIM-s9LL*aF_zf0tLM z?{l2L4ZW=Y7+dE5``G?Du)W3qO<-r}|Nj+Oi~nx}`|_{AlB-mzcsmy-)Htbg`t5~4XNjhl(f!C7Y3%;W9zDfKxWkC3>7q=ey(Zosm))v9f6x=V zFwK_CRDsR=-EL!AdO^9FFEKe%cz0yQJS4+-wMU#0I|4YFcO(J{hx#h1u2h0=n>|nm zWTjeoyVm}XV|b+G7jPi;f(ke?#+UWo1#2GcIA(6C00Trf9TR?sUW#0s`S73&E1@rQ zauhMkUqZUwvAN>TAH0oXXvbJeuSChwOj1ZQppmPd-(g-eu+GM;BqWQAy7xf#b!AXU zjmroEEGfpu<9UpWpc7&;$=Tik)T!7>*;1u)l0MaH?{LOnO*`QY&xx75jPOgUy?b3h zEBfdi09wOWhLY~5=b1}J(U0)sgEf-b2qC0q>GGLQ+KHZv3zsZ!<%j3_3=dvFUSyAz zTizgk=UX{z?iDDoglPW}qeS^zzWo;9{i8YNzO`NA0|y5e21j%O_jUmnbO9%H0oRuU zHvxB{Bk1Yr>mKXw>m%sy9vLPmMm$eQ(nv1JNk~np!q-+xOiQt;#2g&%-cQWY%~#LN zOidUb?%v7Fi60#%I8s*3RLRfzlsE$+S+2p%RP+4Q9{AK!_QCnnVG0`Qw>IO04@uAj zfLr-dDOtLYSn(ODX+y)^Ar|1op#151k$`YBBC0UAM>q2#H!9I0x9nChPO4ymDMm&H zCf^Ul0VKoJ0_oVp$bR9DRt95=@N1_UHHi;+EK&zX<%**ub;+KXilH1?O>C2^uuWq5ulyeB_24ECCZwwZx}kW(Jx%zA?g zZY8dgCXr7Owj{|l=nKUzZi!f=J=)>jhU4T~E3?0$r2p=KSO>vE&n4=q~dF5M_#Bi>p$|#lY%~g0$9e8uc(`q3=^%O#V*M4gQ zZ*)s@t)Ec6Y~5Qp&hCpuW$?YW?kA!Z)8R8x{A1zI_ZZ_{R_(_QIn0quB3oJg@X_em zjzi>r_GG)$xAV0aEE{GI)=cFWfaNwwUiNsxB!~P3JPybtcjiZ)mRk8NZlw5`lnY+r zx0qF;*UKIZsrN~)nwh1J)0;Ca-g-?+BgPOAW0=JKgtCpGMZd>2={k);E21Bgk&Zfr zbky_=UvlsJ7=q&9>g4;GXpl@2<11%%S>D%kMHC+!r6AG{b!UmTk&J~Y{?Q7V|FVk< z1I9AvkJlyV-{Sdy>TG3fO-+BhoBk7&&r%)(R#X(;gk+hWwwsTipuKjJc?igA1TBIB z%Un?7^N9*+hA!sQM=`~tbx`?&s&ip9!z8A0k?@0B<}^?QRX%-LOY^wk?1Ib$DWxSDT^OukVsJ=O$X%WF(Cq1SK?J}lBwpM`*Nnxf_i{$tw z4}j>Ja9AwZPcljyJQstr8|sQ37c_S<6=dzIEaD}%Wpmir%S`M^IpvYW0ESDfST!<35 zghZ1fIJ{2Mrf*i3%?gR4tDC)RGbVp$_2dD}j*Qkt>~Rm`9rPVB#D4yIO;+hy1C_b{ zd;R^IlfiM~F{2D0ULTku3l)VG!UwWA{DMeT`0e8kZ?uw8R7ejgf@doyDYSA0h*D3)7-C zD0PO+K(qA`lgj03CbSde?ztA^o{|Hg(?f}a%0Ng!ycPICx+lR^yBir~5|X2Q>obn} z6^;^gwzyCa=N@W@ZwN|9XrZq?7`-5Jtm;*r?~g=;RnsX^P|t5HOl_qzW?08Vo=uh=h8D7x^nza|(UzFhc)1~acJ{ZxXL&w@v1g0#j(5Gq+x z9*f6T-;r=s`P$3}dm3D09wv8y9Awwa0<-;*IbnO*U!j>!VYPq2z`*}J;+zu$_bibh zCRLPr^pxRP6gcC=ESeC!th7bMS!VR~b^fMCLqt3$pcv)8sc z-Jm<$0J-7r4lTb1AFC0syqhX`lyo1xQc}D0HEgyl`jJE#^>+nt&IzKXjd78MLEGzV z3-RMKD)ezRh4449+squAOJ-AW-WF$4Z3{q#6 z9D<>(x>C?&mv}9=nFb9uNixf~f^sJu?M*+Stl>jIR`}r7Oeer(Ss*MZN(t4!7|S22x6EUK#wJ^spo+?|B&OV%`6#PLcCID2 zPNI`n(Fe7^>Ja*9zAe(lEdLMRE-A5l866-` z+U1WN3(~(AX$RwfOXi$xRh4A^ZOs^~_1k44XiCl6Dt$%satRvT998?N0#bvNJ){zHh4#uuB0YPblBkh zC>$K@f*M>EMVFe$_yANH29=X2Ir%l{s-o?~ZxGzVCBkNf8!dW2N=$hWp3?E%c#ku( z1{n-6Hg7?Tl9mmJFTHiew6=?kD842phuCi>%%oj`5C!c@*uZ-tLV~wr56022Qn_Iu zv$OZ`1|+=Y3T5jb-nRi38j`*m7mDQK)ulB8#A?~f=pVb-8RD*Fxfi>DM#8gp$i?TS zE*NAN;Mv<>FtFsFTUd7J?SkNLRV&sTytmj!E_gVlHezpyDfEzxM~f+_5#`}Lqzv{y zIhJ_xS0)@tL}*>=P1R&GzNM-dq#I7gjcXFh9{o6gftG(yoQywU`1RK;JX*c_ zQ3ZIH{@KKp@^4`h);Bc!-vVW0{8iM($>G0{#wewK8nw0+Pt?KJMcFph1eKnX2XOemZtM+EU^dm&DduK*T!nGpbEyrl!ro~h8!rAOTelRvla zWo?*i=_A66K&u=Oaj~$oLhVIpAC60>L4(~iOBmg_!?+7s8fVbe97w;*?|*%p!tOh0 zm_Aq;$&=V9BsO(BxO3#q>m*2+l;H%dZiY zi@o`J6Epxgh8xcVt}-ba2VJ3^ZHNfTLT~wrn9-t)*C`V8MXzAJUG(-B>xuq8bksVG zD~gbq9oW0L;Gl3XmZ$=@*pf--{0}PDm%9tnjyqPgNz-}$VvU~HznHzs_yc6Mz&t4W zV~n7QxK?SN_l0;4T|B z^^Qps_WEUKGt(#LCqLaSb!UC;H<&M7O^xk@i>)R`F(7MN8Ar!Gy39Bb71MvIv* z$$k|yWJi@mXT@ILrRc1)nQF_T_Ol)k_NAPbb}pq#*uxb5ypr9bn0`^lR{Eh~IYr7g z&>=yeN43RtgB|u5Yit&+{#a;^$$E~0ohz;Cjw8_A_}*Sc=BWn0Z(lCanoCQp)m>iW zjbCn^EI5rAca?P-!)o>zQh4#y&sNP^AH_&{APN^%T!*%>?TIec&u~5KybfhuQ!k>U zg%}r;?FXkt8cq@)E*a%Fws&zEQ;}*~HDjF2dMi}#0EMhm&l~zSR>9on@=WMChJNT- z#G>3Us2k~{Kp&nYEKEAwuNIOkl0PvoPxSrz^H0~}wEWQhi*a_Uqr1k z(U4*?E4SJDEc8)MHZ@A@i@Qqr>1#iOK$&d08f%9C*~A{Ig|^ikN4itiK+|Pf^7k^$ z9|&KR%U1E?SV!l)VoHKiFj7EPyN;zo@cL()NPCy(v`_WJsJtpnr2qxi(la-e?b6wY zv(n+svgN%A#k4*)@DII~2s|6Yvd_Bk^md+~(FZ*nF`Is<%Fe@Y;%{HHQ@geGhM1>6V;CKz(7)?kA*Yw}shE*y$>1?1U`-0+PoBpS0c-Oo2ps zDv}Y%!)T~&PlV{KeXZ4kbU(8qp7}&s6}JRpPAt-LGK5FT=`LMsWO}Nezb&ZgGa`fI zn-so0LXKgn&x1YzOOl_2;}blC3m^l>BS<13Z3lSW8?y(;^HC^`?krZ%&gO4u z#YWwhFctW0ngah5wDSLAD&b@+1{_NK*EQ{>I7atL5QUdDwHVf5n41Q5n%Nrb4s zpN1G_>`a{yuf$qfg=Y1FL@nf>)h`~lnvNhsJ6Y**(OSKhveXXofmOyG<0r$Jet%y{ zh&?C1pciy)P0pSY%#Co^f&86a#O){kXhaZi~&Q)$Q8QHG^CB0fjfZ3T|rW zU1bW{8oH!m5mWmk|7i^L3M?-Y?67?~tj<1bnlpoyIxk8kCF$nzSly?GhN^Vo9)_Rd zlb$&C1DNh?g=ktMk`!ln@TjG)ZIV_bvl{^Gj%AfYK!UEa4XM;QMrl@!rD>d5F)L;` z|B~L3mo4IaP?GjYM8%Ve*op|Z8}WCZ{Jy4KP*IfOQ_|{)hnw0{l!$f=5-OOaI1l-&!|Yh@A047Ys#GHU z`q?ygk{Uu&Aze~WILotZ%v_f}a5h`KlN-6OPcaQ2>o{*Z|1zVh#W4^64PO6wCh?oo z`ft$eAMSkrd=Uq!SOS%1D7-L14U9^V@L(dJs2a8r!^2~Uh6x@>djUaI_;VNpa&7vv zfk0n^hu%0$A7B2_>kGEN$Rgo{G{?&g-b23G^|wbL9PLZAIOm!;=ztY7=bFD&>Te32 zO|79?Q@kY#zK$^|FktXb>0R+67bxAvX$|M0JL@?p&rRn~t2Y*n9ChVgmYQF9sT$Ap zd3~%)?fR@X6)^3z9zPq@6!La}MWNahP zh0Rc78TLikJOFtKb>r>@r;%^*fcRE7O}!*>iJShuastO~6~*avUdtln86T6WU1a^I z5TeqUAP(0>GI`r^oxI#9!R-`iJi8SMFr|`Ul1zRpid(TmAlbxf^Lakiv}1xJksI1* zSLM7xA3`g7h{1j02cQ!uzr4|Tgma1>K+V;>fs6Ph`&A14o7PHEKOI>UL8Z{WF;+t1 z=v-M&g=->A$>9-yLIfX;Arav?0V(A&j1gRxWuHs$-Uw)yy#Rhdcz=UURnZ)s1#CGc_5C3Ex6@jyIUP_H)zRJ_c9Zb-~&3a@Bdv$2S=6Ax;hTKvD7y_J?+fv5(XkdCA3B$u9T` z9iW4M^&8S4ekH%>EH{7Thy1H&4eaDmWCjMG7?b@ z#32z03MD!7k45KR=X5h_7b|s3s8^~g{zUdi5H|$~hC86pwR@OTz&#I3&v)DR)gB)} zJNAC;{y20Uyw2PO3(*n=gM|TLqe<1!R^-_jLPSS>Gh5;4M#c{LVf5qTu2_9X%r$S? z(bfd1=ukM=q4{f%-Tdpf`;G7MewcS}A<3aZ!&2)mg5#0Uo$!(qB(caz^=H2xlrSE-U_~e}7DaJF zbTLjG&BF=HGWYTGl|PeCb{kSDeeIrpDLi=qPugd`H&!k?M&PG)!PCIF;%h{ zw17y~P!*Xr+F$K8^J(x`!LVzDER$0*;Sws>Y!1_T7hw+d9ucRoEi-#;2Yo@XHAi4W ziG>2c&kt+7teB%M#Tl-AErsHa8GgP;xVICy1@8H-eIuS?jXXdk<%M2(DraFDl5x4u z3uwnX^5QwPcZLI0`#;=5JtFG;W>X29IzhAND7O}DU0d~+Wmf4tZQmHt`4Gl z-&g-|C{{KHFV12y$36_J-3zW1HWz8NAxj7$ts!UzuNkHz2!FlhP3iP2H2V6aCR@xZ zdA3}C_%rJ=a{%VL^}8tjSQgTyb1B!iFFe2Y_!FI1oOVthPlaA&T?!f?u5VA5HhEgj z*a5E@>eYA8uiCS0j)u+ESR13A?tUklKB^TI#hFlEU_jjH)FRX zgs~R6k$Tvy6KXY5W37Qx8G_pFqKaXdF8UV~J}q;0jZVyK55lYxJ89Lh3Bd~05uq-S z$HmCitOvPNEIu>W$wb5Qwrvf1LwOWxS)UsBcI?3AucOckP-rLH1bZB3kqhs|uN|3= z)WKOepRC#%kTG<`9*KGP3&gMJaL#Ic11 z$E`4jCai?U$5LwRp4)0RRPU*qZKyo%AbHRIajHM$;M&*fYD#lF6Vt?wz&KJiv)v=k zOm8eEZoea>`%|f2z3IdK=PM+tJnpxzgy6(dP;x_u9VovoyF{tETUC#E)|R67C#ls! zU*%@5d!8GrNtRoU@30D>3boCq1J`z4+QcOrt9sHq>qx`99`0^0>CL%85A^#W1b4T< zYq*|1tQjT2APMe?IqL-~1yGaEGV>;k`LONEXm@MAq-5zFdl_D+X8@D@W9vT2{9=t36eZ;G0-m_0t!iaO(Eg33EZTdZRzvZu9gE~= zRGf$68%(pc|R}Kw?97Q0fA#22fUjY0HfiKm!E)6%_Ek%l$ zo@-WbV0m_BZD(*VxGjcO6#N#Jnh&*WBXA9HP#}skoSsU-zWKIT@3m-3HEt5xjJ&xi z_D(Vq8D9*AFqJ0p9bw`=nEFJ}gCn2Isz2+A&iL&8c$_TXj=m|v4P5_b_ z0y}r~hqc`f*q7r@k|sd~PhwoI>H?JtJNQWNP`p8^weYo|)|$;@hZMh$9G1RVAJWVh z0?5=2Py)<7vmwFI*w{Ql5H}{=*xd9r_7ejn2v4PdjI$)7)X3O6L$qf;y`aQ^$}J?^ z(D&CqcHAub$!^MRYk_0xvnjA(j-J0GeUaql&8FI95Ski z@e1CqS3!3wYwC{q&!5H%U&eRROunEkJwuMwNU+BGE`Gp3KMIPnqKLjjTXGiR8=TbA z)kE78Qy)<<`_@xrMEKmkv#f+tOT7vDCe8Jc=mZ>{U4T+hQ>~5&)d7ONWN{w>j`gdG zhLBWQ+#8e&zZ#zjb)4nC7(Ek4nuwr@bpy8DnuC+dzWGq{)$H7GOrUEiOX-ka;PGc+ zo?b=m>am$fxcKIzJ7l38*)s|Sy8W9b;LYXKwN5ygTeLFaB)gLw1 zkazGJYL=AaatO8pj9Mu7E-FwUX47|4+XB~XH%xS+d7p*ffH7`A0e33Vu@}u{8lM?r zUFy^NJLsi$H`4-qs@}8%z>mn!KN>q=F5*d5c=GYxuN@DK_xM4&QUz@;l36h zydQl&Dn{b|U<4YRDBe}X>XBsvWV`vG+ef=>Yt~tCIkGAuf9$JLW0&@9yjZz#)DS_9 zRASrL2b?*}Y055t))L5>o}s|C(9M28zd%~io1@f4{P^Y@C!!`l7L6VVY9Df1PFpfX zVq2EEX%JHd*=R$T%=*NCCj=J@ZOFxCJgucBXhZrYLvV$&quyyO9=TTxbTAj`HfA(M#{=mv!PGJ~WH#wasT z9EU-nY$oPa94JP-pGxoMr)uxqjUsv(q!CAl&;;ED=s~Z9nXQqpz<};bi{MU@4?bvc zriO=Fj#h-UQ-H8x?kOnGOUP0Sm5{0UP~muYC^XyPw+WTlaNUQx>Aku z&cPZBXocJbapsPEau(FFmk^C$t~HOou^^FPZ zZq3N!=N6fw`B_1Y&tYEl0D}LW;8zUtHQqKWkH8j;SqfyWdsIs`Oo&~D0caZu1Rghd(r*f9i?M~;Bfhm=(8 z0@GN*QPp5bS`|zlvh)PzNw`wGYe_lj+j+AEQh&9Z@3t?~3-t>VuRqUk0r8ji1RjPf zkG!!Crq6rJNrK&JGMu$EkJQMhn(|P5iKw@!lt=TQ>xn2NGk=*F@RT4jT?rmR4y62G zMRwfiH9eQ=0cXM@#cfi4Ox&QCT!!w4Wb;CvPZS(7IyK6ZRB1JJe&B=nB^kT##)~Te zr9+`20 zw9pyUMS=|7)CDTOXPWN~J%p`V)MjA?YMmt_^8xMR-Jd&#F$#bajG-4?KO!hEu;t5q~(`-#PRQJplwIdkx_2IDUU|goyBA0%vDTXK3c~&cv0pqex zT1Q6Enee^2)>J7H_~yxCH%Y&Q)iO!c=n8Dp5s@OsXfngJAEh|XAjlz} z(i73&p|-xT)(Wtr<8zhdM`sq}=@%#&K1(Cj!^CrJa^TbtTcsvN)n?=9kQT>q>1p{^c{5?v^fUVe+w$D78&(`z~&&dfYc+=Nxt+C2G4EFy|tDj+>0AG!dl$msy6Q z`$-?mi0&Gjv3=_!r3Pb@h}O|D#T7iCa0#(;OxZX=wl6zYe_3UNXSLIs$3wd>|3?&X zkR1dxW;`8Cp-Df=uP(%a!T0DEGP=F2!B+M*Xqd2aorzxEX^r(t%i%Y*mYqi6F)S~ z=N#@xaa&|v%{HOo`}z3mz%fGQmt}ym*UY;aMkML^c1TP}r~9Lh==0s{DBXfM59IaO zN2K#d?T&>x(%V;o#qw)a3^?#2^V2MjIWt@g?Z3vb<6jI64Xm#lIDhZ>5|PpVqWN}^ zrBUIH=A&Cx<$Gb)_0hZkxI~(KCXiZ{jZ79(#8C;;?&O0kU0OI6kq30G-uUzW>+>l{ zMVEW4c4xa*)tme4-0evh0>92?=PP1^_DhZD_m1~8p`-Pm-p-u|OAu&Z2G4ikaPGEW zcjVT*TiQE6>pC{>ul9o5q6xg?u`M{A3a~S;q004v@tDfmvd%hk*}={P#VEXx_D>uX z@mM>-aUkNccQ?8Oo=qllHH`pH-m;U5Yac~V&6&q)?^8HE;m8I=FB{j6g6XDGG=RL~ z92){&4RFte=^A9GKsj#g#=s03$Xr5vOec#~GdyRZIXAd@%P zorFT-qt^C#h?lgz$)H_+TSqQ0p56@Za-$G%#{Oeive?gIA6nI#9JsT#cTcXQ1l&2h zZ=o+2>sL)BCb&$oK|vXh9w*%KPRq{LpZZ$XmbeumVV zN91}Si_ihqy)|*(gG~+L7hoUy>D3kC(70c}tpbllCw}qm7Iyf9ZQzwd0QwZ>)w(u~ zR0Wh-kZ(uJFCx?v8cM#4*f#mO+lRWV<rz$cv9)RzR!LXGz_>d zST-c@uQX!8SY}4foD{chaFTVwM&fM#j+RGQ^^WDo(nE19EknMZPa-0pkWT}6k-_jF z(_!W_>~-u)us$)%kXo3ox`NglTu7T~ZJ;GoBv_gWr_2(!oU=~L!2u-`R*#;bf>(@~ zYrQ_$8WH1~{zw~Zg*EcR7cm&gCZ@yTTdl6Hp+40KF8-a~#&$bypMNtK#$5A5sQXnl z{a*FOwr$}k-v(*V<|{fK#fv4%oVif$0(jk^5H??6saPD>qh@)r0|@jd67GOEa9c`L zk%O}oT~ovdGp6nGb;3``d1aSRb#m@>bX79Jx#$Q`0@nq2 zUEIT?E-vg5tvhhOhcVYgiRtcYAM8NT*x=|(9?bd4VJYOM9M%L92@Us~BFs$i!|hu% z*Gq0jq1R)f4f}Am6`UBe z#yj*756`MELhb?C;>{J`XLtD|$|_9LgXQDCBT&&@`8`bT@844p-4H{5((ZbB;F)$^ ze`HI{WfW5>H4&a;;>27I5=a(?yr1fHc*1LWw(xpIQYj|Y?VZlq!F$VMMoEa8 zm)o@Kl@56E`-buGF7X}onykJuDZK>2Khq4hAAnQ28jWBl0F_JQG-P}CXfC;PdU2!u z3;q2s;IN@#C)gVP!-p@x&6@u++wMOhVLP{fPip^RZp!qZU;GoE`MtMw9*8T}!vJVS zllL7_MRSSH#Gs!b83mD$khF!8S-0&q!I=uV>&?G?dQlbl*H&^`DIP{z*944He(dszA;M^IGe0RelhS7 zA7Q|&rD8=x-6iJJB3DQ@tRn$ta8pw9Z3XJ6k$fN{VUl>tcqB=(0CYm-xS$9dQMlTyd6$_e=}sMkto_hQH#*OgyOK^1;UV4JKLOC5hKC2FyI+u+(W_E zpspdCg=uyKLygCo60xcA>CXB3R1BXboJ1?E*3>k+Ze%PWu}53*m}u_j8X53+v-Tv4 zFLY-K`Wfba;PN{G2~s|G|Ie%!wr2H zUIyw&ja3P5HYRpl}>|GD< zD#G)lJldGNCOvAJRvTR!**L3TKz~gYXF))9x3ma*YxB{@if6J&EePGF6E*CAj>&T4 zeoQveFP3jKQybm;mQnt_=ZlYMqjU5Xx5Xi3mn+moLS-yOc3jqwlWwcXDAHqY>l5t4 z0eockEBQO&BhrP=Uj$vNF4Bn>V19J`F+a%uHf#9bc0DRM7#kUz0J(TZ{~A3g{)4UC zR}K^-dFJKQg?>b1qtg~#i%m5P#bP9(`)1rt*eEPz*x>nBzd>zNtFk>0d(U~M5WlB*&~!Y~o409hDqA!cupL&SE={LwXp z&5yNXEZM0fxn@lp9ypmPhNPd7mNV+0$CO_+Ivhl`oC=9oRD%P2b_=hiThyLBuu-Cy zXtcOyx%ERAOymlyR{RM!x4I117n0`UbT;?1RVKH)ci2oN*eUvgd~BibgEQDlZ;OBG zNN0}vinx8Zg~w##pU_F?)pr9dn}ZinYL8ey-k?^vl?_4ylr&^Q z(gqNUL_ib!p%#Yjhwcx9i)RopiFvY+LRsm#kx?iK5J<`)bX9f`Fb3brWWFUbuz7Kl zk9&Wkk&*s5Gm23=lX(+qmt_T6p7Z-CWBRi}sRKjB`NvTG-3IqRadv$p;96csTPv5p zG!6d(-K~Mg7s0!!sIv)YMXmG!42IyN`zC)PT}tR zbLd$nMABAdI}6vH&WkxI^TRET7`4eQ{VvuGzR4}>8i1)6t6O8G5FLwsdj{S31z*DR z^68zq>@thrCYB}voH|ascY)G6%M5I`@i+3t_=S%SN^Vl`9JZrfsSIp!v9YPxPR4tpcYXcMG(PjVtn?D!}E-aVl+H8 z>hnCu=iUJcnb<&NXmFu?^f_jh46@GkglxS0H)NlW>JkzJcrPukKreL5wJ1h?L;SL* zPWRU~s@q>nhx8xXTBhtXcHxFV#3Od;hIpfkxP~~RF#IQf z^7REwF2-@7g`pLvSbY;``ZbkYtpsq)YJUGoUjZ!eR5BIDnwa}QW_>j*{dO>KEZ`UR zB2L+t<3RPrGe~ub`@-QsG{BtJ=&-smf0B)SVfy@p;-*-8lT+H7N|M3ZI<&CtUh7-; zbjvK@^9!TAOOdirc?{^LnZyd8pX-OaQEh1^EgS)Ltm-GG&+;&rA_wf!397VTF&}ED zi~bj3-xME-w(LEbOl)I*dH8GxL-JZC1?O0HMkkn0_O@lL>1b}C_ zfaj)rkaJH0h)u)ib&VA>Tq9j2;Ka)b;YevXo`NpNM{!L!xQ6TwESoZKWabcENW1ddM|m1_O})u^*K@3Z^Vd5iI;PJpxeMS22QC9 z14pNA?5is8q1`>oqW{$wYObQkJH@v;pL04-WTj?kcXz*kniBBW58MF?9;zLzosiur zNtW%Kr)Sy6qSdgM>8bE1Q_!HyJJy46)?6Fv2%q<8_5}&jt9%R6?Ob4s_atjz)U@5q zF74r}jBZBXCxJoi9SnF27|{<3T)BE5qsbGYFb#1tBALpCIAaJp%A78`rDs!wuCJUb zd964o-xYaEV@p&@S!s&EE%u#@%ViU-utzr(&AID)5!fmip(WZ;fja~{_9S)`i)Jhe z$lA>m&X>+BpfI5HZx#!9^$l@cfRQBEOD!Nr=%lJGXQQ9JQ=D5>tfeph@x|Y1F)pP$ zuRm9euM`l?KX6{-nlPYdNk<7cO31);N_Mp-ZHW)uo}Ahf6pIJcLE~5i%hrF7_p9iNqku&J*s)$6=fGzdPZg>>$0ym*VYff1!ehe^z=pe2(zXonlg8!pM6a_$4MsAS* zqJQ9;;T~zIfy=vUP>Q8tX7MIq+WmU?nE{SQrmH_udUIfsG?9ACV+3x8X~6VH59Wa2 z8oF%-qOfn>?9xk^-Oksw8;F~GQZ$=`-<}x_(^?*TAIItXA2Z6IrqKUceg3V2``efs z-gA{$QawNNS2-GQK`^5qZ*>@&EPUR#U%=7n4xzp&R3k-8-i0~H$S*)|q_&74jmR9Q zr(@~NH$nGrSJ$9AVA#N;pi0mcm@4Kx{P_?;Zb&F|k|`++wE?Jrr1cvE%r=il8OPqM_-`}tGD@vgC19xUO309RHl8=|IqpP>w;AimckB)m8Irs60rj7SWoH+jPv zFH-F==uFd+)xA)OwswPjp(0;byvSP8ALSF1+mLC{Kpr` zMp-J^(bSy%jyyXC-z(@>+rb1yaNVoltbbe@(@Fe~e|zlX^S{63{&#iG4}nfixoNWx z;=jj*@~$r2*SR}2^oSQg*SB3>$@#&tTED#nr~G%W5lcSwFLCVaBW$cN&hRk_3y-oWY(BR;8*5hH;#;kSfy^!Ydv>8xMtdh^{_ zIbWBwK=p7WgI!Le4w80wK~Upk3WcH9lUB=JsdwyuQ4=NHHdg1Dh=jC6TiIpY()>(> z73x{E8~a`L8;17ABNILVBC>l;qR*#?)c6-`bff)oU48!#S}RbiO6S!w)r(@sah@Tv z91p?{7cZM*%jNGeb=Y6tO~X6Y`~(L?4-|rCi%<_Ia~_vA-?(_ahtWNi-D$T~w{L z%16(>{$O*kTMt_}$npz+%T_+@q_|(zzaO8ic>#ygZ3$lgOP%xmcMB~J*OmEyt8yct3Jo}y z)wWu5zoaO$+$%YV;1#dOU4H+v5PStOZ9J#_L~7>PZ;Ui1Dy4*aN-eFq?nIvl5bF{X zJ?YZ0UJoxsl)q>MYf2w2e^Z^uV2wg4^CQeXUlry??oVXj4VCji^dE_C#b@S6^R9V* z6bRPnk4|taobF!8rWY)-?XhhMk%aqdLSYf|*IdcRDh=^d==av3xe9XDbi2&m+;;8L zfjTyFi;Ye_{1dbq$zoW9i(Mhot+>Iq8}2No0?%IGvD5arta(-NEbYX$D&N~Rgq-^W(9qC^-_X4Q&c$mzVMos^2y0lDk*PFJKJ=< zhrAp^Jzcc8Xr-*xnV{J_dk%F)8&zq%<$Sx)KCAKerb7#PWLGJYC*`m7JJRtXU? zADy43-^WE9&BM1++I`M); zMLKDENy<_KPfQcMFuA{?3Wmiue)#GIYVf08(tJ+-BkNz zDJc~2Q%vgM3x=4A>IW4FWZ7RiWso!C0VxhnE!?lKpspvgZ@$`U^@kZ#?K`;rU2sE* z1@zfq31tL9s>d%y0?@mWR+Q)xCFidSui8^?dlT;@^;i$_%Nj2{k2r~gZ3=;t`Et; zSJ)nZ^WIt;ovqqH0H%^neGnoh6S~z>L*-XhX1n#_ph)@!mb4}My7B2gV&SjOtamkl z>0Zky&$X>Xz2w|=jEo=mGm!;2t^Dr_Ag0#cDSAtqCK;ggap$z60G0UC`BcNFR9J~~ z6WiLmj!A-_!KK-4URcHF-!h754EY90x+8h91j(D*wp=m_XQCC()jx-QxTDfzIfgaB z%MGcybb`Pj)ir-QLRrN#{$*MMxW(XqboHn}UjWNhs;;Z8Cbk~?j(*JWMmvOo8OR3B z{%#S_>xFF`&-064z5zd;@JU1)W;?*ou|moKwQx7B@~KasG!nVctN<_)ZX$;D{%lNd zkxCtSB=_Qqt_^COi!&_4gm zpX&@u;ra%q*ge$zr;L{|>dWVa;tV$)wLSnuAzk)A$~W z0!g0G?KJyK5XEBms2@a1+>>;x1<|Bcn_IsXyx`ckl%_vUNAo|D5dXwmO6cl+*nJuN z>jfP%Ee4=P50qYTbgXZylv@A7d)a3hHef!G?yEci@pYqE6Dzegu>qSa$&dfC5A0Ic zZZ+B6FvXj>GLm0 zTzg&HS4+4CYJ)rSL6`%k{V4a(_o)(kVHHjXX}aB=A@4*mifr+}{u;KKke(dP=w-eU1BPB`kes`@_$%s8`M=)pOQGX5gtz%H3-8Qt0ihxEpz2pL zq(Pn5G-3&P85-Utj|zu#Sg6D5b!TMCtm9Aom_}YHgsHgbsu3mP;coh*Q#+{}CEAk; z!n1sth_mL28%_sUI?ma$9tAL-(F-#iFFBq_<>E5#2!+`LD{yW+2WpJv>i~?f^?VFb z8d6TGSWHH!63^emYy9IfY2J@0fc)bf{gY)RYGvwRs%v5D_8%OUf76J6JED6{1SL3T z>dWt=AY@LhA2MB7rNJZOk9>g<0t{8*G!~erpGypA6NUPS={=lA({~krt*cEXx&yR! zez@^n6i!zEFXB^i9=RGRx$(+Fl^P3Md(#Tk=2l+ z^9?5_rpxCO6cWTrWkIJlX$e$qUHfob_zpv)u~-xE6%%@wagARK;5kMo@N11C2&A*t z(u)>m$l)i(*Ktd$P&KQE(A*N{m87GTL9?O8fU{PVK1XdP57)`Arh#Y0RRoT24=*e% zy4QddYJ1_Os4I?34;=9HW^?*!KdwVBtnq8C;NXoxOxbkFVEMzejfXia$owPMJ{5aQ z-Snu}RO9-hlU8eXtE3XCPorHw^;aQUi;k1@<7bhAM%*rFYqvYMpjX6tBzu$j?1Bsc zPLFQI^Siy`qT?|bnxFa2N>o4{XHUMM7FWd{V9efUVq;2--jda!EvRx*hn2i`Z`GlE z&VUs5c>|>*1TEr!ALK3?h&fkda)oay6=z=bgl4O%QD0NBY7*2@%Zs)y8#g>Lq(2_& zvSA=#XTRKFM%2S(AP>4m?;oK%-Fk0P5Dlk8L>~85NJ`?v^DHVbGl`C)3RkV8o}%JC zj>lb1Jh_D`ZOA$zYZcdL)n@Ul>-u#{fmnFKg4>ILA1u@SLFFm9x4nbEy7aKZo&6c} zf)C&=RxJ9&Mu&LiL34~>h+N2-N^^=AOLNXUiuXZEqp0(b zsMbHn4oy%i1*!uZW6SwOipNLECINv8a&2#QJe~(LTFMpm3 z@`-depBfi~i{6v*H2>h~&=nF}=8z0>y<&;JMtPS0Ike zp&(?ZN^Uagtz4j1(bOo4TOePx!l5E$r&=!9YYQaqB|%8@!*rzelP%1~@ld17aP)^a zVNW{92Js~|3e^LNuQ#fT$|nEkXebX!K?<#6JyxztJrJ%6J(R9oz3*BSmtH95m#)4v z;2xrTuEii+#d$EpM|AO=|uMa<}Oh*7*<-=IJ8L>2}5IEBu8E z|1zK_{ulU;94FJ3tZ(#!J_DC*wBA?D(6kUzEw;+J~&4JxZf7WJd1CBeP|UT8R!+Yg$GoBZ<7SILJMavQOhq5y`H@Sj?5wx$L{ww z|8UMTbx0~>WD}k`w0cp@ckkcUdMOsV7d1I370Nf>8p7oK`kps!PndJC!_DXh)QG@S zBKK;siXgpQYJdZg@s-iy)Y6i(UO^1L3ZR$ju zu*MK0BFBsdR&C_2h)cDl0X?Or5-~P9hvDEltKOY1k+4&OL<-~jWi0^gAS{nkO%vI0 z%w?f=&3>!16z@ia{G^|#-_bq@j+HF=piyqbvot`L2x{8sV>cQAXAr+JWt3Ev8f&L? z+9CRoW6lOVot9*^GC|I%TR`)B4tmIj;kS*nCfC3k*4Xa zEtNbZI;2v?{hEydwz_{IGWz!ng8fWY1`L|}i_5x+pl9f0(|s3%6n-q*CTbSuOFc2L znL4JXPA8zk!0WD?AE#kTkKf()s1yqP9cA{6Gdzh$~+`ZmZEx&!Hxq&rjwV4O?0=2KpzWC9Qo6%*`X{ZV$o*sZG* z(j`$8D6U`ahhs!kAU-B_uzB415?UwBBR9oa}(Gcz{rZ)T( zso8((EUn<2OOj}{1Sr{+P-&I*;jdckr2E%RVlmY%_TD<8h^9U zn{tQ}6LN8$EZBjszrOy3CsPXUE4Esh8`JxyFq3drXH^RsBKM4v0ReSGNW97L}DdE9gxJ@f5M{6BC4fV zFfNuwXJKAKWlz167TVZ5y`&aq4OrwanEsoVX$=b!URw83%556%kx=8>Y_n# zM?c@pZhH(u6yv+1Q|AtuaHK@HYtoSl1KV6}s(u5#t9iEKA$msQjOU1YMc?;>8q-VH z$$EuN%6RvM%EFQZ(Yx%(^rIKEg~YX^6jg#C&v=Ho#JogOs!aQR2Re;v2mED;`a-I>f0CUS6dqGwS3CQS!KV_GZjtr#~cLlxQ)#_Wj8kye~F zG#oUj^H2S3Xs)3iB7aeI=W5BcR|A)w zPA=EBAnDpZ<9_XQYy5~g#eR&^PJ9AuZA^X*6#E@4=uyV_z4ynWV#A6qD{yIwTZaVSsBkYt2s#yrH7%j-?boY=ue_T=S!Ka`28>^LDSLJv`E zuAnBolV6hZj?%1FgtdPp4dlYZa0jJodo6=Hnqh$L2e}8!&x|N2L3)mc-Rr5^h?hv; z@ujHwPi8rz)_O0fF}UL$2Wfo_64)*3X zBh=*&%XEPlyWwW{zI%406SbLdc!e+p~#2X@E0*6}reQ z6%l2Zhp2OiwaRDWn6@4>FC}?L@09~aOW5WCe97qfE(m4q z{x?&NAzou(N!E<%ujrrWp;^}w0kyV_N0RgEw?%1G@h(pgnvqfOznXexL^-HXOTFVj z`D24j8~Q(s6Hjr6f_?VI3L?=R!WY+zEsX6E#F_$*-Cv5Ik~$pE7T94?Iv!{3AA|>I z6iSlZ#_?2zonYZA1k2*NN4Im^L~O(Dl{Yw|@?8;)IHqD%sTDDYCD3nq$A_)E6o9pg zytl!pP*F%m+Y`>%4w)bj^CGO?9FtQSfs9pzej;C(R<4s+1W}@u&{vkQ&g{9N+1|&Z zmv>S8POW>wq6F43YzN`lipFNFqb~s`u{pOh+u|$#*pb&u!RfD3U<+Bjc@t*uZe3uC z8Jn`f=xZ#!WvG}kXw~G>i>yU?!LH}F^Cc@Da3pSm8;m(~(pqVKaDaKyrCc96rlXfTt1ZL|4Kj(2w{cs*_V-6oH)7kLI}8|> zdzIkl=*958uBSb~9pkvW#oAtl)D`sot6FyO;J+Ll?V)|k$U52}vCd8{yTY4Y(Yr~P zDs@vu%=x2ptecLd{EpqS_zHdjwI!mBuMcEe()NHJlosjO#1WKDFy|+!n0yAV z?ZSeM=H2_0%@&2}g%ui=e=b0}l30DD_EwC>QDu{F;U}FC-G)bnCsr;%EGG`wXB%dw>UI0@} zgYDN^OfiwAv?L-c(=oXwd)E!__qSinK3XrtfP~nXtJ(-@gSF(V#t3Tz4~iXj4DKH;q&p-S z+@lYIgVm58hx#o8m-GNq05*tM0P>fqQQFcRM1M$wjOOGXbq#klky0j?<7db|V1rw>>c zv>oN!P+49Ox!V*Sh1>39B@aNeVoKGoraBJLl{{4V*1MJwwM=ZS z;v{(KTj|#4_~}*|1;-dHC>k~oO}ARy=8zF~vc9d%+c6TvinvNZ3PLXuC^6JFt(BmK ztu&!wN)k&F6fq=vNP=|H`MGWt#_hJMY^F+BPZBy?h?S{s ztlY1oKb2XcRki9=GSPK1j;#!l#mWfUEf)Uv zPFZo^dz6>i;#&MzWJ02>BtE)WhSDS@N-~dzbVwjMT8$#5x}4}19DVF3Le{8YtfBJ3 zpvtvf#}vodS6iw@0tBmj61RyeK;8#Ch5bl4Ya?K5sW_z{ch$3PrPe)LQX;os_+dn? ztHw^rTRdYKvAWQPAkFH!EU4+lq3)-xuImfDVrHs1I=nJzF=y*g_APsvfq-td2Kpi? z;79CBxc8bC5@JG)Zl$BKzeq_WdQyrMQC^?wCi-qxv%Hi4q&OKH_nb76X z0!xTd`{k3Rs&;`he$(*p5Ox|a^&fbGAt@pfl0zr}c4S*q>NTlWsFY)&>@w^8+rv;Fr{nr7(4w@webn9KgD|=DUZAHa*In>VQIhi<- zSqE)FpZVW7Nk_-uur-Wag0@tHD4?mJE1{FKa`e!~$9?U)G&vf>!@cuXgGplOUDEh8 zPgZ&rF;@>=x%@W?)KvS3bq$qFw3Sxvv{p6io=l(}PdC>tz#TZue)0mS+3w&~k_hV- zn|MlrP6gyG5WPI~;O2{Rf8x-P|B9 zrd5D;ev_T|&hS^GK1M>{{!t6*&t>1AcuFHXYfHHgoy!kN*?)~HE#)r$0$_Qr##^~# zO`%qXK)$J5tzc5WYUt$&aVhfP{RO~UI*vWFvhO4~{1{e-O$)plZGQl;0*U=vM#k5= zQk$QRjJ#jpu3>o4@P_DzsG?S}oLSE8Ds0xO{qzTgA?&$0#-38_iXzl}*sQ*&@DTX$ zF!XSaF(d?1*(K7s=$;VqbDmy|0%H_1sYy2;H#%V`5vL^WJ2qfkloMVwQu9II=)T)v z9mrZ(N;%Xo+6yxQ*6Wv}E9b>9#i}ca*uI;-#VAxrdQo%Mx`*{TpU&IjHyWgz6bo4* z15^yz=@*czUftp7nk-|9euNsO#O23&jlySC28Olg_~{^u0I0ZodT1?ap65g9J?n+pBRkp+VZLNBtlpwnh;%&Pe@C6 z2i_l}zuh|>5RjkgOW?i+yT^M#{A*^Ii(=+KVhrMdqDn(E+yyC9@TpIho)YHe9dk^C3Z!WZD^h(iuu?|bg#Mf5TxD;d-oDxt zP;rG7g&y`)-r?Q7d#p$%co8{HhK4^|4>H;>SfAHiU#=Eux<8}$-Q#N@C?b4E$O?%T zqzqL-y$TJH7W5$A7DvzyeNgLX#>WYLQ0jM<4Y-m+mNkJ+&X2jYL&kO?3PYb@h@g*P zkPv~VCC!B~g#ber=$N+W7sls2@9YEv_rdMfI56!ec3?{t6>)(cY28+kX-|h5DW~| zjU?TjCTOrlLj@c0LE)5y+K^{@uAznqnulY&bEug4JbhLtB#;2bp>m8WpDF`uBVO&f z*`oL?TB@BoAAmZsc(eGChHfwEf>Ua-d6jAMu*bJ$_LCh;AmcdpR;M9*3u!xM&&{ah z&%ot^>oc;vEJ3bE!SVX_ktl~g6XEb^f46g6K1-!3SC3Qh_vPPtPwN+NpE-C`$${J7P zv{$JfyK+nLaOkfHEl{9AI|m^l#eg4M;2)~=Gu{7SYs#H@xzz$OmJHXIvMP59(3z_} z7>_$R#;ST^KRXWlWJ}NNM8QxM=bqM#&Y-!{W1C#>mMWK2ggr@xN!jMp2{z4o^*-r(Y#+P{#H4g%smEkZfNDyOpA8q>0kdXdm-kS8Z}kFSNth1@clKq^^Ngt>a# zRFbGuwURDBjNC^>BljIvw%C|G8MFrIMdIY29ZbU8T;ZEr?5sj>4sS{7gVevLhhqOM zPgZa=CjoJ2PE$~9CVTMnG4HK%om5)RuM=LsmQjB3r`=-<-_O{BwPTwcrhOTMRv5sG zVwh#ex-Eszzh}m#ewsTC%M43fVZ0%#pb4TnSozT5T~pMlTo!}0X%7* z+0`EJ;aFl3QcWNRO0>}vo^~kRuZX3`B0<~-qn0bkXbK{XTpGoo)}Rdm_t1}ruO)1( z70NWFKGf>ifAcCP63Q4ev)dtL+Hnb-MY#URMFF7P66nG4P{}# z!c?Z&X1_F$29Xkm^ZRc;K#r?3wDL#&8iM$z#+>Sp#{551)L)>$50VI0i3& ze{VTFWml!X(!p~|nIu^-;{|Mx!9?PN=jshGl*oNeC_a-ceL7!HnKRKB)}r~`1xc7A zhg-&V1N9<4zpP;bLYj11JDHKny3qP`bA#q%+2K@kx>@Lt0#=PRhgE?p>vMn)w>W@> z<5rTlyTyvi(b0nxt`X8GEL1Ko)D!;9%j<}P4)1a#s;s8(U-Ls|D!irU_C9SK^iM;^uVrx-qt zeNZ3|`4t^@K%d8+u0%j$>N>_+m6RkQJsmXMgAF<^1Er9>Q4WDsp%UW}s5s^-bmA(v zeo2(TgQhNF^xlYzmnC$Xf+{QgTRUxSkZ==!S$)@>scR5V+6@i+8#&;~vSfNwto&8s zyU4qHRtTr52%)RSHyRAPhVoSZU3VNo$~=K##7;2-`=%vTC#9}Uj34oHn2CF2N$Tm{ zy<8apSNqUyQ=vh9HYjA@6WNm=1w~TdS;MT%Z$3+%fbCD7Z*do<0P?&4f{gDg;t<7u z6r|99EJ%OKZ~rArE^p}IX!Ebg{$(C4g#4FzaJWY^(j1RP?70?Bz zN5r4sxk0?1-+{Wn1#<(`e6n`o_yBM9CUt`upgez^|pCgOYchAvb&la7AGmKCt_tK0TE@l_dg>NBV$y4f> zg&_j*#6sRX&z5x6#k1NQyr%Ceq`aNV$K5glJ*|Tz#Yqlyvf<;4#^pmD9P<>9KpNXN z(Wwp7VT$XCPw2yYTBmB{nITfA?B;b(29@A*SM80lirN#r$Wvyt#&l-%qs(bn>JS+L z7Q;N}Ji>Cjp6mRM5c`t*=xwGRXbBE>C53HSqneSd^3F6!uL-iW@ z9|Tga_H)%yMpm)C!{@ysyNuscxQ>%}nZhl2h{k(GY?P8PS?=wup#-oajns1_eL*f*9$S_!A-XK1t=fd=R^Q!=N z155jq=1uNHuR9k;oKUTeL@&pQqqK641^vNVW*0C!uD&Qmyh z3*`}Vt5W&quTWC0@raZvEaz}dWT_K-y_iQrdQ*x%5{F8nx?m6oLd8qZoJgpeAyz6& zRXO-wFtITrZL043hb@ko(6|{8GRcD$)jQ-b1RN#3 zwRCT2X{yWOI3)-h6x`-AC66_##^FvwOUBEnrwyp%k_*oKq?YEKNRSP>pX|9&j~iEF zaqjlfvLyu%@>g`bhe!eh(It!xKUeHK==?}T1U~CiJ~3X09ZIrVZv@VFe!tLdD{F2QAlR5YuGub4gICoklm+t-Z zbmEoel#{l5&@}f8|0XWxd(0K_s!-sGM~QtdYSziVRP@7He|+eCDQnP8(nEJGyj!Rb z#7unS)2HQ?Euia0B;W)yGY6j6szK1XtHcK%w=R4*nF>xoZZ-%4nrO7n+wbR|Wy*@- z(?_}V`p4%U-XG=iKT*$rl}qNo6RBg|Z=`(CPt#iQ#X!^sd^y2fL^VVbmweIbei4y0 zAF;5FNvmMT;qx&gr=*5Aa?zJCw6{;*30DFPqAh^)J(U7Q~V_R-MsF*-2CZH|Bt3XUO{W9z4UHT@LaBacH+ZvYw zhbeHGifre}FEmeV9PzOy&S22$$ye_P{O-h*8DcGowOBU7{S4xU_KUnZgAMZx!$jBOo z4YIynjMo;bTt}d&3{gM2Tf5b=XaqPMD@~Fc~B<${`c7&j^kR9%SL_ z1Ou#em&_up9M1ANXUPD~9l9S~&Lxj#7v_D47NncNw+hySsL_0FC$+pmHz&-Xwv=^M z7@@gy>dJjPMe4Xi8}p_dP{C6tfMdd6?TcK_f?1KrySR7a0-SgTdfSG2B^sfuJ^t-P z937Y4{ZUSf{?Bsy|Mg;{qot+3yTDmD9E4S zUm*CVB^L^lA3v~z?I_&31^c-ny{t_~Pr9~h-8q@z?ceu*Dwu^g9n~ zR%{k_ky{Ot$_N3mkckrcV59fHOpb4dGpCobZByZ-1A^jio>k5~g=+Y5Y@rgBA5!+C`IM{f6{Ons1am59?pBY@M?IFD%>4$_GklR`gUsg8sKN8^L`- zHmoY8*;;FL^e$VNYJyef5*457fUk%(>r+5xgZ0fZ^2cZkXOndb$d6M*f|q#Ez#a4Q z{6{+bJj;4@1%Kv=QUmv7LfhrLDTtTzo~Qe1mn&uxt}YPtea%nUq3CDazgnL}{06rF z8!0x{xydNHH{1&?Q_NGRr|3RLMl>gwq%EG_FHfXA$_TrlfTD9rW z^w=Ra2qFQ9=*Xx*3AcKKXZNNkah%fY+rPXj>g5o|ky;afz;-E#!V(njb&ZVo*?+Fl ze;`61A$2fm7$rcfG zBB`r>J!c&S#3M0@yqn~{RZRkb(qn`^N)^be$jh+l`r3__o|WG@aY`CW8q z-GvaptZvhP!+brqC~XXC#Y-43e65|numPC1%7ukzrlg1Ag6{=x<-KNo#QM2` zwqG80KwS0Ni)O|w-kv};X5#OP$$Aif^s%hqK zg^q0~I#+sE|IvgiF02s0^w5~_SY*MrJ?fmHzgc=(eW=o2fGe;-Bt9Y*bR!mPU`PC1 zmAoR;!TX$@ZL;T3bFtat{7dHY71H6d{4Lfx4=rySjI`Hpv}7+^+>XOX8p`=cc-sF? z5TT={{~xI+?Gtty_bo^e*ov-RZk$p-B{41Qn76zFGLb?R<666-~EjTa^Ia!wQ#$NPH$LjjT zdEy#~j@AnBg9(&gLx+c`rU+vMGM$mGR>4lQ%Lqh6yvT5Lt&0XI%2HL$h-qK?7cmyZ zXNeI4^Ew_q?XE|*zZfFf)x^3yItlGh8AJZ|Wpz*kVHnNG=cym5=(Qtza2^=D*vC$U z#l1_v%yMb|H$x;{Sw8tMhRAp8rX-t>RD_X#MCe3LoQhwvWD^kU{sS(U67~D}%%ZRz z_5h{Wea2EHpQ@mEK{J+TpVZ2tp<~!8%6I2Wtdc+-^-!|m{uB#o;0+B|`(n)E=S0U6 z`}MH9vAK}$HGIu9{n6nHz8 zqzjxbDqKK3ket$W(4Muv-k9U3_eaLb@m)o=Qn8_OK{Z2FqN-5cM8!n4LXquHi2O{> zBt&Pb4}D;%l0vHcWfm5d_GFWdnoVUe!k>+rRi#%EjM>&#cji~qoq+b`TuBUbVe6!f zO=mLnY}GwETp<4OCAPOTsH(&A>*`5n!o$O4f6+Agv5Z~?yT+}+rJCJ5?&Y_IIP>N3 z#;vcmb4r;CQ2E;?%EY!y$?X7mMRmt_5YDvBM(;_^Tz6{<2mw-X%pikVx{h?rt>8HB zIW=e}HwbJ;Cr)t1f-&GAhLd32{I9m+^byvjB3z>FU2~HR==^85mUK(wEc3o=3K%o4 z)8-Hai$PR$5usW%ShQmf1+0StK}IQgJx*(4{AG*E5P~?3h(Vz{gbWGM5(!)9-mw## zS$Ag8L{zKYWQ3qM1!n$kBN*OghT=`-(1~kSVwD0V8~cRR`%;-gLXM`WUV07SV@8-T z!2*VqNN}UGo<9k@w!E;uW+4sJdao%~hJ~Cb3eB@Oa0;$~ViI*Go=z4=lE+-o8+};B zDy7~LyDxbWlVs(FwE|@Pol1Dzs+8VAn_hA5egI_vCvfR!Y$5%)pvS6;89N5%7NOR8 zgYOc!M3`}^eDm!W&RFHf^A5cQuX_D$S(&m$1xxsm9?t&}(*CDs?|;zx|L#~VWz=a1 zdC@uN*BLC`(t2x7yhI6&;IStSw7<}Bg7dkQpOYmtRIPV`a}Us5ZX0sIw})QVA##g( za1sv-C|-LyN=-ac|GIv<2kQAuEE$o8&Om3Sm-h)<5DHoe)dC8$0w3C8g*4wl{}Z<* zbnfaPZ5#R%x#B&?9paINP#!g`A?a~Av|C}@=#;jt^aFPn(XYiTgKH~}{dA}HOb>Bs$}eSb3K66WAP@Aj!7_4pz^#y&~D;9m^SEJEPpG{&9YXV)9newnLWa zv8)qTaHfe7v{EV!{MsR;oq>%Fa{D3E8ZFa0koKOlo|`}Y1tcP;m`H^nUurpZ**RNB zL(lCFVUQCNnt6EsE?agj{W*GqV#&UlmL`^=%CXHr_kox0=c!CS{a$ocegXp%vwg5f zzGNGpZA@Lz-^P?a%fKYAAH62?KlYk`vi<+De_{DMsF8}6AG-y3ucYKP1J%!zUo@YK zEG>W;D|EuqYgVrF8n+7hrZ>gcEvR*M>)WlI5X!s--aiEWETlma>@B#rSljah^|5{q%7*1*4GCeTLucql_UhGOkVC`zk8aX= z3~e$fV0;d`z-Yp7LdT(fiQxegLK4|d^ErViabfo5(nv%Az+gbDV%U0o;~%Vn2DyMr zi5XEzo(^<2`0yD4Ki~JhJ;9qz;T)BQjk?!lO)@yqeHnI1aQ8IJ*Tx|prW~v4pE;XL^1=( zE2CqknNiC%x0{_uVQ5!tFne(wM`d$(>Wfn&`k~+=%q`iXYWkvLlOs~Y}*y&$F z1hR>&0Q1{sP^qG-N~jEURCGqza3(h*j5*0(!jrLXMnqmGtV zEVtjdOjnZhL6}W?h)?)d6V$g68;DV`qF%%fv-Zt}Ou*z%*48S563YUrI`%FfYvgv{ z>R9}~G%iy*F36`lV`mk;J_aGls`Wzg2KELj-zt1gS5N6m&K4DqNnV>`eykwsYhJ@e zpe4^;zvOTah82>sSV_Y-hk&?oy{k0Dfmb7nOI6G)bpb-yURaQFTCa?0&O(~cU&}t( z=U>%wpbFlYO0|r4$L9?*_$nxcka_}QE-SZFrM&<%nKQ-4y2!~+sV=2>p9eThlzR3ss`9dCjcAm=AW7Ex)oj6tEX` zJghR^kLc0JzVb|d6K2E2#N#bJLp@F{Z#+Z1rvfzL%wd)>O3jC_n$MLeN8Ho~;vxRDL=Z*Ja*R-1oTXE}Aiq;m`P^dXQ{^|<5T&rl zHDT5XCW3NDn<9>4=h^OZVqDOWAT*QStzQCyd`;5d*cBYYIcVnc}`hV}tD>^|zM>3-$t}_Hq{Be&4E^S@E0v zTXe$z=TYHbyM;>df7&hlx&HhY=toJ{!tuj;;O`Wbs9^rrn~JkphI&1TS%pUy3hFMm zVgOA=p}{8)N{~F|$x>ZOqi)@@P2G<>&>JXCMh~ew?IAzHewEns6YsO`c_)MY#8`&q z{(UwsH&B1}Ibd1a?Zy zRA|kY>K5CzxT#2XL=SKX0`Y(xAKYf5TRk=(k`Rb|aC_|(us#k*9R%q>(Jz;uWp&R% zY69AUI{Gy}StID8!wS!Np7F>_jIYSgSWVa5AwuKX;j*bJkE}G6m#B^W@DyEHFxIbD zn9~3>kroqREtjrHL*Hy+hU-#@&N?|znIA*UkC+w}RUoSHY=`d4m7aEG*@qex0cm~* zSCl4i4Jx%jJ@eCYfLYJ&_q`J+<3i+;CVPqBx{((=el39f1tdf*mZv+|ht~WpB)L!d z(5}%cURh2i^L!w~R3+#+BiFpv>qugyosB=+Oyx<4EPFm2H(&yq^VbNIg?MslpNqUN z)q3qXu%|GCcpHJA2WMkPWz{e=KUM8AIhcP!llVXeHKEa^S)kuzqT3W4%#b6vB@4x) zpLi9|&5#Z_6Zc{T-w2VQBh}>8&$mD|jqmG6(JryO(4GCYtEZRDkz(TuTBhvMR1SzJ z^Xc$_Q9YMdN0(OA3(JstRb;|m@i{&8P6^?l2SYs7`a|<1%GO4E)(QAmFp2qB?-6@K z69U&&JWj(E0lh>Y)X@|bAcvcoYnKRg*ZrgK0>uwkkOlOE(q(YCXCcAAzNBvA6Uq3} zAW`YxBI^$+_JP!CHlpKg$d+R2`NHF{@lR`S{+x2g;vBFM;^e<9xvTOq)-%*FFFwLc zTTH2(t(d+;Vw^>BVK9s+5n!+h&;h}j9?}8v>a}}?X672iRPg@-3~Hhbn-@Kpo;OS2 zq@T$oP)xc{5S4rAe4E|ixJB}uAqAK9`rDt0ms`|)_M@6ld{}Ay@0|W`e@rCg9IgH+ z>i??Y{~jeaS5}&np?oo;P_X`iFSV=0ulJ3Z7)Fw(HCr=?z9n<{6b$!W4Dl-x%>&R2 z>2~TsjF0)|en-1;mi*g*oLZ@-X9}|dHhLg*^T*{Rc>=DLoai^cUSZj?h@!dlRRx_s8BnJ2d4ssZJ~$%W zGD0FV-;(-%X_R+l!Cc6Ifi8<#QP-Nb$xBO{W@|i;dnP!kM$dBGM5QBQ|9V4MLVtGT zRM@DEy+&w;kNKh$)0N@NJSsiog}T)S^O|?S`r~_w1|5%i@uo@+C=c#C`&GmKt|d_`^=6d%4}`uBnF!k@_nS}qg7FJ1ed}N<%IFf zn|Omk5AP~i6MxpYf^2d7W4HP(D0@SxAih|sXhjx2`%|sof zqDfbF4pZHnpT6hNfH+fv)amDG*M-B+GqZWVPo{}gS+m?fW}l6E;%Ip%j^G~mJKH;) zo1K4-axYSAf=)%0YjNxi%xZO<543YkTt5U2HFI?B*k%oZ;Gns_jOhmqkw!yDi{f-& zJ|qqq;wZaFTs=e%U2wc@UXet1yS$wF<7z^;d8$OPdy)z+1%FyW$I`Vy^WWi8$mi$5 zTLD3)yWC3o1mq4f)2aDwT?vTjL#YV|)blgdJXsDj7&?}LO-e5(v>Qf0NWu;*5Xurw z4&w6{1w7&@q5ntPSAbQyY=0vH(nyDdbazThmvl4H!Eh%nlL20NE$cq=p2%bRoycEaonWJueT zj3z`X5uOx`*c4Qh3)I4t3#N@17jPSC7c_{dMB9(3M7v_c*d6scZWH0{;v3=b@-E(p zNuauMjrmLW94R@@E-pC%XUI9JTZ`h@xVM~8NX}KTnOlZzT@1|@q_$(EJIAO?Axcbk zE2O&cC5829_I=IquulA{U`c6$2gM(9ajI5geONR`KZP48ENQY7NlO~aEAqNhv7(Mq zRO}LkvWm;f^Gf(=DOsUl<$1I6Rtu$$jOZpZZaCQ7A4r}b#P2nod8fS;Eu$UhZYlN= zcaygCz{``g7V*`=Qg#*{j#cFSpf>9WuO=6WH@BX$?q04|UpO=`O%Tzd32nx(l2(9! z-G%Y}u+t_a#4WA!-ug!KQzap0Ll=`~J^|Kp{?e&_J8P%X(9+h8;OSe=YBs%EJr#5X z$zH$fOq`o5q zN^!&URONR}E0o$ir119m%>pmpXuW-(p{!*pU2Bq@_P zvu1*_&*jM9o+!)13l%pV=2<0w_dK`jX})X}x37^>4ShVpMB_rUfqOo0>6EJ5I_9P% z=M!a`&CIk7mUs<&(&VY!>OAX2N0DScCFzF}rArMO+|qY^YvSU<3yY?1lM(00;`VC0 z9Bgp*D45fghFIk%RHq3%Dwmr~-6OiBIxL_|Y$a}n9otO6v?gtUL zQ^v>C&WTP*I7E1IK10M{HKj8+oV#*{C2bo)wb14DT}dUj`(XpXVS?FS+~b#`lUuUUml$^{AE>=i%BJB8!8pSf*qM*Av2r=^@)>a?C1TrT zM6M^yD9)GJ5)`SO!!?4cehuD7z#Hk@GD*GnWc(fUT7}U?e;X}(s z@`Jdpx zBYRQLb356Ykr5+S5QhLuSZxCg?Oh1W$Rk88${)7t%6-;+)3h3!H4<*}+01p$<>U0*b0exB{b<@1UUBRuy++HL zE_+bhwJk`6UT>@3H$LGz^4gY{xTK8e=}ocdcj@ZvH*g~#?s8{_wWcT@xH*h}z-vxG zc-dmAyAyB|Ycx4AAR?G?a$bHRSn7fFImtL0Y%6Pqq1A-xl;_LM3+_k0W|e3^w(5@IA_((D$&P*}Y=!<<`9$*GF0qb6@JAB3}W0g^H9>TEY~!bIp;9 zLa(8maCT>2eTh#{b$NqJbmLpZ5DiE{_obu}#@>WWHH8DuUbdT2sG3nQ`b1SS8hrWM zJdo7ir)~_yEe)rpo?zPupV*1v=i%A+zQW(a*Ier%;^?5ubL?@p&(mngbzsqSNf^7RI6^QP39CKJ8* zc%$KT<{`?*E2PLsZhg4Zgf4w-MBdEL2}b8S&cC!Dhijjk3FJVR&G7Nx?7_`ycIxkF zCC8_q9Te%~xKfi5NLdfQ&tl{=3!|7Vw%_7%b$(H^j-K;|>AtIF1Qe23Z2}qcbW^>z zy32f{*dJ;q^gLh3P>ehHg0teX?G2ph&Ce9l%s3`yW0KS}cWh)mmC^|?5;il#Aq#XA z#WH;#>ed_(+`0Xkux8qc+FLTN&(t+{5hscz}JA)M&D9oufXq?bJ?uvTI4nb2hPs1#`g$=jSn&R2YPLr3`V z)=~in;eecb91<4|JtujR?!`l*wYWovch}ENHFpHwtv^H@neIhYuUCARIjeS2vf-@C zx4zeR-t%NC0b><9^)L*$yZ>EKrEq+Ie5tyk!<4}J$8UAey{+I2t!hI>HVCAx!fWee zrIT_O4hrR5B`@P(C`6?BHA^yyPj^)irY$%=esN`8S>@#opOm?x`Vg##4=G;G56q%$ zSkGQwd$qS2_}WKfV0PZrPxR=0z0(a2Ll1Ci1#Xt%xe8wd2bo0*B_GK!o>DS;hwWES z8_uj}1$*&aJ4ZCLaRJw1Yg6?Ty@23s3`+XuJGlOYo$VDA?-!FfcI$Almp3ueZ*6b# zq!K8<6qzKz8kQ89j}eb8bFmP*P91eY@4}tV@sYH(yLaE;nu}(aAbGKn~Q zruOWkE>&;k5On*>DEV@8+12xma0WOSn=wpqYj?_On|7OXIYWrp8k@^=?z_uK`{}`X z@r^c^Z}oAEvQv9)3k#HZuD*Wisqe99Qwc$*sWp_7We={XpiK;E;1}=qt0&v(X?oci z#JYuGZP1WGA#W_!+JusJ)0!+L>xsV9j(@F3feiZ(64rS4;4@T=`7jaB)h8}QTd)LSym_B#bZ^E8Z*Wf0R7mgznXuYZ-PKVsf93a3B`z>B2i>?zRs1v1 z0sKduYEGK9l-ILnG?q%TmpQ03Ye?A%2;M#shc`akx8)gobVwxoU_ToY&N3s~6(#iL zYw;7!^iz9Muy-9gHDU1=M-bmDq4G4p_X}rbu_LT6WMKM$=yOJUr!5azJg9(m`1gHhFD=#k)V>S@HCEa+?B#@NU#*wK}G+d?s`2|qH2;hNpAiIs@1z(>@G=K^0n0` zh0$F^swTQ!L~jH$@2M1^g}Mp{XPF`$q4$Ll22)+@Hm}s~TfyYF8*mCDQyAc7Zo~Jl z?xWp$9Fbd9gX1|0->yn+e6>`Dl3l2++KiRvfhXOkXb6qz=8mDg;B;w<%`({=HZ%5O zq7ZJk-lT33>2%8t3&Xufl)E90fy6csv@!cCUj?tkzq0R9z;i@YUl3P9KunVAN#B&y z33;noUVlJWv1Er5vW3LCSa6+al4UJov=G#pgcPCAxEs!(XODPbP$QMr?H!cX*mC5K zH8GSwiO^hCn*NZ1h2C(aGb!bzFIXV5)zt0B>zp1>Z}^;jd!kKeXQYzp1F_(MK$!+& zJ+)XS=XSHyb?UiCRE3OHw6h6{v%K}Z@E_?vxx`*{mb{9;Vp30lG>tisrnDCHl2~sb z32M@9V8xHV70>4!#Tb%$p+Q@!ZuL`M%UkL53ToVRpQ+vBvlgkemo=9}F)Y#g_#|(A zR_5(`r_f+p@mQQ`@DcCH#goSh9tgxm3{56|34g~) zG(U3?*W)K`vvq#+6NX8bRTSl?bH~L%I9)hY_F#e7JavJ9wPa~~H#?{^#MKIPf1^3> z0e4)>=j!k^Q~jgt-7bC>T!?VKtJz1#e%pG!e=#((5E*ZECJW<%#FddSc4SHmjyJWX zXpC!{CXZ+r{1%K`ifJM`&n+gT>a{7kuX9YB1fDYseDdiBzG?Hw)I9dWG`L)Sbs;x`9GQ+K?_MBI|HcP3Us19`t zC7Uhc<;cpo6PxzP+bj8Trck2IEXSr^7@`kLhkw#Jl=Q^R+})GCG*BI#r;1QyALNuv z^)@K0l2cfB;JuK%Qj_XcW4bmeJrM;-Y_yOA?c62zF)Xt;dI+3Q-pGauA3;Tldg5hb z(}jHjQQF7X?}iJ|?(kBgM-^`lCgpCC?6Na|5=oV!iWbF;=*LKx&%ZKq`}6Qc-f-kT z^ZYggR$pyi?|UKscf$k{gwDANQ9N(i_aEz|Sc__9e)p(MRgcW<8Lv0H?2@y2qz3yU z4?RA~8Fl62)GwZQGPtbkqU7I9XFYU^c!ec2@S&4!g@YHpDHofB`n5+xbIU_xk;a=< z2Z*CxV^d;|FRM#tQv@WNpoT4<5$rQsnfzooU6k7mCz;K}d5JM*d(Ssq9>lr&X!t0? z`qc&dbi$=sdi}Nyzhg@=Vwo@O2eI$gsj)9TOp^AvV*aOzIJ?=)+d2NPrK4S(p&}_K zUdU|Wqbjnlvb*n#-SI;z^;^rEm>3ZCu?e(o)V%K!FA_baX}==lexzfh<*l}Tq_#k8 zh^p=4q}-p7@V3>*CmA(Bqo?VLUUrQBcPiX%SI!9w5c33zi4wvYht<4 z0a|1~<{I&kA^eh7WBzP-Z(EWmesp-Yfd87?vGIJyOjDXy61$3+2my17Km_D$Xf zMPBK66<^##IpCVgps{mhDY)0`Aa(mGx08rGDncyDjjXgzNA*j;rP6j2jbM&;aQJngU-%NI zN@UZofU90$bZ4zxFCVG!>AZ03IA(l zAgJp!J+2H4R_jqX%ZVqlW;0n)qo+?1N%f3H53#`TA+q{v(SE0|`JH6BJBe3hMjlk& zLuNaK6I1BDVo0Dg_i`!SWgCUxc$VTWT*J7_pkw*wwXRlsluLMqW^;*Z{`xXxX$bGN zeOy&23fT+O4+vXD(3D~>PFc@ldzPxe_Mcm0% zvvHn>A_q)o^-km68v%QF8(*ne4e5H%L>~pElUqm+v6Onr7Ph86Gxv{o(T%`q0nF8JcU zsN>yw=8pT$n2{mi9Jm@=AswMLP^9+-xLr8qzAKsTDaSD?fA$tbKXdxLXx0xPg*tk)@ zeeTFyBWM6^oI`KdWpXllSdfjz}Fy;Pobt(jcOxr2&-+J{vcSgi@Un}@WAsx1m^0tO8BeMy~8?(qB=e(^95wq5|{rq)y>~+w|5(5cG zf%o^PRInF>fDOrhddUxVOK2p%Bnod7uhq^Rc38wi;d|!>8-LWR)qyH=EhG zN`nOya&9ti&u2$zP<~9TCp4lmUGKTUbp5I15M`kleWq&@N22{_vVfKN6#>nl1y20_ z6#0VUF&*_Zs&xq`M1m_?4~qL`uqP<1&}|-$cj$c{(JW!)Q+}M)lyjIop{k{ud2SSc z@{9O6uj_03XAf+HYqE4DH@}`AfvZ9o`GFdadz1cN6_hZJ>er7CxW1dbnm+qkad3oG zQ&QA0>mX0fYSNyjNkg@}WJE)5@laAGpSy3PWKL&SO0J`dg0`P&GR$kgKYjbU3!BSJ zu}tGLB_=Q1>q1emG|2NsdOcI#bG!U7h> z3*lJ0@i>J^#fz_iPD#-pakB293* zml-3CaHVlI=<=0%Qyf`|EFZ>6$Wp!GdJA8^4#L86Ni65pu8)m zYki(Vl(8fGlG^ChsDjKWZAi7gpk+68NaNJI*oud2k`;-LGh4HTK?A)n>P)(m165_z z*|~|9V`R-~MG4UBBW`t!eZ0>;LjFP01c~Xko3YOgQX2=^@C78DL|HVgT#4Sk&C#Q0 zdU35(Q`bf2C@so87G3bea!H@H1YSgtBNf&&i75BjIx~jx24Mo;=kjY_J+6mq`rs?icpeh5qzkE{eiZaqbZz#q z_cla&9~o~&(#WD{5P5ywP$WMycpnaws(GqXVH--TLtUM%pzcsJB?mJ%Tj=+gK~rC2 zSQUIfpe~_!Qv!Rrh{2j9@nUxNMI*h-9J;bkh~G2XVd2p6T#4rrzVsQ>FA!^0_nNDW z#k)R=<+=-Pr8gTlGOH$Lwx?z`zNJH7#A$HkT+{Zye@(e{SisteFPcbD*R^uhe5hxE zG*Xm+5cj;L$~`0t9D(4sD5$R?Ljw!y0R|1a2C68MrkuG8m@-;d=8_{m)nzT5aV7Ym zDIAnV6*$FIS$E$gUhE3Q;0e#&{E21GvLep_asS+8zN3I;T^W1Y@|ou;m)TRt4zFKZ zGUVr2nKqe-yIOzk_D1)7V)70AXZ-N2dIzQTW~F)|n~RX!Hl43@koQ$>G=zub$=ljbfj%F6yK??e0=Dvwr7 z$#%y&LcJ+t6nT4Y9JyC~qJ!1-AVlOwgVCYr^NEbEDC8KIzyhk8xh3NcQ8t@1|X$)g$>AJ9?qs zf+I1W^X=UP5mz*lAr0?iSFUp&F^{OYo+qNbN=TBhbf-0m+5q>pY@NB&N8}eF?PXV# zVhjk^mWwDw6w2P$Y>P>}FivZBXEQt?92t_HxNL*yBvS2Hc<;s>KiT5zv(0|$nKJ0X zc@8<%8j*DvZb5kT8B{aM4Z+5h$fMz^U6&xYZX$W8esorT!9UaqwZB*&oQz5$xirq^ zyvX(jxvB76oCdZtlY}z%8)|f^ZW58cTj__3a6EC` zQf?}S*J{=imuS`&4}7Vn?=NASCQ7kh*`h1(`O&v)78vqHAJTzrlqER2`u%DM8~Xj@ zRN~uoOVaVWhNnaP1>@<;rgBIS&3RqSs-Vm9N^KJNRQbXb6cYJps?V4V>d`&IB}&Pd z*y>kTuPLoHdS66PDz5KvUk{(bRzl}eL~YI3Bcu0^TR%OPZgy~YucA?u`w%nhv+Xnc zX={74k)B=D8MQ^>a3JzglV=X0!<&UZBjMF2vQ7Ibg!E1OA%wH<7JQ5jUqnX2-zD@^ zpF@LJLAkCrrwDIMupiO#0rAGg9Z7!*ltbM)dU!GX{nVBL#2b`5=lsP`59Q|u;Qg@o zi(6(9KC|s$`TLFdy%76lyOf+Ip9T=wqgN zu|M{jz{MP#qFYzdwLI%@S#Ow>n~luXaINe(V{XN(MT!%@-62VJCE{B5%)u0Fe{F`- z90^I8$47t2Io-NrO&p1xq&C2!cs_2Zr9AO=4-pCRo%5#UP@Z-(xu?hr@3|%p6^Kj6 z^`k$^C7YhHHy<~S4z81Kl@#c9q-ggc67AlBAesvFI|fkpaTHw)^kClSd3k@1dp|Ki z!}C!9x95X^D$le4S*PXz7thdufq80F@^*bZ;np7dog0Y8PxSGxNAW-IuPX3b)E6k3 zn-VCR6Y{(pVBsj&>alw>z<7Z?Bosd#Tt${?I5&Zqd4x)#x??k4Jjc(n{t&&{bYCP| z{$^18<$G!Y^n*O4>v5{1L>#O_v0OQNR95=huJu7zmHK9UMvtuJ9!eNCWVy+uWKa_q z)eDVS(7U?u$JvCX-zp3tSyxEK)_>TWL^kpEshR*+`(B}eLbL`mi4PwRm&cR5b-Th zwTULBLzCBVoe6D*$DeQFZH5j{>~ghAB6o&l#=zd7nV9^ugZ!uc1mV`os$;D5D0%_e zTm|F1WVf?4;MddWx-8W(s+C(4wTX;qL%kjtQVU^>L3o|cbKFuxSVMP%go@`yS z(v{!Pj^K$DNHb6A?~SmOH~A2sp;jg*DBp8+qHQ93GEO872Zfn3tMOhfgJ!S?(X!y9 zjVcqGYeTt3Q?@qY233m425X{q@QM$JDU%zRN8C9naf^)Gz->l{8xJ~3GwiLNO+asJ zcr8yndCw?PIdFBE9qU=B9W;uuAlxl7c~vvPV6rx;C#B1Z9QD(ghBFbhR3G&cU*5Zu zd?}IZQdR5yOQ?c3Q!W&kyGJfub*vq}J3f*gJf9ZQv^Wo4NnYa+nzz`BXe&<7;oo-l z_(XeVoB7JrbkFql5#mLmsf0tZ%c(hMY1{SUGwIJ9gq;(9$8>Mo_)g#4wwgubT=c69 zCu%>mVTSh5n8k~}zBdB2d3oNbn4Kqj5*aOsM&K0Fb1y4stVOGJ(UQGs^E^r5*_oIX zg0}(0EM!DaFzCyfR?lw>`0pjV@EE$~E>BzGaN*inp7Yxzr!1jW6tCv;4&S#d$*M?} zsjV$kP{~q=DXxsuv=TgI`G!iJ?VJqqadHGnqRRTQhQErUpo86DX9M* zUDPX#An6*Mz2}#P{D_usu#;UvXg1ayK>uW5T{`;;xyW+&Hj=E<3{jbg0{SxK(!fPR zE?Xz!>+^3Kr=4Z(!wQ{spK2sE@-Yez=V^bmAh_d;Zf8$aWD(nZqc!vPim&bX%_#VQ7#rs`P(#ev0Ao|kg|(SzK3M&2!=mnp@R?VKVk3)Gn9Q`k{h60IvP z>s>bG-qo3}B6701mx-g^hVlHl!eh582QLw*avDqiO4h@>J$|wA1`3O1=qs@k1KTM1 z748Tw+=ZkU&&;DIqgO|nye2YwJ{#Y<;IFOsp0quJr|eSr1;thAhE*CPrnnYCL2oyb z*9Y7wPb`GEEa$3Tyq$1jUZ1D6y=b6tgFT0TPu+E}xFI34?}gf#mr{5XhQ;Ml4Tz4=K{Aoy-V&9l|C`l4Si(Q7&G>P`pWx3@mFKRyc5w0Qev_>y(&KZcQ;+n zIwrq*^(^+vDyq4pIHr0Od8#Uw&1#3_$6{B`s4MT);O}0zPQ*-~93!zG=F)WI&6ne} zvm6@Sa3Uz1H^ezDxcQ|f#|osGq3TeG*+2G(t;%he@zQIm^J*@i!7g@QR3)L_W!Vkz z=Fq}b+qRpKRLaU{4ZlmP7C6*_wUcwpAuXaasAtQ^VMWQ@Lah0vC>-g%925N~(Gi9i zIId1af71?L&ZwWl#=%k zsFV`agJBGjzDrD>L zTTyTTMu_|cP6577h)_4x^0hM>m~Xl{ZY4dtEm^{Cn`q7Ya?!qJG|6Y7Cdk3PrI%<@$i#Wd_I|NZlQ`zmJ8?`0 z#(5^eqBP1v1)I-b-J%S)%9ZPjRfrQ4C=qMvC0>d9%uo3a0pt#xe__M!HkC=i)G#Gnf?rKhWN6@17M}8CBiS97WfhV#xnU6N1!+)*i+C*Y#Z36OQoU$B)i3;Q_{_a+t z{p+U{%w4HSU#xMEJI*$~%{kvsK*NO57r$NMyIud{!}{UD5oqcmx#3>EJRcWIh7}_s zd{qX`<2nmuLKgSNO^gx*&zqxgSWYq5ieIP5*vskzde1?~jO)n4M|$*50Mo(V6m_`atMXwq+1oQrX_A`&CO&ys%S@-gljFMA%o z!~Kc}Q9~gu#p`%Ol;R%*m%}cI=k2>1T6#E{J*aFWdA3nubFb*&*0oPvM?qOcCBiT0F9(WJ7MoFykX~si}AGwr28uXhmetDoWfO z9-Dpj<*`qHJt%Y=Yg&z?-|~aIEHX3S@``LfAh25;5wk&R2uGSWf{$UcuuxkqBimbA zhj}vJ@=-Qfgget@UZK*Ez4T_p9z%o7P)&q4{(HvB2dg);10ygQ8^q&ehal3jcr%5o zbkh9H4bnrU5m%&-A{rx*@UBK6;bSlg7Ika8UKw(d7L{&~IE;Xg2#8Q-KxIT_j$nvj zB2jZyA1ch&h_Kgi^}$Y_(~@Rrw?Q=03F_Hdrz+`~k=BiRVd*%R2TyTcfO-ew@8ti9 z7jK@MglC#|htfYK#H3M566IwHgi$m)i`%lp;1`nyi_db`;4wewBN8!fL9m4zzMJ|H(r&Sk*g9kjwG$KMZbd4NDpxc{i7 znHX7+HJ|E?QVZj~*VT!nf=Q@}^J(z2`1`k&dv@G1p6$A1Fzsmg-$gmpnsb2vGI(EI zifQ+32KsgRg5~{WW#*;V>W5MVJv$;9Y+ZzIdf^mn)u_#*RyBiT$R$tM-JGAkz~8S> z)>#-3Gz^|y(+n0{uU4+sThf07_rvv60?4LRqm@#vc1b)|yWyM!5nyBHY_9S8RFdvl?Jn@P}5J49!K zvbD5~?h<1@(!nOsDt&!1QM2^5uz7S!jr%?Kn9ZtMoM-AZch@M2>~OvE*?R8nNlVoF zS=yNRj@umcPG(-~z}!z=x(wzmd^RrO6YcFoVaTKI zlk;-VQ-&PfxI4waG=Fs#(m=UB~Ooakd|l1_rrAwA4o9wpua$I!G!db~m}T!VdOWCT)X5 z30*T+t-8ltI3w4K%%6zi7sc{2py7_N4Le}z?Q$5VKF%%pLeyhx$e$*qo1nqL9H{pS zTY*~y>e)v9D7)6-;@03BgC4AYaj|r1l?IRG>k<8LYUCScBSy$qH>dB!X_=2~gVMVl ztVzisRVMn|TC}CJ?FLz+Pxw=$%VH89WGX37W$~{MXo~gt+7xjyUOMY^QIWx-_6yYMG>MQmmY_K~@PdwTPQ?C}iC^|;JAF_(QjE8?pcESY z<)#E~pAWhrFII}ERql6fpa=JAh=|vt=odM^`%t2g>Pt|OHik`FYIJw=A@vk;aOu;S z!`hacPmc22#Y_%1{I%L`*NRl9HS6!c@8IO`jG|^)t3J~9aIKp~-6Whs&a01ibb7-v zbEbKD;o}<~3aEfwN|IGCCf_r2A^S*n=CmX>2`xjBM_8`4kC63@FOs~`bG9YupC4ga z@AXabP;9z9$X!{@WCsK-z*h73rqKCh>{Ln zS5y&MxrhDb3}H=ESE_AB*d0;M9+Kt!i*)FdqQ=+HQ;%M|iSHHTj2BuGR$SqwA!+Ix z`C_!Ze!|w)Dz2Q<*TPR=E>b4`r85WZOC?Wkp>*>H=AoCYH``m0Ro=bpU3k$}LXRNU zR+*70uHY%0$KYiJu3p`>tn|2k{i0gGJA?6pR)_J8*SVVLXh;$>YM0g*?h&Kvmx(+0 zxoA|~#z&GoQ(M8hCeF$^gX18U@MNAKdXL`5I44rJ91D-==8fB*>v*0Lt zQp2d3lV9*9rTsTiaIOIrs40i+aAR?_bhYLAgU& zaKe026S#0z$NStFxJ4Vh1tXX za*7xu$VK#(Y5J7Hie#hjhz%lcdL^mKyQRN1avy{tym)ml4i>5EY07S z-)d5~;5};8YM(CYtiP({QpMNTXH8eq-A8ik!0`QB=L;*#eEmJpgt9wnJxA_3*$+aV zAibE5A1g$diVhnWM%_%^K^I1p7oEd*Q6ztxmT@MbM~;WDkCk6Xrl)dK1xn2ST9mP6 zB+BT6-{9bmn9ZDkG2gZiTh563Hm#1ie6IJ6T|^T8(O#1I+ZrwWCS-|Q*zVAGfg6N6 zGANyda#j8NXhQXVow$tk&8&G86-R3I>Nr7rYC`dv)^DTc-@exqjXvrP7^yHfV-tQ~ z7fC1Al$;XiT@r%yk+mb1B4WSzymVryta#!;71v6)7;o_CB9Y*QyDWLAhm4e$b5skL zcE=b6g!6c!!C}k`508xYOblMmcRte}U)`_i>~6+-RFKs(E*gdU(3!9S?N%F#XH)-v) zrvld{jBFE$6sMtE>L1i|T;U-fDj7#SZ$TFY_<~uJTHrQ<+?V0ntKutBoj=6RF7?hyCJ}7CuypCA)|BFnr2EBUMBP zRGw^7rF7Z8UOUK^IiD>Bxgv@CSpv$Ku7-7@hR*W%PEP1swk5WhDh(+n?WP+z1HJ7$?%oaL zZMB@55otF06?QlS34!m*+XqI3gNnQE31Ggdw?Ws}HPLw}6_epu(7pL8h7gCyn6!%! z8wW{cVFCeD-PF?o?hCOb5a zr2K}+c5aOXQ)&@5`_r4c{fkM$_ANDqrn4{JdN9Y_FbEa?$SC0Td6e{md_?xT(?;oI z!WWr+&ys0{nO8VtP``c7vCnd8D;@MNyh(K0zLx5j&Csv=+HbM0ubPrd+vW4VXnDbU z2(1qOks@%Mn*HZ-Xm_PX5ORj}^_d8sm93rIwRkFxBY%8%`y$)L8>G|iXv9a_`sQ3XVB6#mx>{-F!{*jb;6H6Zr=Iqn6u#6{X5>a8V(nTg zLTa>P##>9XOP`ahybSKSUtOAtc6_ydw+Fd$c8aaU-!$}kQrE_OHgimf$O|mwH~#oz zwtU`ZBUuntT%3S5T=okDrddOu*9{MBH}*)4=>^l$+^D@nts{;Wz;yPR4xyD%gqOeBFthfyaZxgF^=YLsEQx zd1|%1=lE6u2Jm8o7xuCHcB1U)?BED>cC~`Ks4Bw#=ax)QlxqQ5V^GRX0`uQmfq!#w zwqS=iLQE~8>{jkx?BGkUA32L(=}>^?@e9C~m_TPSU{3#dEWpv+zv+ClnqR3X-z}Jb z4UX|}&%?pd{DTG@Tny&FsF*r9Lyw09_UEopPuG(v4jI;n!U5i@(cs{uU?}$D|CS=S zX$F*6*vmm}fz)VJoUJUZ>>;+|K!D~DQ&(k(9rSCl@5zW(eUet;V6@6$;v6gH=OYTv zAb*j>4<29;Po~?m+$W&|j6fP_3syE10OlmR;Hx|p$Kx_(my;=?PMzQGxy{p=3aat1$L9Bkd8&L;!ZG-er90OBoR$j6<-e?Ai6Ur!qH zACoS%V-bRaqHR>ruKc(>`{$z!3N%k5ZTi)We<<;rQcoT>r>l$aafc;pV7WX$34>o) z;9q}FTk;q|%E80l)&XL6s^rz6;XDt4P&m&0im=Z6p9o4&SBM$J6>=(q&sK)@vCHrW zC}J=?oV|1!f)vyo;%4jmv*p)Urj#J|kmC}s|^G8RPg@} zCIKj$(4 z3$+29=I`y}f3gL0#_f1s{!_LmL&dq4VG{#2iX9uvPnN*1WV=(LzFXmO0VdS`cQVuV zz~cc9SqKKx8ffnK!aD`?WYNJrys3)<#KsT61Ym%I-TwyYM*{z36y7&$w`T!$ z2O!4d6M&!3JI}vC(Q&o{OU}s{?$6PWWPrU5f^#7Lp8((&zxQbv-^$TJm9wjry@iXC z1Gp32;kR@6VHGBgApr3QMh$$>Kb{5OzXmy(kBOi;=`TP|^Z)`@R@wfiL7eQ2PR2kb zFkjjMgFmk0hs8h)IvwNBC#Bhq3bsc8V->u|UjzJnt_GisVFtAY>!mZh)VIIV_8_46 zUg!7g{E&EgSbrbr;Vuvo%TLPSms7~e#6jhty(_!)@jt)&>61mXI{!*15Qt?CObXcL z-ZkPh%HN7jp=QV5`Jd`ex^Or0o`KmGgZeET{W*HwTc-g4!e2*!^034FaHq2YsTuHs zu;d?)XWV~KJ$<;Cvk_M}fjowQv|&xfCH^$RX5W1)`;Vpml)=kQTJs&xDpjCO@qY{* z4z45dk1(X|&3=R|e*nk&vX}b?VCfA|F<5#3@x-S55u7p<`air=*rMZQfE-)D7{G#E z0p0KX5e}$hc7!hI02SadhPt*PgO2ZZ6?D`inyqB&NdJD+! z76@iwRmPQh8ldyHP?#NP?0Zx!1$Bhln?dbOPcv_t_o>pA-aIv2`pAr$NvdS*4g35%~8Lagj#O- z(<*=!4X~vDAv`#^`(6JhEP0R+a(0259`CVEW>1s>d{zMF7}LLYAp_6;6!QCa?(``} z@Mz+29vI>xh>28Sc%vNnOQ4_2$q&e%JFt_vL}d1+g(rZ6gM$5!EDX5c$p0Dqzd-wz zL@0xRg;oQN!dlMW`d@ z)XWAbqW|Kt2>bsBO$lmd1p&L=lO=nHluqCXpdN?Hup8<&oIgeVPut`b4o2|-g5(BX z4K_xm!TWO%ki9W;`7bG=t$aB9=|p}|3I7!8KkeU#NR=WVfGmMm2)sWZYob2~v4ny| z2gJk{D(?!lJ6SvT{oFL%fZ*hS+l1YxdVy5fpRJ}>tW2TyF27mZS~g>&wE*#K;FDp4 zI2F=A1yGXu;e=0SBDUtJnHMZDDj>9i4dReN%Kgun_%k=*j!6Ej5{SzIOjy;QN&&wL zX#NyQ1tce&L6z!Hck$EYPM=E5AJ0~iG$w+5ST8D#%2jBon+9c8b3RT?*e_RoU~ zLD)by0>r=419 zlMM##0C{c(jF|=f*Bs`M;-Aw#HHkKUE=%AA5C?GKzvTX~)lY_SbThHFa*`a*jqn`%!vH+Ka6KMGeOfT3p>9qN&K!3tTDAaq!6A*X$7jbK=KO%mb9T{|A z7x4uOo&o@{hi788rxO2hH21@^{Rt-wO^D8}z)#o%KLI--80}95`vVnl?aTG<0c$;; zaIlPg1f2(dXHYK3=LBBt$GZLY^V^KD0}1BStd|;{%(V%C9e|e;ygwfUaGH8D*w@qJ z|0PmSJ<(jFpy9X;62SV-aBz%2X@g&$-lvlX`DJAXv)=>*!ZC~MRzT0k1rS^?WWmJ> zr;hqNSqWRCj40THy|fd=SrBvVgj*D07!Ab?&yl@{)oD?yVcjdIL(hAQsDUdWGsILqL1`I z9X$XK7VC23-(me&osZqt$yk;d!W7aVwon8$E3gJT{P?f1PA1=oTax1rSTOI3x;U3?i~$YecHXPpADOP5fJ1ib^r)w59{A9OTyp=*rR`kv`4a^@uwY zUIA_u3WN_Ucw992Q~k#GaepKJ<8=09;i%|ID~W-~bOraW{5Qbm zfo1=fv?T=pkEnyAaZ{*^3)l)nzt$O@%tVk0{mk+4H5srVkcj%n(?IY?FyH&4{0N6n zMj7&Ud+`F$W&_N?>cgDmKT(d4h`y`kJ5MKrJa=y7z6umx3uYTwivICrUpO7)IEC!` zd$%_mm!#qif--;FUkPiH{{i9OFK-y?_>VKeI9tIB>*MHIenI+OJaYJ!=73P^xm!b<6i z!XMHr_(&1TOcL(OC8_nm6OT4Kq1bimh2i||D5JVXOiK^gaFy&x)9jN<(%^G zNPnwmIWGPDA@$!`PuH0c8UaB0b?%d? z$JvMb95;FU{I9VOtN#Uco|CEfr-X#^f^jzji-FBfT7uS9Ck5*+-|L8A&ljjBLyB_0Isu+amu&Ywe_OdC1t<>Vx=OiLgnSg%hQ^xw3#A^hOToekp zIhpnr5grBykWw9ZVUwP_|3zEZ&i03fzjrwOO&zEe+b8aOz|B1Z8wFLs!_OxeR8*X5 zRR6{R)B9ig(Q!UyE%Ha;ND3qY*Ovf$hE2HT#QrV%$ub<3(aU22{Fwm$V3+?eQ1Ean ze^%~ZY`?F*A1tN`8NmUqUoUV+l1K5mu#$|Kt#vg?4|!9dN{bsrc+RqK;#SdXA%$> zsF|{Zy%NOL)DqNLYJ&A-t}+6st%ecj^|zv&<7)o2|VjR4GhTtwBSb4nM|n{kdp;G zz=mb$BY#Bphub~b9p&zZH}(V4DZnK$!Hk+8RC)c@ijGx#YBZ@jlP7chUC1GLFT>C^ zd~ph09f-3%m;iq(dtnKr+T+_VgTVBIO%rd7{|n8*#lnjH>qj#ZZuVwi0@T-%3c^yIVOs*dGIY7uv}52nwix zJ(d#=I0X(V3bDlAdyhS_w^(AVvG?A4EYaAF8oMUy_nRx+zP*{3z zdGlt5&V`43lQE)v{7gd%H#`g@tLQ9uy6=lA;I|5EZnO!Z#D1ysy{odVTWPQ{T9xDK zsssc5gX&r+t|_tVVC+(pxq7=T zv~r{xjCFoBY9T}aj@}OU#=HR?w>)vsY6htBTWIcy7Qg(97D_~3hdP=-)V&Jfd-jQ| zYF$}QQV1I7c$dz~#&U0Tn{q@|KYQ@e;a_4st#KQl;|47X_InSG&mvJxS0(?>4@?(B0fn&m1g!bEAOcq4|6FeXN%#Of2%izpz)K*!l`zp!mFlcCaDk zELx@|5onROP#_1Y*d?1$!cG0=#C-(8-VOoMD_?cO8Kri=X3eGhL8%>jqaP8o8`A26 zc1su2FEP=Sm{F47nhHd*rTeL{Lw`bYMu0lW!032v&CV#z(!KXn<3eGbJUXCQel28$_TIWZQ?nRctS{=%|IE=VEOD0FGtUmy?+a&*F1 zthG8Rwlc?PDw6o#yVF)yiFFLUfd*LJ+o+T9H>MgeE@+^$=upxBcEAr#{Z?*{`@+@f zSjIr}0F-RBq~6p;Gc>8aDg}-73S%xUqJG(3J4qs&yLI(mItcT85wOuLWZS4OL@+L(JJ@unqPPJoh8o%Sf{jofRa~ot zjFM86DeunI`JF*%9Au(Buve0h5ROtnP?+vbXpG2jpybJIR<401p2th$Cyzd=bgWxv z#B~+COs=6O^uQTKgf`3j4(wb zD;#5tumuaE#h|>eua;+%_AoRN%_yDf_Z8&$Z8YcPZ#P+1<0xEK8zS560osV!mb=r% zlDaCpqWR=MKHRb>7tC-kCRsE=@np~!lGieE_~);yM=%T0HG;WZTR8FID?Ib$RHc^TJm#SP^k?n zbV=sn*o;%*OOicl+l9fP!8T-48b6O0(=bK!vU?=_i%k+fjrgJU^P!SMw-YT-X^fnx zLc{8ff>ddg>>pd^C(vNgVKdU7GEGsY!O9rSNqQ*}clKNM3sL%fcHTKNk7B^6hDe7F zf(5330X@Fr!rS(pbppZ^L<78;WzhCCJKSlZI-&S9Mex&8 zdQ8d11|N9Iw#29k{j3V^mx}$htr9~G(T3CnDvCS#Z;C8D+tBq>B;-2{`I-_Iep>Q1 zEbxuuc$1)YNuV|X)cgpwq-ENu@dQ;AFy~}9PgY>I5G1K9?TBM81hdyTtEHIrNJ;Vq z+i;`z(%;aMZ_$#}Op2^hrGf${MG>-$U6gGcw6z>BP4QM-s{!0>^nA8Y^_tkcBb38# zt)vbh?>bfF5R)R;nPN^RtpLY4flo`b_ODmP)}r&`xl8|qLRKAOTUqw_+D(}_(&GVv&uOG1Xy;29hm z=quhydg%Fqh=LefHe$6gnDj!^7LDNHiTe1YD0Uhf+vChgxwH6;{h+A6vxy6@=RBU+J9R7 zsYcJDFXifi!ke5*K7to2gM1QQ(+Inxh96f0M_HK<3T$nPv#0BWqXmn<=ljjva|=3S z1!y#yUV&2>1YX-}BA}&>$f^a?o?_2~4%uSa*)vZcX(J*xhh{0pSax-bpxn*3@!Emx z?95~bf|dXNI)Vb1RPD?i_j4fF*@0l%v#%p4s6l<0-*{va2tJ3W@*y_A#4bRf9%<<}9c#qZUfYyR~3EF^0OqCrH$6|cXJAj?iFmCiSEzqk80P_2hP z*pX0v{pRZ^lkQ@#7UL_R>9vpZ>Ok9v;M(XMsQUY_W<%bg_$={E>Nr(noAdsUG@70DGA;&lS zUeg~Gs}4aPAylO3hg)bskE>A?rC&yNhPel;0h1joFm%9zvyY&vJ1~f<#30(Y(tw^| ziu1sMBnfOaMh@L6-`Hy@X!{ftWYD$SxuI&%sL1wgn}EW6gJQ+yv!<)FzNv_uj8Wc+ zlG|w^f$Dj_PjFXq4H2VvHtz#{y~-;$uUmt@iYng#9>zLF-a-?x0guiT4vhRlau(Gl zBHzZ&$uUGWONojSO;awFiJm_Lq>sSD!-$2rkNryWRyG3ktRz(!E`H9mpnee-ovvha zx%>?#|GzT8SO2J`W1P=H{$V=g3putyBQ+^f-uc&m-``Xd8jC@EK-V@#T@b>uof9b6M`e*^ z8$hd+WQ=FRQvKaKovYjh!|{j)X{OEvN5=_y<+2T7ygBvQmlmbvoAvnrT?Pm3x>zHJz;WSF(S%b~V1`dB=R%_W%xi9vrqm zVP)6ruV;l%v*VQiR3Vb5a*D{-BTj7jC1&B4U-6Z{Z|BZ}TcH!SE21gUO|B}M=vdb|S+x(l%)nap zBPbT@IKVZ5WlFkXu%sb-^{QH^sY%moZPl2zz=qqG^^ZLs8N>H6C8VJ|J~|P5p_C8X z_HbMKzkHBs68hD3$afBh$BDYg!x{12kg^|IS_GeSbSIr5cs8kq|nX9SX&G&%eZ z;hDmaW9>L>(~2isLszr4pzI;PZH2A5U}&c6HNBc-kdTrA{9SOtoT6xyPZ)tHnX%2Z zkV#2IoE{paCe5I|N4(y_%Qu9Vr+Lg=Eiyz#NtOaZAuld~fG0u@AL4AHLllTuF5*cq z+pwLpde&@GkVAauxbEwI!LDV2L1{pyc4NvqE>XAX+dHtM+$a zK*dJR5+5k1x|a;!7W6VdR=gk`jej=b*r$vN$ag`PLO;hO!qCT}S5eoHCsCaSTFDq; z6G^1OneBBi*~$K^9K6r%y_uMmK&v1&J4u@38CbPo^XOu+S_(aRy z0g?8l6qTCR8QqGkj4Q9x<%yL?gT|p5B+r*`cU1r5w;&&@>1s!MYn@f<1bkv}oI<)e zTYr!p<0CC+bg&zN?8PWSoe=2Q8UDw%satYfsEVXzwkHeSK=)S|$m=}KGsM|8==WV`nF@Syc^`AgnB;B z03jwxu&+`k_x#F>>wW>DCwS3oB+t-iLPFR>5sgdzJi{%2@^$6ey-n~$=xPMwcPhfz z=R#zhCe6;kNRLIWTOSoGllI0f-1G_!@(JCNcFCbHRSB8L8!c4yWx9_Zh}K{+4yAJa zohlV`HKGo%|eAKWL#5Ya0sCC+kzE+5rv z>-u}F{GmA5rY@^UaRDj{DvZ%7*d-6&(iVHR1*d?wzYudS9LTX~X_{&(S5gfb_ak~I z!!hpTtx$Zs~$aZ8kAZcO!TYUq216)i@D2>4H#2Hod{ns z8`qWXH1ZK*vb(HP^s!;y9TXQi%3aTXuPSVC8(u64NS?+1B5ZrIRv`iT z%g6q6egoVGyp-um0qVfYX~(C_)r?dVqHtw7j)9Ky%8q(m(dwP++7es^IzXCVHBw;VwY zX|0aRx3qUnyUk6&GX7YszPS<1b#26$%!%^x#uk0`W+eEFK)0rN33@(WfGH_ij5qDc zY!4Q5U4RcNP55gb!N;>U51+lICqvlv9J;r7kp+=FBcc@Hl*YN!9}gSZDL%~+fmW43 zVtEM{Ci2`EF(IXczycOeckzOOzJlFTL#i6bhsLT34&7zdF3hC(S}5T^(KH<+{xUQ? z?M<5#_^2qU!UWSq+PTWrz+I;J_Q64mtz}DGl@I$K_n)idJGLu+s4E z*bqJ-+#hN=*g=-I8e>a!hw`OC17@$Ba|#TzEpIeV4I8FPM+Z8`VN=LHuRYFi7(gUTWX>NArnAnd|1*l8ms-AYZQzE~U^14?qfP z-@CCLgx9tNRH>C`dPQz*1fPfpa?Em0c!=_OTD6VK`(;;812?dKBCS&B(UZ&}7oQm169G(?O#cm8d2#aRvkf!+4{sgbi2)u-#Ay#-adCZe?YjHfU?lW9^Gdn5#GcZD6>J6((AMCYv|P!&|B{H;lL+IGZ!@diG0mBWP~0LLXlX9@?m#| zN&D8iVQ46VL5xl#w*Hy{ntTObf4?wS7`$tBm*B==8>vF&})9Y2bX%osX$W2=ckXB*U^8OgKbv=(%}J{sw_Y-4q&Suq== zJpX)F3##(=qR37j4PwAAvcx)93CE~&y_ikcGZx2TdwPe21zJif-Pw;mxFkd{=N>Zr z$i*(`Xiebs3KO)-R~*s!^3J#teGB#Wx=6@Nr+LS(r(+~Wr?4X++4&c-26(dcEn?o* z|Ke+iHO3i*gW%7@yN=li2AP|q7VLRjfrvJ6ci*mm;MxP-uh2v#qJ^fYOSbCNQ3C64 z*nq;<%(hQ+QfqUD@zJLa zz)YhWaX0S#__iY?9*LLQ#W!y?;wGer?D8Vgaqk;cxD2MZJL`0)t(QEDlLWMz!g3YMH6KnY>HzeuLNk{He=sHCGjGTwgo=90F@E zj($(MS(V8S5swKm3g8Wv)SbYEG|F;6Bn-jh@h~J|?5yA?-)u4{%Z?Gge%3C|SCpL@ zjZ=1HUgU-+7e=zz#2tAl#;RXDoQ#E8K9RR~3jTmAa?D%b>8eQM8d`}2!NW>u^9kh~ z)%5aSNTY`|?nEiSd#MrPBd*^ye8&pJj+Ky1qiQ}~TaK6#FT4}s)*p3lGzU}10acVR zw!2X}OiE&sDN(5=|9az03E#qedZBYr^9jb892SR(34-|=8Wl^-SMAk|ihvn~K30%0 zw8_r~6###Lo9+0D2|u0b-(6_aRm_|TfNQV~AUGv9*{H%ugKt}=KE(IlXf~?g@~v$+ zAt|x3Dlm<*>5b0;GaNAG2s`cD*kHnp%x9(pxrdlqB`D3Oei`1$ZxXO>;id8YoKO*_ zjD`~Yj0&Oa5qj$0!L&*cmdAt%;Q38a<=oK|0T_sa;Z-n$l03sZ*?__giE+kwy&!*8 zW6YgFWuZBC+XZdRyxnaGZR3rpHWX2=E@#x|50`*(28KanlKy%w4r&8p^{9z8L>W_C z#T&3NslJ(Q*31@7mST##qKLdNZHeF!;VZ4%-g&7kKwvlZm6oVXr(2w0M@dRaV0#k` zk!q4Z8~N<~14#Wh+5wwZB+tD>1vox$#(^brvwYoAytJQ}=wkzGZ;X#L^-WUa>%_d1 zKGy(q6}-`e()7L}s4r=hF;)Lv?uJIWgGQmod^t@(hcEm0vwx}c8PUoCY_iA9M)LTK zumPDpUCkOEk+BXX?!~MASpHQ9eD%Bh^T9L7=e50fpSzTJ7|qHO+n^a;CA4w+$TQpL zy$oqRpnPNold8t)BNWb7&cAGU8tV$Lfrio<$&>togQkGgbIh=NxC24w1b^Ww!O`g^ zzP2ItwOE9WHwCoc{BI?(n_$Oa#|QPf>)&P9B}M3C^|&K9QJ?1Su8%jxy9+9hUf+)z zjWZ5)Ex{CKxsu2Dk;3qsdcn?@RsRQ}a2}`3Z@Fg5VP(S_Vz~1Wbn`1GCxY%oyp)5e zEH(tc-dG+I1X2GxmpgPFzCDGvG+z5t^nbSR{c(|K4;(yiR*3nC1GGG!oq0}^izfqP zGF}=X7Rj0pmlAKjYo!_=V#LfBpN8S{g?MS#Kj{Eb-90WMFwi|Ife&>$`;?C}0CN~G z<_qA@v*T^MDIFeVGyyiLTb@Ww!62cXcocTXo9L+0WL^HI=zj-vJr zU(ya3Msye8`&GzE||pe=v1lnjs$@J)p|^9sP< z7Dsoa<80wFb^udkN<^|%--52~GP~^Z+mMG{%1SxuT~P+a=j52nKlg_M;1|3!>YP?d z1|V<^mj~3IJ{n(dcR=V^S%x4mn#F}yub+UgnTw-5xL1)O2+ZZfwd~(c!`Bxa5MId< z1l9A0{uox)gs(^8rM}>3RS6HNf_c=0_gh-0;M1{q>C7rZ~YuM#mFdT(;p8*8kk>pW>(UHPXLMFhry?!RAo7H^mgs zH;qSC)?8=|D`&?)P@i(6rVTGV1#>ZhVHlqNaCkhv?|}G(e*axv^1Z+;l7lupK8Nos zV?so~uh@uuFEENZ56&;f);^sxdMW+BVKef*z$7;1omlf4w8xH5E=TmavbpVhI3DY* zYo5&w@qx)mpkNI*$k{$3| zU0X#sv#nc84+R7H{bvKi&j5+l$d)D86}zZn^X>;E`NJ}Eg#)bTH8bKAy}4sRVO1x9C-5>D;J z1WZIJ+tq=6*d-x4jD2){V}el|C4O53WOZ)BDl~BIvCfsWMa^D3T&w#QgzI~oxL;ZV z6jm8dW75;H3TS-RJU>_Tj{^{{rIghi?Tu3a6Z;77Y}ZC(8;{Ndc2|MOI>;mfWJ`&b z+(31~7``U;UXwDg=12$BbtxK9>+*%qv}@*+%i#4RqCLuM#r_&l>+w-NryUGHA)I+W zNKxAlQAe#I82%PE-nHfj@EQ$X%M-m89IcKj2yG%=9ym`x>$4|4g}24(y9 zs6wAl{9ZxEUHiTOi6(ykX6s(xO_v06>ZtpD*a|H@G0iY%k53Zr{Z?KZX{d6x{lYC=&IFr4i&b@s!tRK zRDXW@bKOGl+aI=Eg`idaT?tKMxdLA~DJd@M5D=Mbb0^5<_o*TCB_GvhaVEBg!b&)4 zh}v|&5@LEV#fL4}zT~QvfW-x}vi!?I9O>u~`J#OUekY~r>S@?%tUH6zp&XHT>mfdn zM2mvKq4BeKuh<Q?>{A0>rLMxXtvA zgC$A5P_8ZBSY!wP74J~sPjLJOlo}8!<3D7aih|D%t+d+xH3AwsS!fCW@ zycM*-!puwZjslsrkf%10r{?F3kU`<0uxo~O3d*?-G}xUXwFnKDOgSB+17XX=iN$cO z@3|)mA~b?`@g*c`WYIvvz8t7|m|W8?U=Uk=&4FlzMk=|osZ->e@TAw-w^jybH%LK^ zYeEiXOqM;gv#uJkREqS>%Upy3EkbkMZcZ5?6lIEy3^GIsTYk<{ubb?criBjH6_qC= zbdr$A6It+>zkZL`0e>HqMjIj`uR0BWL%e>Fc7QD_9LHOknA7I`9L#6~N*s+5o+w5( zmqPd_;w0z@R*Ney;%$|0$apsMV9SFv-dRyV0obxs9c+r%M;ase$X=@kKj{fovpbFG zAuCf0Dj;K(1>V6Qw~(Qx5xb}p)MdErnPoS-r`s8ns0`8HXr;uL;kT+smu+S020BH4 zS6CStVU1M=KJu<@3j#L*_+JOTI~7$!PF6&&exl4byMXEhp6MW2qL?zW#Lp>GxKpxJ zrw2f1BY`I<*`J2Rl_@ZzR9Te#?oEfqOMu80&}dvUxTHEb<`v=A1JC(-H_VvgbQ{pU zz&{TecKr;c9#>ZDnD}V;Z#ii#w6s>5?raDfnio^pdqWJ%~ zX~9QRE8@Ry{&L(Z@Lv=(s@e6ApC-C1;ak@T{7s2{^@+GAhp(N*`g14N0qX@`nzxu$ zRsr4a+W4@ywkyj^kg z>IAglVuWIJ5AKL690EpLm+`a~CYh_9_y&^t-<51EQ$G;Cmo}lvQxO^mmc_vO1T(8T zYtIjQ4wbM|q-fNwudWH!FD@ZDO_|0|E05JY3XM1;jZZ!MZ#6X0u)+(R;FbI(4kmzo zc0f)DM6o|N>ShGq2NfSicFf7 zb9;x!BhDa4H+Fa!jyjR;10>7255? z>H50uIpq<|Jl~8`;>+UW-mumwR1NjPVl-VzydS8A0v|Oj``F_1prL6FeKU7ERaBM{ z<-@kve_#*4Z?eiOhg`PZo3Q@EVAK1in8wmL!<^e-I~*#feKWkHCK^^+g1&ioX1&f0 zU^h`{RoXwMcgYC+m-Nj7?yvU;Lo~M9M*Bxl_b;Q(Z>FVaJdyXdaxkoS+BhKy3;Q)ss{Y`|OilVXS_Y5!Q&Qws$?YQjI3M{;=HbZ1Y1Vh2V6Q^n46}E z%l7EnhYz*Ie4kpn#%n<05{3_&F<8@2oeW>u##>^)g~ile9V z?{>%Jfnu6K-!$I4K3x-y5S&54XWy+zgY&sCQn@48qdn1Yt`=xM>Xq_d37=tXYaLp0 z$ud<`RsR&JKigzKTJpI=OJ@E>6FQ&i_seS6cMK2|HrIPC zu!2(mDFx2P@Gjq-|a5qB`o>SQ4-dbPOW6ts>okQ3ng1uhZ2U~y43V){)Y!EM z(y?hG%B@dz3uHUfS&0x`o*vub3n}xB!Y_1n!X9Wc5QOMp@vc*b2w7@5uq8_m7c6MD ze)Z1%vlN8bflxGIQLanIh$&ZET}wM!tn~dp`lBGij^d?6e4;W=MBo?ayU(n!2n0GoTXYCdjL|}bZ&(it z=dZ>t?bv~Kped1Fy|kdRa~Jr?!yAoV-4IIg0yiH}obhP%J6aXgBPqi~ z`HiwWOW7NRAQxM~pvgFaT=Rtb`@ zjUIGD>5URvg;}3S79Mv51l`eNsENItoDqUS`tQ?x46%X}ul~vET8V9CxP`3@ zwA93HS_Y`_?crU^!82VT0y_$;GSQjK95rwpF2iPleBdJwYDH~DmZ2*mUBtVRXXacD zz^xJlKKR3qXZdB^)SWE1yBirns_3%o6rtPU77F_= zLq7Q0TkoP-ITAZeg@SL~q#ax^F#oS+@#!qe{TYTx2j-Gnw1D$j-V%1Da^8ZtF9LBT zbwGaGG@$Z9LoWVza3Ln^!TIGw=86*8f*YT2b8nBhEV^iNp zBiBI3?A}B=)|b1k0lc-z7>|V(KI5ORT=8dzxA?$w`AWJ9#JH)5ZHZ27TVc{Ot?`Dw zwt-*Zn16X2^-l*-OaVn|jxTRzfFfUn;h8pIKJM}#3VWOY!xIsQ(D3#J zZbWm45M&G+V^Mx3h!9WZyL7H3P;oqhELba+$w?!!DXN(X=c4Kb$D=&WnLW%6Bszlx zou1FingJ4NqC1x^7b!fVEEm5xR(!N6#}viRV&p6T;PDSxJ%Dcj|FjTv0ZvTOitlSm zK|vy)!kCd+hgSlH?w~;D7@h^SQ}B;9=>^AZ1eK1sQUV0pfdDN_*j7Y4fxvhHeCPaW zJ<4%gz^kNo_#l($pt%{J_P1t6dfAbESMB(rq8qCI`SJ1zW}uGhuT@q%d{_)l&g8f6 z^XyN9U&H0&un<`f*iunD0h~xGz&Ex}{g&l#a6hHYpXKq;h|g#I(CT-;4hC>@Oy#KE z^sJ@^jOfc<#WTF!)^#@9AygOe)W^4|sS#f26;>}g{L2tvH*>%)Q(GIh*dx5T=~3nf zD>hxg&s0wvwvsofwL9Cv?}3h!l4Zl{jRxA#6+A(~g}={ZYH0v)I@kQIu{Lm_7wFVv zNsE4fc5KZl-rAtW9^j)sf2lu!-4H#9vTgL$hAr&& z2RP1hm09?J7TUnYz5Y$-9et79(mgIO53QquHDL35ylzk7H6Ow7V+V%Ibx;7cMT&~` zRtsOeAC?(W%vrqUNQC-BXLW2ozr#PBztRa0fN!1Tc}hz9Zba_g5Rcz47s zt~RF2bjTmxMFG_xiDFj37jEBsAj&h;X|<`s1?b^0bWIJqX{2Tfe3h&%>t6RFpxa}_ zp*6?}QJSHd7z#ImZA|_x^Z`PZ&YZDovR8s?2ud>Oj^dvl&IOmKP;xq4pARlskK zeILjuC{FNB?)G2I=1FWzDSD3NnY>s5+ExV0=Qm%#yJ?3o8`UElNlm-%Qgu*1X5*hj zj+F+Z!|>9jzOXGSm=*)%!{+XoyB=Fg_!lpoqa4F}r7)aTtsU(7XO3x;q0QHLDZ8Dv zYr^HT>su;v@g6|3l62Zpn*FW`R-9h)^=~&P0I(Hc3DkFV-Jt}nT%Z(2c5Kk;N|5FP!{C8CGnz`f?LjW(VdmytGHOx}kuHOX)4=R+vxr z=4psdw51nX7(Sv7;HLzwc{>=G)ZK4jabABNW++;hZ8zy-`56W+g4> z^7D~v9clSPH>h_G=I(T%(c^i zy$-lt=N-GY&{6#(@@a=xn87X#EVHI12#j#BP@jU@35aL0;a-#SVC_S<0YL}N#XtQ< zBR=0d7_uBcybyi|2WiQ=u6t>Xz={t3K-uNBL!p?1Pz;?%xR3X~rs~Ni)P`44bco~8eTWN_Qwm?3R@;axUGpDUGC`}pVm$v z8;pbCqYR?E7zbxwH@pyxf^EK`{`S2Co)0@m#0F=t)Jsa~p-+q!9=DaFMvwbW@FjJf znA8^+V0fal@FjE6Lqz%cUPEqnDbNB3tIv}1$D^bh#ZLEeUxX31l>Ec{= zzcrJcLe6Ze1}RuxeNbiIVRm1rBV=J8HAul4|FtY^Kl z0rHZAE}I0XL3;6R=tIb&h$HG1@_o z**dEr*5S8x+4bJOEZ@xTc%&^8+EoQnaAx-K+8cH{!5DG>nGEqKy$Yh>yzG&_PRoV@ z(HTCC-godRQi#YGx6Q`NIoXY!W=#Imc(mj%r z6LDmfzw)hN`*xX@&jQ?!kd$UXVtcDWs@x)${l=jmeE{vaai?37Dl}gKv$qa@^^B{4 z*U4&-0)MutW|NwH$U1%0AO#NXj}4=rJOG!h*4Y(*+Ml$(4v_4!yiiM<@nt?2pKyxp zF>M7jdj8|uR5_ZpctkkGnXvY^l^(!3jF)!5$7zmOxFJJ4*=V%hL;nxh3Ir5t|0GXj zKRFbFSs_a?2fOWCg|AQJrCn`ae>o10f{bCoF<(*J0@f{l50p_3!c7?wLcdJIzU+h|JI2n(M3)72Ayop%Bwe{kCm`P;ip4C$k9TL5iw8~ zU&7ltw)XoUyysEm*QkeIHCPTLiPsfa+Z(GJ%ZiDnWAFDw;$Y=AM7&ZlzERK-k6i;FWL_ZeYkU%>VJQz1bu zf!-PhMZ5C7A4L@SIz6|qLL58do-HBJ9@B5W0ErLOc1(2GHMC6yG*AK3)ImSz)}fXIoFhAWCu+01sPa;Y_tCW@iUmgLayW~i5rbAf_Nt+psV*W3QveT+~fMCd-#;y z+(&uovP=SkJ225RP4R)gd|o;){I=slz?{WPTW8F28%!|nf{IrYYDcDd`^Ey~XS@aR z+MlEqHXsNUj5D+OoV>j=pa(|_~RP! zPy>nr1^FGpiD8MR@?ZnTxE%C5(F4ILn6=ihXmvB!W&DTN9$}DeyO6m zwff($F7+ATu;WT-(=J|b``SDtC|k&~BgeY&75Zp{4Td!bR={v}1iw*U^cp>rrIWVf zb;WCcM*nKVuq#8Yd0ap4+hzj94|r)0?7q>4hAn3}_dG>V5_≈8>;_b`BN|dh-8f zLkMNg4X2y?rA8QVCzj}X!fbtK#ccr;3p1#P>%7SZ1%m}YQO4+$L~}3VE9vB%9A_^8 zg~bJwsV1=>zmO(z+xkenvaMUB&!#si{B$^ToiOEJoQZJA9Hh+FiL6hhHDjFG~WeE$FK9K&jA zlOQe)bDlkx;s=?cqlHT&mFnFMWm`PhS^9oN&P`9HAc{=l{+Gz~u?bz;@;{5LqwAxY zO?!geLAb2agyfs&Qj(Tf(Qb8uSpDUctK0k_!Dqa5C3x*CDV(AN7}4THrtsIhel6Hl z=AXbbO`f%VqnUsp$M+%b$3tuv_Br6wl+v?zQtUt!;hXOTmbfOk&g83g289 z2k4&w?Ml>~{a-1xwlTQX=6#R00qq&kXy4lVpA^m3arN=I@LS;2_O5=ohs}ELg626Jp==1OF6!7nZF%R(;+Vhi(q(q{qEcMH)C9~< zMU$ZAqx#g_4uqQ!G=P}N!eUYsMFm>TgyZ`K#yQ5Y{1cm&F920)^Jhv(sgf8B;YP4A zDT!q>1+c9btu?aaz*#zz4uwZcNnyj_JA@OKNyX>Sxdec0uw9R6jqLxo&lsg!~`~9YHgV=rs1$Yc4!Csu!!cO0TV0Vf*cK@b=rvQ8&jGIX>!#FBB}hl z`dnrKYfemz>4^VVQz;~RgpE(WHTXOAtu`A#bsT_Z`bvQ$!!92v_tAeFvm3VRLa{U? z{XRenBzG#f$jNFNXf%PeT?W@)!g72c@KT30yrmRdvC*mvhSro)Z~m+a;N^JB5Q``m zECshs`s^(vaIeqm`qyB~YR=FGjZ0UCNCD9>xZ*&$d4hx9pZ%k zKp(%5Fh6;sBB7P@_Mvc_mJOSh*}a{(?;&n(Gkhs3NRpF13OL55Fe2yvY={->wa+oxGQ`= z{+2xbyX4V@v`9wt=dxxD4w^p zNijzB64{Q&-btUXL+;9G@s@<&%e|!Vgj0F03if1oa+v75*RY$0=!*J7dxq2qfzsKT zc%=zpj|3@Uxvvsb7kzx$=?^EswS(@x3EZ(nB{;|25^UM-XhmuZIg{g9PqYXtvZE1) zUy76*$*tOzl4wb}iqKy+_`M*@GO;;2ooVFhn@5-|ttsz3z5m#`VkKZ60j3er$);2( zlZtJdU`Z!fLu3)hZ+%04!DU0bUPy}31o5%&rKDJxz_)YnjqTp&2eXlQ>GY-6Few5; zqB!L91t_zt3=Fvo1a=X7T_WM2+V{!~aH$=qZPSZR~w;l0NYEKv& zZT(9lrBqZ}Uog_P4=iFF3I5N(NxBjl7LB3kw)IY2sE4IOzFvp_RN=3grFFVRZRATE zsW{QuKHaqRGd_mb+cC9xUtcoF;8qZIgq)5sK{tsW>dJCJAT?S9JiQ+44m7?%mtwVxBFxE7f zzi9-!JMj)oryim4>vky(#Xbpt+YYw~D}=iT7gt-(V$&F?mgcE<@1$f9?(!Rb*`O+2 z8-q(1bZa^)#_g3tkVY4H=NhNm3|)_Ic>t2r!1V1wDVCzt0xQaM#jhJXwqrbarP%*F zBE^;@-t3l{Y_jB+1YL1)qxUz~Bj(%YELY?8V^Zv9EHlGbV~z#+&YY7% zD2gsggz>5PzlhnG1xk1aN==Aa-M%EH!c{_?fo(~^bj_|8y?IV3&`ObK`QEXV$`2su zO^AhFl@cQ&*|8&}OS`S&&4X;Lts$>N3%GIGay;hY+(6^|JJlCZEOA2mvL41(yo6_!kfV)j- zM(PnyJe7hVaf5;vOVWmqq^~>V5-ZpL8%VTQ`28bAQj}G&uQ#0?WH~sOvZVb_ zzLa98&t1~oG44mQIl8EFWA)W%*^FqKJz$weJbrJbkd~nUEzOtu-o#-ASPQ2ChD!bV zofJk2oNgtDA(n26bg(8c_v18TQZB5OI6nj2XNJ;&Lz3;NXH6<~^U`Zi4I1B;N(2Fn2 zt_9J9kb}< z8Q83#&Yx(_IlB;q?}TkmQXf5FDB5=#r+Kq zvjgzIhXj=gS{WyTX0F7uv)(gaAB37M@#^~Adhc8G0*eg*UXB2d$|D8ljywWpCp751 zx+5@40h2~A&GSkz%}Zc>PcX9Mz_8W$d;_8a8WG;fCxx(Ol=vuNWo|W^3Y5)2aU;TA zC@4i?O-~^mg3nw1zRrF>O%y&jQKU^$xR@686;_L zcw$i{lIDD>J{C8kM~V_Uiw{ijU_nP;*i>nvorT4fpan_5F12qBa0938*jEboqNEf~ zYDwnkjj!vTA65*+cqPh&JBqbpgo*A0Ji2Q%J z?>`ez2QcCz3X?nuRh(@p&~BDNt!1Xng^&Ztfxoh~Jlphr|BusGgOd#o_SdYMloQ^h z%9?u+an-PJqLKNuT+kz(uLahSLJRaL8_W2L{Pu%$J9dZHD}?Vfu-@t=q#=(P9rI&+ zVlDqz{>xPms|jLsMd5Z`LM+A*(F-GDs%Y-E<<^yF`=FVk7z=4VW7bAeAj{;3&rDp^ zjca>Av$*(LwmT(vQz-@l(N^Yq&m=z1nNi1Aw}VihVLNnO-sCIAk#_6!(JXyx!p9!w z_a?O<zbLZUk1zf! zHWnSI6&#YQq!*#!KS&B0Y>G^YHP~0R_|iKCM6YDY@hZ?jX@XXDc9Ei6JQ81E_oyrVdVxDufk81+ z9bW|uu{)8Fc}&clI0=wPtc~n6p%pK$Grr$;wuukS^38` z+5wv#JWJ2@88c9dZEMzaCB~YgTNaiJy2ZtX0XbOgzY<*2n63UGDKg<&J_4`=PWp7% zUJ5B7c~`&YR$U0gj;1V6G{1d_l%#`2^fIIg=SNEm79N!w9Vfp-JQOfY3YyMlnmb%O zza^)y?s5DCbdtwmd|5b3ie;Ib@G-XCt=GOXFuns%u0+W7W2G48aZWP*({>lCs9y4-B9phvJ`_w`zn2U&dv{0=7W`L@OW-S zxCT?@P{eQqS;WyN-sNZOP5w|A4t10~73WCtI{|kKIM{QSS=`sWu530r2S4uD&`p<-A^@(1EaZEJ2C+|8}j|EC`AW@q*ybk*`lQ1PDM`6iWL}QEd&SaLA zX2BTUir{rXGYusIt7Rbvh&^pghHryJ>-ur%l`X^F?T?1SFzM{r!$e ziLp_VZyLsz36n?TYgX+`eah6MQUr^~6kZl`OoZ1*M+CjJ-7BH2Y;Ktb9Zh z43O(f8pkm6ckE(MuSww)+bP+^iZXHu3YxdB$F zTgvo8$_j7gM^b!ciMIs&#qKP7f-MF84jMG3Ec8lBgKk8yhb@-hV=Eqlb9NMn$o&-? zQafjR(5VMHbbjmgMoLFA@vtu!3Yw})@m1AN1Ct$CPp6VM-%2s%O+_kQ1rE+&7;%W@ zL6|+syy*uiwxYc{r2GWLHlFF)pA{^<0I`O|NSyzZ5+ebLY)JrTYU3nnclSg?oT-m+ z6ms!2o?_|s<9KPxqRS^KC8^3png5n~ch2KSg~vV%2KVaN&?~p{xvi$O9Sn915FTkE z$?hy4eYsz6U0Bk^CXY_vNQ4?A({` z@QQTE*_lHM!prCh?6u0}9KBy)7UhQW1ZqXWPEts!IX-)L|8~1J0j#-VWTo@%u|=g= z)@5PKnlm5jr*ERY4nSS&9V{ubloX2kGRdYYfw&8C+*Et8c*vm$YOsqGS8Ac6)%<&H zzb!izJdOsBbbjmZF2Z6x9ZtNNKFQ!~6z`~rn|0Oj0c;M0CTT=fpqvz%#bmg9K1EPI z;XHB5f?eQ{6{wXa793Vt3L|w9j6=a`dehxD*5`W(ebu~zxnniQJ^y>^Pq~u}jut{d zI@`_TDdmcooIEE1e{3!^GVc@@h7l2Sub<>|{G_^wWdi4j}2A%&E2%PBMVW*?CV5NINFNqtUP=z`x8))@+XhLg%PJor#gZ0i2|}3Juf6661BM+YMMJ!R7E&m# zbG8pwmJm9JT^WR{P+DhxzQuas# z$a|f@F?6l+=kk-#;#1&L?BZckZ0>y3RT1-rd zXO$qL(RlC0{|0sgXla1b#7_D4Qcy)@AOhl64!+r6u6Z$lkH<^9_ni(>a5!#j8eEW- zui@XnVjY06AcO|mex0O1BmiOl8_rP_=?(L(y_%g?5eUgkOY8@y4R)4d%3YbjAZHx? z?lqhGhXam!i5Xp_aBNK#TL>*RHG&RQVQJ&db#+k-jy}C)UYzGrXT2@v0jS%3Z zJ2wq(u?se((nh$^LkcG8jeMYUc^aLl2MM3!r5<8?FDX!%5%y*X#k!C%!7=5@@4rL? zrW}+-Jwy}CE2UX$*&#=EyN|LtzijLs90C;vIJQwLN1##Ftn{G&1VKTUzC(GU} PGdpC`?H+(xy6*n~GiFI( literal 0 HcmV?d00001 From 24db7fb866d421cbb295bc3f25ac881f1cba5d74 Mon Sep 17 00:00:00 2001 From: Indrajit Raychaudhuri Date: Tue, 19 Jun 2012 01:07:04 -0500 Subject: [PATCH 0073/1949] Use updated SBT plugins from newer location, cleanup plugin config --- project/Build.scala | 5 +++-- project/plugins.sbt | 5 +++++ project/project/Plugin.scala | 6 ++++++ project/project/Plugins.scala | 7 ------- 4 files changed, 14 insertions(+), 9 deletions(-) create mode 100644 project/plugins.sbt create mode 100644 project/project/Plugin.scala delete mode 100644 project/project/Plugins.scala diff --git a/project/Build.scala b/project/Build.scala index f6ff64481b..98445a4265 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -87,14 +87,15 @@ object BuildDef extends Build { lazy val webkit = webProject("webkit") .dependsOn(util, testkit % "provided") - .settings((org.scala_tools.sbt.yuiCompressor.Plugin.yuiSettings ++ Seq(description := "Webkit Library", + .settings(yuiCompressor.Plugin.yuiSettings: _*) + .settings(description := "Webkit Library", parallelExecution in Test := false, libraryDependencies <++= scalaVersion { sv => Seq(commons_fileupload, servlet_api, specs(sv).copy(configurations = Some("provided")), jetty6, jwebunit) }, initialize in Test <<= (sourceDirectory in Test) { src => System.setProperty("net.liftweb.webapptest.src.test.webapp", (src / "webapp").absString) - })):_*) + }) lazy val wizard = webProject("wizard") diff --git a/project/plugins.sbt b/project/plugins.sbt new file mode 100644 index 0000000000..e5056c02b8 --- /dev/null +++ b/project/plugins.sbt @@ -0,0 +1,5 @@ +DefaultOptions.addPluginResolvers + +addSbtPlugin("com.jsuereth" % "xsbt-gpg-plugin" % "0.6") + +addSbtPlugin("in.drajit.sbt" % "sbt-yui-compressor" % "0.2.0") diff --git a/project/project/Plugin.scala b/project/project/Plugin.scala new file mode 100644 index 0000000000..f5b6dc0fd2 --- /dev/null +++ b/project/project/Plugin.scala @@ -0,0 +1,6 @@ +import sbt._ + +object PluginDef extends Build { + lazy val root = Project("plugins", file(".")) dependsOn(buildPlugin) + lazy val buildPlugin = uri("git://github.com/lift/sbt-lift-build.git#c78f617f62") +} diff --git a/project/project/Plugins.scala b/project/project/Plugins.scala deleted file mode 100644 index e942eb1983..0000000000 --- a/project/project/Plugins.scala +++ /dev/null @@ -1,7 +0,0 @@ -import sbt._ - -object PluginDef extends Build { - lazy val root = Project("plugins", file(".")) dependsOn(buildPlugin, yuiPlugin) - lazy val yuiPlugin = uri("git://github.com/indrajitr/sbt-yui-compressor") - lazy val buildPlugin = uri("git://github.com/indrajitr/sbt-lift-build-plugin.git#c78f617f62") -} From 239c28c6fb781a9f3566419f73c5642850005c1b Mon Sep 17 00:00:00 2001 From: Indrajit Raychaudhuri Date: Tue, 19 Jun 2012 02:00:12 -0500 Subject: [PATCH 0074/1949] Cloudbees having difficulty recogzing gpg passphrase, disable gpg plugin --- project/plugins.sbt | 2 -- 1 file changed, 2 deletions(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index e5056c02b8..cade7ba087 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,3 @@ DefaultOptions.addPluginResolvers -addSbtPlugin("com.jsuereth" % "xsbt-gpg-plugin" % "0.6") - addSbtPlugin("in.drajit.sbt" % "sbt-yui-compressor" % "0.2.0") From e546a9f34bcf7f449ccca36a45d1fff7706f9416 Mon Sep 17 00:00:00 2001 From: Franz Bettag Date: Tue, 19 Jun 2012 23:14:01 +0200 Subject: [PATCH 0075/1949] Fixed a typo that kept bugging me. --- web/webkit/src/main/scala/net/liftweb/http/LiftResponse.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftResponse.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftResponse.scala index bdbe886a64..dc989b684d 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftResponse.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftResponse.scala @@ -353,7 +353,7 @@ object OutputStreamResponse { /** * Use this response to write your data directly to the response pipe. Along with StreamingResponse - * you have an aternative to send data to the client. + * you have an alternative to send data to the client. */ case class OutputStreamResponse(out: (OutputStream) => Unit, size: Long, From bc5370dcb71d069fd771c20f490ac63e71c5fdf9 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 20 Jun 2012 01:00:41 -0400 Subject: [PATCH 0076/1949] Rename redirectAjaxOnSessionLoss to redirectAsyncOnSessionLoss. --- .../src/main/scala/net/liftweb/http/LiftRules.scala | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 80eb0f24e2..ff24429326 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -1316,11 +1316,14 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { private def logSnippetFailure(sf: SnippetFailure) = logger.info("Snippet Failure: " + sf) /** - * Set to false if you do not want Ajax/Comet requests that are not associated with a session - * to cause a page reload - */ - @volatile var redirectAjaxOnSessionLoss = true - + * Set to false if you do not want ajax/comet requests that are not + * associated with a session to call their sessionLoss handlers (these + * are the client-side liftAjax.sessionLoss and liftComet.sessionLoss functions). + */ + @volatile var redirectAsyncOnSessionLoss = true + @deprecated("Use redirectAsyncOnSessionLoss instead.", "2.5") + def redirectAjaxOnSessionLoss = redirectAsyncOnSessionLoss + def redirectAjaxOnSessionLoss_=(updated:Boolean) = redirectAsyncOnSessionLoss = updated /** * The sequence of partial functions (pattern matching) for handling converting an exception to something to From 0a982486389803a6d88f7cc9438823ef8d64c2d6 Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Wed, 20 Jun 2012 05:15:27 -0400 Subject: [PATCH 0077/1949] * Fixes 805 - snippet Msgs.ShowAll should not be a session var By being a SessionVar, it meant that if you had two pages, one with showAll=true and the second page with showAll=false (or omitted), messages that correspond to an ID would show or not depending on which page was loaded last (setting the SessionVar to true or false). Now we use a RequestVar and all is well. --- .../main/scala/net/liftweb/builtin/snippet/Msgs.scala | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Msgs.scala b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Msgs.scala index 696702f577..551dff9d06 100644 --- a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Msgs.scala +++ b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Msgs.scala @@ -18,12 +18,15 @@ package net.liftweb package builtin package snippet -import net.liftweb.http.{DispatchSnippet,LiftRules,NoticeType,S,SessionVar} +import http._ import scala.xml._ import net.liftweb.util.Helpers._ import net.liftweb.http.js._ import JsCmds._ import net.liftweb.common.{Box, Full, Empty} +import common.Full +import xml.Text +import snippet.AjaxMessageMeta /** @@ -219,8 +222,8 @@ object MsgsErrorMeta extends SessionVar[Box[AjaxMessageMeta]](Empty) { * This SessionVar records whether to show id-based messages in * addition to non-id messages. */ -object ShowAll extends SessionVar[Boolean](false) { - override private[liftweb] def magicSessionVar_? = true +object ShowAll extends RequestVar[Boolean](false) { + //override private[liftweb] def magicSessionVar_? = true } /** From 8069db87b10e929e534006ded60d74e738d47a8b Mon Sep 17 00:00:00 2001 From: Tim Nelson Date: Thu, 14 Jun 2012 15:57:18 -0500 Subject: [PATCH 0078/1949] Issue 1275 - Allow overriding of formats in MongoCaseClassField --- .../record/field/MongoCaseClassField.scala | 38 ++++++++++--------- .../net/liftweb/mongodb/record/Fixtures.scala | 23 +++++++---- .../mongodb/record/MongoFieldSpec.scala | 2 +- .../mongodb/record/MongoRecordSpec.scala | 22 ++++++----- .../net/liftweb/mongodb/JObjectParser.scala | 6 ++- .../liftweb/mongodb/JObjectParserSpec.scala | 16 ++++---- 6 files changed, 62 insertions(+), 45 deletions(-) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoCaseClassField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoCaseClassField.scala index fed5a8d221..a3ff4a8e2d 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoCaseClassField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoCaseClassField.scala @@ -11,10 +11,10 @@ * limitations under the License. */ -package net.liftweb -package mongodb -package record -package field +package net.liftweb +package mongodb +package record +package field import net.liftweb.record._ import net.liftweb.record.RecordHelpers.jvalueToJsExp @@ -31,8 +31,10 @@ import net.liftweb.http.js.JsExp class MongoCaseClassField[OwnerType <: Record[OwnerType],CaseType](rec: OwnerType)( implicit mf: Manifest[CaseType]) extends Field[CaseType, OwnerType] with MandatoryTypedField[CaseType] with MongoFieldFlavor[CaseType] { - - implicit val formats = net.liftweb.json.DefaultFormats + + // override this for custom formats + def formats: Formats = DefaultFormats + implicit lazy val _formats = formats override type MyType = CaseType @@ -60,7 +62,7 @@ class MongoCaseClassField[OwnerType <: Record[OwnerType],CaseType](rec: OwnerTyp val jvalue = JObjectParser.serialize(dbo) setFromJValue(jvalue) } - + override def setFromString(in: String): Box[CaseType] = { Helpers.tryo{ JsonParser.parse(in).extract[CaseType] } } @@ -76,22 +78,24 @@ class MongoCaseClassField[OwnerType <: Record[OwnerType],CaseType](rec: OwnerTyp } class MongoCaseClassListField[OwnerType <: Record[OwnerType],CaseType](rec: OwnerType)( implicit mf: Manifest[CaseType]) extends Field[List[CaseType], OwnerType] with MandatoryTypedField[List[CaseType]] with MongoFieldFlavor[List[CaseType]] { - - implicit val formats = net.liftweb.json.DefaultFormats - + + // override this for custom formats + def formats: Formats = DefaultFormats + implicit lazy val _formats = formats + override type MyType = List[CaseType] - + def owner = rec def asXHtml = Text(value.toString) - + def toForm: Box[NodeSeq] = Empty override def defaultValue: MyType = Nil override def optional_? = true - + def asJValue = JArray(value.map(v => Extraction.decompose(v))) - + def setFromJValue(jvalue: JValue): Box[MyType] = jvalue match { case JArray(contents) => setBox(Full(contents.flatMap(s => Helpers.tryo[CaseType]{ s.extract[CaseType] }))) case _ => setBox(Empty) @@ -99,12 +103,12 @@ class MongoCaseClassListField[OwnerType <: Record[OwnerType],CaseType](rec: Owne def asDBObject: DBObject = { val dbl = new BasicDBList - + asJValue match { - case JArray(list) => + case JArray(list) => list.foreach(v => dbl.add(JObjectParser.parse(v.asInstanceOf[JObject]))) } - + dbl } diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala index e7ff2d4d8c..ec88b17892 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala @@ -22,7 +22,8 @@ package fixtures import field._ import common.{Box, Empty, Failure, Full} -import json.ext.JsonBoxSerializer +import json._ +import json.ext.{EnumSerializer, JsonBoxSerializer} import http.SHtml import util.FieldError @@ -243,6 +244,10 @@ class MongoFieldTypeTestRecord private () extends MongoRecord[MongoFieldTypeTest object mandatoryUUIDField extends UUIDField(this) object legacyOptionalUUIDField extends UUIDField(this) { override def optional_? = true } + object mandatoryMongoCaseClassField extends MongoCaseClassField[MongoFieldTypeTestRecord, MongoCaseClassTestObject](this) { + override def formats = owner.meta.formats + } + override def equals(other: Any): Boolean = other match { case that: MongoFieldTypeTestRecord => this.id.value == that.id.value && @@ -251,7 +256,8 @@ class MongoFieldTypeTestRecord private () extends MongoRecord[MongoFieldTypeTest this.mandatoryObjectIdField.value == that.mandatoryObjectIdField.value && this.mandatoryPatternField.value.pattern == that.mandatoryPatternField.value.pattern && this.mandatoryPatternField.value.flags == that.mandatoryPatternField.value.flags && - this.mandatoryUUIDField.value == that.mandatoryUUIDField.value + this.mandatoryUUIDField.value == that.mandatoryUUIDField.value && + this.mandatoryMongoCaseClassField.value == that.mandatoryMongoCaseClassField.value case _ => false } @@ -259,7 +265,7 @@ class MongoFieldTypeTestRecord private () extends MongoRecord[MongoFieldTypeTest } object MongoFieldTypeTestRecord extends MongoFieldTypeTestRecord with MongoMetaRecord[MongoFieldTypeTestRecord] { - override def formats = allFormats + override def formats = allFormats + new EnumSerializer(MyTestEnum) } class PasswordTestRecord private () extends MongoRecord[PasswordTestRecord] with ObjectIdPk[PasswordTestRecord] { @@ -269,7 +275,7 @@ class PasswordTestRecord private () extends MongoRecord[PasswordTestRecord] with } object PasswordTestRecord extends PasswordTestRecord with MongoMetaRecord[PasswordTestRecord] -case class MongoCaseClassTestObject(intField: Int, stringField: String) +case class MongoCaseClassTestObject(intField: Int, stringField: String, enum: MyTestEnum.Value) class ListTestRecord private () extends MongoRecord[ListTestRecord] with UUIDPk[ListTestRecord] { def meta = ListTestRecord @@ -283,7 +289,9 @@ class ListTestRecord private () extends MongoRecord[ListTestRecord] with UUIDPk[ object mandatoryMongoJsonObjectListField extends MongoJsonObjectListField(this, TypeTestJsonObject) object legacyOptionalMongoJsonObjectListField extends MongoJsonObjectListField(this, TypeTestJsonObject) { override def optional_? = true } - object mongoCaseClassListField extends MongoCaseClassListField[ListTestRecord, MongoCaseClassTestObject](this) + object mongoCaseClassListField extends MongoCaseClassListField[ListTestRecord, MongoCaseClassTestObject](this) { + override def formats = owner.meta.formats + } // TODO: More List types @@ -292,14 +300,15 @@ class ListTestRecord private () extends MongoRecord[ListTestRecord] with UUIDPk[ this.id.value == that.id.value && this.mandatoryStringListField.value == that.mandatoryStringListField.value && this.mandatoryIntListField.value == that.mandatoryIntListField.value && - this.mandatoryMongoJsonObjectListField.value == that.mandatoryMongoJsonObjectListField.value + this.mandatoryMongoJsonObjectListField.value == that.mandatoryMongoJsonObjectListField.value && + this.mongoCaseClassListField.value == that.mongoCaseClassListField.value case _ => false } def dirtyFields = this.allFields.filter(_.dirty_?) } object ListTestRecord extends ListTestRecord with MongoMetaRecord[ListTestRecord] { - override def formats = allFormats + override def formats = allFormats + new EnumSerializer(MyTestEnum) } class MapTestRecord private () extends MongoRecord[MapTestRecord] with StringPk[MapTestRecord] { diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala index 98babc7d28..512126dead 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala @@ -323,7 +323,7 @@ object MongoFieldSpec extends Specification("MongoField Specification") with Mon "MongoCaseClassListField" should { "setFromAny a List" in { val rec = ListTestRecord.createRecord - val lst = List(MongoCaseClassTestObject(1,"str1")) + val lst = List(MongoCaseClassTestObject(1,"str1", MyTestEnum.THREE)) rec.mongoCaseClassListField.setFromAny(lst) rec.mongoCaseClassListField.value must_== lst } diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala index 8cfe355f04..4b692f6c69 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala @@ -37,17 +37,17 @@ import com.mongodb._ * Systems under specification for MongoRecord. */ object MongoRecordSpec extends Specification("MongoRecord Specification") with MongoTestKit { - import fixtures._ "MongoRecord field introspection" should { checkMongoIsRunning val rec = MongoFieldTypeTestRecord.createRecord - val allExpectedFieldNames: List[String] = "_id" :: (for { - typeName <- "Date JsonObject ObjectId Pattern UUID".split(" ") - flavor <- "mandatory legacyOptional".split(" ") - } yield flavor + typeName + "Field").toList + val allExpectedFieldNames: List[String] = "_id" :: "mandatoryMongoCaseClassField" :: + (for { + typeName <- "Date JsonObject ObjectId Pattern UUID".split(" ") + flavor <- "mandatory legacyOptional".split(" ") + } yield flavor + typeName + "Field").toList "introspect only the expected fields" in { rec.fields().map(_.name).filterNot(allExpectedFieldNames.contains(_)) must_== Nil @@ -194,6 +194,7 @@ object MongoRecordSpec extends Specification("MongoRecord Specification") with M .mandatoryObjectIdField(ObjectId.get) .mandatoryPatternField(Pattern.compile("^Mo", Pattern.CASE_INSENSITIVE)) .mandatoryUUIDField(UUID.randomUUID) + .mandatoryMongoCaseClassField(MongoCaseClassTestObject(1,"str",MyTestEnum.TWO)) val mfttrJson = ("_id" -> ("$oid" -> mfttr.id.toString)) ~ @@ -206,13 +207,14 @@ object MongoRecordSpec extends Specification("MongoRecord Specification") with M ("mandatoryPatternField" -> (("$regex" -> mfttr.mandatoryPatternField.value.pattern) ~ ("$flags" -> mfttr.mandatoryPatternField.value.flags))) ~ ("legacyOptionalPatternField" -> (None: Option[JObject])) ~ ("mandatoryUUIDField" -> ("$uuid" -> mfttr.mandatoryUUIDField.value.toString)) ~ - ("legacyOptionalUUIDField" -> (None: Option[JObject])) + ("legacyOptionalUUIDField" -> (None: Option[JObject])) ~ + ("mandatoryMongoCaseClassField" -> ("intField" -> 1) ~ ("stringField" -> "str") ~ ("enum" -> 1)) val ltr = ListTestRecord.createRecord .mandatoryStringListField(List("abc", "def", "ghi")) .mandatoryIntListField(List(4, 5, 6)) .mandatoryMongoJsonObjectListField(List(TypeTestJsonObject(1, "jsonobj1", Map("x" -> "1")), TypeTestJsonObject(2, "jsonobj2", Map("x" -> "2")))) - .mongoCaseClassListField(List(MongoCaseClassTestObject(1,"str"))) + .mongoCaseClassListField(List(MongoCaseClassTestObject(1,"str",MyTestEnum.TWO))) val ltrJson = ("_id" -> ("$uuid" -> ltr.id.toString)) ~ @@ -226,7 +228,7 @@ object MongoRecordSpec extends Specification("MongoRecord Specification") with M )) ~ ("legacyOptionalMongoJsonObjectListField" -> List[JObject]()) ~ ("mongoCaseClassListField" -> List( - ("intField" -> 1) ~ ("stringField" -> "str") + ("intField" -> 1) ~ ("stringField" -> "str") ~ ("enum" -> 1) )) val mtr = MapTestRecord.createRecord @@ -685,7 +687,7 @@ object MongoRecordSpec extends Specification("MongoRecord Specification") with M fttr.legacyOptionalStringField(Empty) fttr.legacyOptionalStringField.dirty_? must_== true - fttr.dirtyFields.length must_== 7 + fttr.dirtyFields.length must_== 9 fttr.update fttr.dirtyFields.length must_== 0 @@ -792,7 +794,7 @@ object MongoRecordSpec extends Specification("MongoRecord Specification") with M ltr.mandatoryMongoJsonObjectListField(List(TypeTestJsonObject(1, "jsonobj1", Map("x" -> "1")), TypeTestJsonObject(2, "jsonobj2", Map("x" -> "2")))) ltr.mandatoryMongoJsonObjectListField.dirty_? must_== true - ltr.mongoCaseClassListField(List(MongoCaseClassTestObject(1,"str"))) + ltr.mongoCaseClassListField(List(MongoCaseClassTestObject(1,"str",MyTestEnum.TWO))) ltr.mongoCaseClassListField.dirty_? must_== true ltr.dirtyFields.length must_== 4 diff --git a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/JObjectParser.scala b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/JObjectParser.scala index d664aee401..d03ffa6d3d 100644 --- a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/JObjectParser.scala +++ b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/JObjectParser.scala @@ -36,10 +36,12 @@ object JObjectParser extends SimpleInjector { * * JObjectParser.stringProcessor.default.set((s: String) => s) */ - val stringProcessor = new Inject(() => (s: String) => { + val stringProcessor = new Inject(() => defaultStringProcessor _) {} + + def defaultStringProcessor(s: String): Object = { if (ObjectId.isValid(s)) new ObjectId(s) else s - }) {} + } /* * Parse a JObject into a DBObject diff --git a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/JObjectParserSpec.scala b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/JObjectParserSpec.scala index 6aa12477de..5d65b85b8b 100644 --- a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/JObjectParserSpec.scala +++ b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/JObjectParserSpec.scala @@ -45,14 +45,14 @@ object JObjectParserSpec extends Specification("JObjectParser Specification") { } } "not convert strings to ObjectId when configured not to" in { - JObjectParser.stringProcessor.default.set((s: String) => s) - - val (oid, dbo) = buildTestData - val xval = tryo(dbo.get("x").asInstanceOf[String]) - - xval must notBeEmpty - xval.foreach { x => - x must_== oid.toString + JObjectParser.stringProcessor.doWith((s: String) => s) { + val (oid, dbo) = buildTestData + val xval = tryo(dbo.get("x").asInstanceOf[String]) + + xval must notBeEmpty + xval.foreach { x => + x must_== oid.toString + } } } } From 049d246ee71ca3289f4830b6b7f3a635f30fffc5 Mon Sep 17 00:00:00 2001 From: Tim Nelson Date: Wed, 20 Jun 2012 09:58:09 -0500 Subject: [PATCH 0079/1949] Fixed a test --- .../scala/net/liftweb/mongodb/record/MongoRecordSpec.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala index 4b692f6c69..b52e98844f 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala @@ -18,7 +18,7 @@ package net.liftweb package mongodb package record -import java.util.{Date, UUID} +import java.util.{Date, Locale, UUID} import java.util.regex.Pattern import org.bson.types.ObjectId @@ -621,7 +621,7 @@ object MongoRecordSpec extends Specification("MongoRecord Specification") with M r.mandatoryIntField.is must_== 0 r.legacyOptionalIntField.valueBox must beEmpty r.optionalIntField.is must beEmpty - r.mandatoryLocaleField.is must_== "en_US" + r.mandatoryLocaleField.is must_== Locale.getDefault.toString r.legacyOptionalLocaleField.valueBox must beEmpty r.optionalLocaleField.is must beEmpty r.mandatoryLongField.is must_== 0L From daee7f9717410f7bc5084e0e053b770ac2378401 Mon Sep 17 00:00:00 2001 From: Torsten Uhlmann Date: Sun, 17 Jun 2012 17:59:36 +0200 Subject: [PATCH 0080/1949] Issue #1280: TimeHelpers noTime always returns current Date --- core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala b/core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala index df692e815c..09b0d5c74f 100644 --- a/core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala @@ -255,6 +255,7 @@ trait TimeHelpers { self: ControlHelpers => /** @return a Date object starting at 00:00 from date */ def noTime = { val calendar = Calendar.getInstance + calendar.setTime(date) calendar.set(Calendar.HOUR_OF_DAY, 0) calendar.set(Calendar.MINUTE, 0) calendar.set(Calendar.SECOND, 0) From 6d55cd38d5c22b727af99ae5da4c35c06780720d Mon Sep 17 00:00:00 2001 From: Torsten Uhlmann Date: Sun, 17 Jun 2012 18:46:28 +0200 Subject: [PATCH 0081/1949] Issue #1280: Adding noTime tests. --- .../src/test/scala/net/liftweb/util/TimeHelpersSpec.scala | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/util/src/test/scala/net/liftweb/util/TimeHelpersSpec.scala b/core/util/src/test/scala/net/liftweb/util/TimeHelpersSpec.scala index 1d4c9aad5f..fe3031022f 100644 --- a/core/util/src/test/scala/net/liftweb/util/TimeHelpersSpec.scala +++ b/core/util/src/test/scala/net/liftweb/util/TimeHelpersSpec.scala @@ -128,6 +128,12 @@ object TimeHelpersSpec extends Specification("TimeHelpers Specification") with S "provide a noTime function on Date objects to transform a date into a date at the same day but at 00:00" in { hourFormat(timeNow.noTime) must_== "00:00:00" } + + "make sure noTime does not change the day" in { + dateFormatter.format(0.days.ago.noTime) must_== dateFormatter.format(new Date()) + dateFormatter.format(3.days.ago.noTime) must_== dateFormatter.format(new Date(millis - (3 * 24 * 60 * 60 * 1000))) + } + "provide a day function returning the day of month corresponding to a given date (relative to UTC)" in { day(today.setTimezone(utc).setDay(3).getTime) must_== 3 } From 1f00e152abbb25944440285abdc134c8b91949d4 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Thu, 21 Jun 2012 12:23:34 -0400 Subject: [PATCH 0082/1949] redirectAsyncOnSessionLoss calls client-side functions. The client-side functions are called liftAjax.sessionLost and liftComet.sessionLost, and can be overridden by the user. We still need to hook noCometSessionPage into these somehow so we can have backwards compatibility. --- .../scala/net/liftweb/http/LiftServlet.scala | 45 +++++++++++-------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala index 3f2c9738b2..60c3216c9b 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala @@ -214,22 +214,26 @@ class LiftServlet extends Loggable { }.isDefined } - def isCometOrAjax(req: Req): Boolean = { - val wp = req.path.wholePath - val len = wp.length - - if (len < 2) false + val wp = req.path.wholePath + val pathLen = wp.length + def isComet: Boolean = { + if (pathLen < 2) false else { val kindaComet = wp.head == LiftRules.cometPath val cometScript = (len >= 3 && kindaComet && wp(2) == LiftRules.cometScriptName()) + + (kindaComet && !cometScript) && req.acceptsJavaScript_? + } + } + def isAjax: Boolean = { + if (pathLen < 2) false + else { val kindaAjax = wp.head == LiftRules.ajaxPath - val ajaxScript = len >= 2 && kindaAjax && + val ajaxScript = kindaAjax && wp(1) == LiftRules.ajaxScriptName() - - ((kindaComet && !cometScript) || (kindaAjax && !ajaxScript)) && - req.acceptsJavaScript_? + (kindaAjax && !ajaxScript) && req.acceptsJavaScript_? } } @@ -241,20 +245,23 @@ class LiftServlet extends Loggable { LiftRules.notFoundOrIgnore(req, Empty) } else if (!authPassed_?(req)) { Full(LiftRules.authentication.unauthorizedResponse) - } else if (LiftRules.redirectAjaxOnSessionLoss && !hasSession(sessionIdCalc.id) && isCometOrAjax(req)) { - + } else if (LiftRules.redirectAsyncOnSessionLoss && !hasSession(sessionIdCalc.id) && (isComet || isAjax)) { val theId = sessionIdCalc.id - // okay after 2 attempts to redirect, just - // ignore calls to the comet URL + + // okay after 2 attempts to redirect, just ignore calls to the + // async URL if (recentlyChecked(theId) > 1) { Empty } else { - Full(JavaScriptResponse(js.JE.JsRaw("window.location = " + - (req.request. - header("Referer") openOr - "/").encJs).cmd, Nil, Nil, 200)) + val cmd = + if (isComet) + js.JE.JsRaw("liftComet.sessionLoss(); lift_toWatch = {};").cmd + else + js.JE.JsRaw("liftAjax.sessionLoss()").cmd + + Full(new JsCommands(cmd).toResponse) } - } else + } // if the request is matched is defined in the stateless table, dispatch if (S.statelessInit(req) { tmpStatelessHolder = NamedPF.applyBox(req, @@ -556,7 +563,7 @@ class LiftServlet extends Loggable { sessionActor.getAsyncComponent(name).toList.map(c => (c, toLong(when))) } - if (actors.isEmpty) Left(Full(new JsCommands(new JE.JsRaw("lift_toWatch = {}") with JsCmd :: JsCmds.RedirectTo(LiftRules.noCometSessionPage) :: Nil).toResponse)) + if (actors.isEmpty) Left(Full(new JsCommands(js.JE.JsRaw("liftComet.sessionLoss(); lift_toWatch = {};").cmd).toResponse)) else requestState.request.suspendResumeSupport_? match { case true => { setupContinuation(requestState, sessionActor, actors) From 03a56715617f9cae801c4774ee04997952d179ce Mon Sep 17 00:00:00 2001 From: "Diego Medina (fmpwizard)" Date: Thu, 3 May 2012 07:22:07 -0400 Subject: [PATCH 0083/1949] * Step 1 in removing lift deprecated methods. ** Fixed compiler erros from Ldap code. --- .../liftweb/json/ext/JodaTimeSerializer.scala | 16 +- .../main/scala/net/liftweb/json/Formats.scala | 4 +- .../scala/net/liftweb/util/BindHelpers.scala | 8 +- .../scala/net/liftweb/util/ListHelpers.scala | 2 +- .../src/main/scala/net/liftweb/util/Log.scala | 264 +----------------- .../scala/net/liftweb/util/Slf4jLogger.scala | 118 -------- .../net/liftweb/ldap/LDAPProtoUser.scala | 2 +- .../scala/net/liftweb/ldap/LdapVendor.scala | 49 +--- .../scala/net/liftweb/ldap/LdapSpec.scala | 2 +- .../net/liftweb/mapper/HasManyThrough.scala | 10 +- .../net/liftweb/mapper/MappedForeignKey.scala | 2 +- .../net/liftweb/mapper/MappedPassword.scala | 2 +- .../scala/net/liftweb/mapper/Mapper.scala | 8 +- .../scala/net/liftweb/mapper/MetaMapper.scala | 3 +- .../scala/net/liftweb/mapper/OneToMany.scala | 4 +- .../liftweb/mapper/ProtoExtendedSession.scala | 4 +- .../scala/net/liftweb/mapper/ProtoUser.scala | 12 +- .../scala/net/liftweb/mapper/MapperSpec.scala | 4 +- .../mocks/MockHttpServletRequest.scala | 2 +- .../net/liftweb/builtin/snippet/Embed.scala | 2 +- .../scala/net/liftweb/http/LiftMerge.scala | 2 +- .../scala/net/liftweb/http/LiftRules.scala | 5 +- .../src/main/scala/net/liftweb/http/S.scala | 4 +- .../net/liftweb/http/js/jquery/JqJsCmds.scala | 56 ++-- .../liftweb/http/provider/HTTPProvider.scala | 2 +- .../scala/net/liftweb/mockweb/MockWeb.scala | 2 +- .../liftweb/http/NamedCometPerTabSpec.scala | 2 +- .../net/liftweb/webapptest/ToHeadUsages.scala | 8 +- .../scala/net/liftweb/wizard/Wizard.scala | 2 +- 29 files changed, 95 insertions(+), 506 deletions(-) delete mode 100644 core/util/src/main/scala/net/liftweb/util/Slf4jLogger.scala diff --git a/core/json-ext/src/main/scala/net/liftweb/json/ext/JodaTimeSerializer.scala b/core/json-ext/src/main/scala/net/liftweb/json/ext/JodaTimeSerializer.scala index d4e61cef68..cd1c2937b6 100644 --- a/core/json-ext/src/main/scala/net/liftweb/json/ext/JodaTimeSerializer.scala +++ b/core/json-ext/src/main/scala/net/liftweb/json/ext/JodaTimeSerializer.scala @@ -22,12 +22,12 @@ import org.joda.time._ import JsonDSL._ object JodaTimeSerializers { - def all = List(DurationSerializer(), InstantSerializer(), DateTimeSerializer(), - DateMidnightSerializer(), IntervalSerializer(), LocalDateSerializer(), - LocalTimeSerializer(), PeriodSerializer()) + def all = List(DurationSerializer, InstantSerializer, DateTimeSerializer, + DateMidnightSerializer, IntervalSerializer(), LocalDateSerializer(), + LocalTimeSerializer(), PeriodSerializer) } -case class PeriodSerializer extends CustomSerializer[Period](format => ( +case object PeriodSerializer extends CustomSerializer[Period](format => ( { case JString(p) => new Period(p) case JNull => null @@ -37,7 +37,7 @@ case class PeriodSerializer extends CustomSerializer[Period](format => ( } )) -case class DurationSerializer extends CustomSerializer[Duration](format => ( +case object DurationSerializer extends CustomSerializer[Duration](format => ( { case JInt(d) => new Duration(d.longValue) case JNull => null @@ -47,7 +47,7 @@ case class DurationSerializer extends CustomSerializer[Duration](format => ( } )) -case class InstantSerializer extends CustomSerializer[Instant](format => ( +case object InstantSerializer extends CustomSerializer[Instant](format => ( { case JInt(i) => new Instant(i.longValue) case JNull => null @@ -62,7 +62,7 @@ object DateParser { format.dateFormat.parse(s).map(_.getTime).getOrElse(throw new MappingException("Invalid date format " + s)) } -case class DateTimeSerializer extends CustomSerializer[DateTime](format => ( +case object DateTimeSerializer extends CustomSerializer[DateTime](format => ( { case JString(s) => new DateTime(DateParser.parse(s, format)) case JNull => null @@ -72,7 +72,7 @@ case class DateTimeSerializer extends CustomSerializer[DateTime](format => ( } )) -case class DateMidnightSerializer extends CustomSerializer[DateMidnight](format => ( +case object DateMidnightSerializer extends CustomSerializer[DateMidnight](format => ( { case JString(s) => new DateMidnight(DateParser.parse(s, format)) case JNull => null diff --git a/core/json/src/main/scala/net/liftweb/json/Formats.scala b/core/json/src/main/scala/net/liftweb/json/Formats.scala index 5349531b61..a583cda1ee 100644 --- a/core/json/src/main/scala/net/liftweb/json/Formats.scala +++ b/core/json/src/main/scala/net/liftweb/json/Formats.scala @@ -166,8 +166,8 @@ trait TypeHints { * Chooses most specific class. */ def hintFor(clazz: Class[_]): String = components.filter(_.containsHint_?(clazz)) - .map(th => (th.hintFor(clazz), th.classFor(th.hintFor(clazz)).getOrElse(error("hintFor/classFor not invertible for " + th)))) - .sort((x, y) => delta(x._2, clazz) - delta(y._2, clazz) < 0).head._1 + .map(th => (th.hintFor(clazz), th.classFor(th.hintFor(clazz)).getOrElse(error("hintFor/classFor not invertible for " + th)))) + .sort((x, y) => (delta(x._2, clazz) - delta(y._2, clazz)) < 0).head._1 def classFor(hint: String): Option[Class[_]] = { def hasClass(h: TypeHints) = diff --git a/core/util/src/main/scala/net/liftweb/util/BindHelpers.scala b/core/util/src/main/scala/net/liftweb/util/BindHelpers.scala index 080351a394..f96f918541 100644 --- a/core/util/src/main/scala/net/liftweb/util/BindHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/BindHelpers.scala @@ -816,14 +816,14 @@ trait BindHelpers { implicit def strToBPAssoc(in: String): BindParamAssoc = new BindParamAssoc(in) /** - * transforms a Symbol to a BindParamAssoc object which can be associated to a BindParam object - * using the --> operator.

    - * Usage: 'David --> "name" + * transforms a Symbol to a SuperArrowAssoc object which can be associated to a BindParam object + * using the -> operator.

    + * Usage: 'David -> "name" * * @deprecated use -> instead */ @deprecated("use -> instead") - implicit def symToBPAssoc(in: Symbol): BindParamAssoc = new BindParamAssoc(in.name) + implicit def symToBPAssoc(in: Symbol): SuperArrowAssoc = new SuperArrowAssoc(in.name) /** * Experimental extension to bind which passes in an additional "parameter" from the XHTML to the transform diff --git a/core/util/src/main/scala/net/liftweb/util/ListHelpers.scala b/core/util/src/main/scala/net/liftweb/util/ListHelpers.scala index 1ca15be387..45f1697983 100644 --- a/core/util/src/main/scala/net/liftweb/util/ListHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/ListHelpers.scala @@ -168,7 +168,7 @@ trait ListHelpers { * Convert a java.util.Enumeration to a List[T] */ def enumToList[T](enum: java.util.Enumeration[T]): List[T] = - scala.collection.JavaConversions.asIterator(enum).toList + scala.collection.JavaConversions.enumerationAsScalaIterator(enum).toList /** * Convert a java.util.Enumeration to a List[String] using the toString method on each element diff --git a/core/util/src/main/scala/net/liftweb/util/Log.scala b/core/util/src/main/scala/net/liftweb/util/Log.scala index d6d7d92705..b98a96d48d 100644 --- a/core/util/src/main/scala/net/liftweb/util/Log.scala +++ b/core/util/src/main/scala/net/liftweb/util/Log.scala @@ -17,11 +17,8 @@ package net.liftweb package util -import java.util.Properties import Helpers._ -import org.apache.log4j._ -import org.apache.log4j.xml._ -import common.{Logger => _, _} +import common._ /** * Function object that can be used in Logger.setup @@ -58,268 +55,17 @@ object LoggingAutoConfigurer { def apply(): () => Unit = () => { // Try to configure log4j only if we find the SLF4J Log4j bindings findClass("Log4jLoggerAdapter",List("org.slf4j.impl")) map {_ => - // Avoid double init - if (!LogBoot.log4JInited) { - LogBoot.log4JInited = true - findTheFile("log4j.xml", "log4j.props") match { - case Full(url) => _root_.net.liftweb.common.Log4j.withFile(url)() - case _ => _root_.net.liftweb.common.Log4j.withConfig(LogBoot.defaultProps)() - } + findTheFile("log4j.xml", "log4j.props") match { + case Full(url) => _root_.net.liftweb.common.Log4j.withFile(url)() + case _ => _root_.net.liftweb.common.Log4j.withConfig(Log4j.defaultProps)() } } + // Try to configure logback findClass("Logger", List("ch.qos.logback.classic")) map {_ => findTheFile("logback.xml") map {url => Logback.withFile(url)()} } - () } } - -/** - * A thin wrapper around log4j. - * - * @deprecated Use net.liftweb.common.Logger - */ -@deprecated("Use net.liftweb.common.Logger") object Log extends LiftLogger { - lazy val rootLogger: LiftLogger = LogBoot.loggerByName("lift") - - @deprecated("Use net.liftweb.common.Logger") override def trace(msg: => AnyRef) = rootLogger.trace(msg) - @deprecated("Use net.liftweb.common.Logger") override def trace(msg: => AnyRef, t: => Throwable) = rootLogger.trace(msg, t) - - @deprecated("Use net.liftweb.common.Logger") override def assertLog(assertion: Boolean, msg: => String) = rootLogger.assertLog(assertion, msg) - - @deprecated("Use net.liftweb.common.Logger") override def isEnabledFor(level: LiftLogLevels.Value) = rootLogger.isEnabledFor(level) - @deprecated("Use net.liftweb.common.Logger") override def isDebugEnabled = rootLogger.isDebugEnabled - @deprecated("Use net.liftweb.common.Logger") override def debug(msg: => AnyRef) = rootLogger.debug(msg) - // override def debugF(msg: => AnyRef) = debug(msg) - @deprecated("Use net.liftweb.common.Logger") override def debug(msg: => AnyRef, t: => Throwable) = rootLogger.debug(msg, t) - - @deprecated("Use net.liftweb.common.Logger") override def isErrorEnabled = rootLogger.isEnabledFor(LiftLogLevels.Error) - @deprecated("Use net.liftweb.common.Logger") override def error(msg: => AnyRef) = rootLogger.error(msg) - // override def errorF(msg: => AnyRef) = error(msg) - @deprecated("Use net.liftweb.common.Logger") override def error(msg: => AnyRef, t: => Throwable) = rootLogger.error(msg, t) - - @deprecated("Use net.liftweb.common.Logger") override def fatal(msg: AnyRef) = rootLogger.fatal(msg) - // override def fatalF(msg: AnyRef) = fatal(msg) - @deprecated("Use net.liftweb.common.Logger") override def fatal(msg: AnyRef, t: Throwable) = rootLogger.fatal(msg, t) - - @deprecated("Use net.liftweb.common.Logger") override def level = rootLogger.level - @deprecated("Use net.liftweb.common.Logger") override def level_=(level: LiftLogLevels.Value) = rootLogger.level = level - @deprecated("Use net.liftweb.common.Logger") override def name = rootLogger.name - // def parent = rootLogger.parent - - @deprecated("Use net.liftweb.common.Logger") override def isInfoEnabled = rootLogger.isInfoEnabled - @deprecated("Use net.liftweb.common.Logger") override def info(msg: => AnyRef) = rootLogger.info(msg) - /** - * @deprecated Use Schemifier.infoF - */ - @deprecated("Use Schemifier.infoF") def infoF(msg: => AnyRef) = info(msg) - @deprecated("Use net.liftweb.common.Logger") override def info(msg: => AnyRef, t: => Throwable) = rootLogger.info(msg, t) - - - // def isEnabledFor(level: Priority) = rootLogger.isEnabledFor(level) - - @deprecated("Use net.liftweb.common.Logger") override def isWarnEnabled = rootLogger.isWarnEnabled - @deprecated("Use net.liftweb.common.Logger") override def warn(msg: => AnyRef) = rootLogger.warn(msg) - // def warnF(msg: => AnyRef) = warn(msg) - @deprecated("Use net.liftweb.common.Logger") override def warn(msg: => AnyRef, t: => Throwable) = rootLogger.warn(msg, t) - - @deprecated("Use net.liftweb.common.Logger") def never(msg: => AnyRef) {} - - /** - * @deprecated Use Schemifier.neverF - */ - @deprecated("Use Schemifier.neverF") def neverF(msg: => AnyRef) {} - @deprecated("Use net.liftweb.common.Logger") def never(msg: => AnyRef, t: => Throwable) {} - - @deprecated("Use net.liftweb.common.Logger") override def isTraceEnabled = rootLogger.isTraceEnabled -} - -/** - * This object provides logging setup utilities. - * - * To provide your own log4j configuration,add either a log4j.props file or log4j.xml - * file to your classpath. - * - * If you want to provide a configuration file for a subset of your application - * or for a specifig environment, Lift expects configuration files to be named - * in a manner relating to the context in which they are being used. The standard - * name format is: - * - *

    - *   modeName.hostName.userName.filename.extension
    - * 
    - * - * with hostName and userName being optional, and modeName being one of - * 'test', 'staging', 'production', 'pilot', 'profile', or 'default. - * Thus, if you name your log4j config file 'default.log4j.xml' or - * 'default.log4j.props' it will be picked up correctly. - */ -@deprecated("Use net.liftweb.common.Logger") object LogBoot { - @deprecated("Use net.liftweb.common.Logger") lazy val checkConfig: Boolean = loggerSetup() - - // Prevent double initialization of log4j by new/old logging code - private[util] var log4JInited = false - - @deprecated("Use net.liftweb.common.Logger") var loggerSetup: () => Boolean = _log4JSetup _ - - @deprecated("Use net.liftweb.common.Logger") var defaultProps = - """ - - - - - - - - - - - """ - - @deprecated("Use net.liftweb.common.Logger") def _log4JSetup() = - { - if (log4JInited) { - true - } else { - log4JInited = true - def log4jIsConfigured = LogManager.getLoggerRepository.getCurrentLoggers.hasMoreElements - def findTheFile: Box[(java.net.URL, String)] = (first(Props.toTry.flatMap(f => List(f()+"log4j.props", f()+"log4j.xml"))) - (name =>tryo(getClass.getResource(name)).filter(_ ne null).map(s => (s, name)))) - - val (log4jUrl, fileName) = findTheFile match { - case Full((url, name)) => (Full(url), Full(name)) - case _ => (Empty, Empty) - } - - for (url <- log4jUrl; name <- fileName) { - if (name.endsWith(".xml")) { - val domConf = new DOMConfigurator - domConf.doConfigure(url, LogManager.getLoggerRepository()) - } else PropertyConfigurator.configure(url) - } - - if (!log4jUrl.isDefined && !log4jIsConfigured) { - val domConf = new DOMConfigurator - val defPropBytes = defaultProps.toString.getBytes("UTF-8") - val is = new java.io.ByteArrayInputStream(defPropBytes) - domConf.doConfigure(is, LogManager.getLoggerRepository()) - } - true - } - } - - private def _loggerCls(clz: Class[AnyRef]): LiftLogger = if (checkConfig) new Log4JLogger(Logger.getLogger(clz)) else NullLogger - private def _logger(name: String): LiftLogger = if (checkConfig) new Log4JLogger(Logger.getLogger(name)) else NullLogger - - @deprecated("Use net.liftweb.common.Logger") var loggerByName: String => LiftLogger = _logger - @deprecated("Use net.liftweb.common.Logger") var loggerByClass: Class[AnyRef] => LiftLogger = _loggerCls _ -} - -@deprecated("Use net.liftweb.common.Logger") object NullLogger extends LiftLogger { - -} -/** - * @deprecated Use net.liftweb.common.Logger - */ -@deprecated("Use net.liftweb.common.Logger") trait LiftLogger { - def isTraceEnabled: Boolean = false - def trace(msg: => AnyRef): Unit = () - def trace(msg: => AnyRef, t: => Throwable): Unit = () - - def assertLog(assertion: Boolean, msg: => String): Unit = () - - def isDebugEnabled: Boolean = false - def debug(msg: => AnyRef): Unit = () - def debug(msg: => AnyRef, t: => Throwable): Unit = () - - def isErrorEnabled: Boolean = false - def error(msg: => AnyRef): Unit = () - def error(msg: => AnyRef, t: => Throwable): Unit = () - - def fatal(msg: AnyRef): Unit = () - def fatal(msg: AnyRef, t: Throwable): Unit = () - - def level: LiftLogLevels.Value = LiftLogLevels.Off - def level_=(level: LiftLogLevels.Value): Unit = () - def name: String = "Null" - // def parent = logger.getParent - - def isInfoEnabled: Boolean = false - def info(msg: => AnyRef): Unit = () - def info(msg: => AnyRef, t: => Throwable): Unit = () - - def isEnabledFor(level: LiftLogLevels.Value): Boolean = false - - def isWarnEnabled: Boolean = false - def warn(msg: => AnyRef): Unit = () - def warn(msg: => AnyRef, t: => Throwable): Unit = () -} - -@deprecated("Use net.liftweb.common.Logger") object LiftLogLevels extends Enumeration { - @deprecated("Use net.liftweb.common.Logger") val All = Value(1, "All") - @deprecated("Use net.liftweb.common.Logger") val Trace = Value(3, "Trace") - @deprecated("Use net.liftweb.common.Logger") val Debug = Value(5, "Debug") - @deprecated("Use net.liftweb.common.Logger") val Warn = Value(7, "Warn") - @deprecated("Use net.liftweb.common.Logger") val Error = Value(9, "Error") - @deprecated("Use net.liftweb.common.Logger") val Fatal = Value(11, "Fatal") - @deprecated("Use net.liftweb.common.Logger") val Info = Value(13, "Info") - @deprecated("Use net.liftweb.common.Logger") val Off = Value(15, "Off") -} - -@deprecated("Use net.liftweb.common.Logger") class Log4JLogger(val logger: Logger) extends LiftLogger { - override def isTraceEnabled = logger.isTraceEnabled - override def trace(msg: => AnyRef) = if (isTraceEnabled) logger.trace(msg) - override def trace(msg: => AnyRef, t: => Throwable) = if (isTraceEnabled) logger.trace(msg, t) - - override def assertLog(assertion: Boolean, msg: => String) = if (assertion) logger.assertLog(assertion, msg) - - override def isDebugEnabled = logger.isDebugEnabled - override def debug(msg: => AnyRef) = if (isDebugEnabled) logger.debug(msg) - override def debug(msg: => AnyRef, t: => Throwable) = if (isDebugEnabled) logger.debug(msg, t) - - override def isErrorEnabled = logger.isEnabledFor(Level.ERROR) - override def error(msg: => AnyRef) = if (isErrorEnabled) logger.error(msg) - override def error(msg: => AnyRef, t: => Throwable) = if (isErrorEnabled) logger.error(msg, t) - - override def fatal(msg: AnyRef) = logger.fatal(msg) - override def fatal(msg: AnyRef, t: Throwable) = logger.fatal(msg, t) - - override def level = logger.getLevel match { - case Level.ALL => LiftLogLevels.All - case Level.DEBUG => LiftLogLevels.Debug - case Level.ERROR => LiftLogLevels.Error - case Level.WARN => LiftLogLevels.Warn - case Level.FATAL => LiftLogLevels.Fatal - case Level.INFO => LiftLogLevels.Info - case Level.TRACE => LiftLogLevels.Trace - case Level.OFF => LiftLogLevels.Off - } - - val liftToLog4J: PartialFunction[LiftLogLevels.Value, Level] = { - case LiftLogLevels.All => Level.ALL - case LiftLogLevels.Debug => Level.DEBUG - case LiftLogLevels.Error => Level.ERROR - case LiftLogLevels.Warn => Level.WARN - case LiftLogLevels.Fatal => Level.FATAL - case LiftLogLevels.Info => Level.INFO - case LiftLogLevels.Trace => Level.TRACE - case LiftLogLevels.Off => Level.OFF - } - - override def isEnabledFor(level: LiftLogLevels.Value): Boolean = logger.isEnabledFor(liftToLog4J(level)) - override def level_=(level: LiftLogLevels.Value) = logger.setLevel(liftToLog4J(level) ) - override def name = logger.getName - - override def isInfoEnabled = logger.isInfoEnabled - override def info(msg: => AnyRef) = if (isInfoEnabled) logger.info(msg) - override def info(msg: => AnyRef, t: => Throwable) = if (isInfoEnabled) logger.info(msg, t) - - def isEnabledFor(level: Priority) = logger.isEnabledFor(level) - - override def isWarnEnabled = isEnabledFor(Level.WARN) - override def warn(msg: => AnyRef) = if (isWarnEnabled) logger.warn(msg) - override def warn(msg: => AnyRef, t: => Throwable) = if (isWarnEnabled) logger.warn(msg, t) -} diff --git a/core/util/src/main/scala/net/liftweb/util/Slf4jLogger.scala b/core/util/src/main/scala/net/liftweb/util/Slf4jLogger.scala deleted file mode 100644 index 1d7f8cb860..0000000000 --- a/core/util/src/main/scala/net/liftweb/util/Slf4jLogger.scala +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2007-2011 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 util - -import org.slf4j.{Logger, LoggerFactory} - -/** - * Object use to configure lift to use slf4j for as internal logging. - * Note that slf4j back-end should be configured previously to use Slf4jLogBoot. - * To use it, call Slf4jLogBoot.enable(): - *
    - * package bootstrap.liftweb
    - *
    - * import net.liftweb.util._
    - * ...
    - * class Boot {
    - *   def boot {
    - *     Slf4jLogBoot.enable()
    - *     ...
    - * 
    - * You have to add slf4j (and/or the backend) as dependency of your webapp, and you could exclude log4j. - * I (DavidB) highly recommand using logback as backend for slf4j. - * ex, add in your pom.xml: - *
    - *    <dependency>
    - *      <groupId>net.liftweb</groupId>
    - *      <artifactId>lift-webkit</artifactId>
    - *      <version>0.7</version>
    - *      <exclusions>
    - *        <exclusion>
    - *          <groupId>log4j</groupId>
    - *          <artifactId>log4j</artifactId>
    - *        </exclusion>
    - *      </exclusions>
    - *    </dependency>
    - *    <dependency>
    - *      <groupId>ch.qos.logback</groupId>
    - *      <artifactId>logback-classic</artifactId>
    - *      <version>0.9.8</version>
    - *    </dependency>
    - * 
    - * - */ -@deprecated("use net.liftweb.common.Logger") object Slf4jLogBoot { - private def _loggerByClass(clz: Class[AnyRef]): LiftLogger = new Slf4jLogger(LoggerFactory.getLogger(clz)) - private def _loggerByName(name: String): LiftLogger = new Slf4jLogger(LoggerFactory.getLogger(name)) - - /** - * enable slf4j as logging system for lift (internal, not for lift based application) - */ - def enable() { - LogBoot.loggerByName = _loggerByName - LogBoot.loggerByClass = _loggerByClass - } -} - -/** - * Adapter use internaly by lift as Logger, if Slf4jLogBoot is enabled. - * @see Slf4jLogBoot - */ -@deprecated("use net.liftweb.common.Logger") class Slf4jLogger(val logger: Logger) extends LiftLogger { - override def isTraceEnabled = logger.isTraceEnabled - override def trace(msg: => AnyRef) = if (isTraceEnabled) logger.trace(String.valueOf(msg)) - override def trace(msg: => AnyRef, t: => Throwable) = if (isTraceEnabled) logger.trace(String.valueOf(msg), t) - - override def assertLog(assertion: Boolean, msg: => String) = if (assertion) info(msg) - - override def isDebugEnabled = logger.isDebugEnabled - override def debug(msg: => AnyRef) = if (isDebugEnabled) logger.debug(String.valueOf(msg)) - override def debug(msg: => AnyRef, t: => Throwable) = if (isDebugEnabled) logger.debug(String.valueOf(msg), t) - - override def isErrorEnabled = logger.isErrorEnabled - override def error(msg: => AnyRef) = if (isErrorEnabled) logger.error(String.valueOf(msg)) - override def error(msg: => AnyRef, t: => Throwable) = if (isErrorEnabled) logger.error(String.valueOf(msg), t) - - override def fatal(msg: AnyRef) = error(msg) - override def fatal(msg: AnyRef, t: Throwable) = error(msg, t) - - override def level = LiftLogLevels.All - - override def isEnabledFor(level: LiftLogLevels.Value): Boolean = level match { - case LiftLogLevels.All => isTraceEnabled - case LiftLogLevels.Debug => isDebugEnabled - case LiftLogLevels.Error => isErrorEnabled - case LiftLogLevels.Warn => isWarnEnabled - case LiftLogLevels.Fatal => isErrorEnabled - case LiftLogLevels.Info => isInfoEnabled - case LiftLogLevels.Trace => isTraceEnabled - case LiftLogLevels.Off => !isErrorEnabled - } - -// override def level_=(level: LiftLogLevels.Value) = logger.setLevel(liftToLog4J(level) ) - override def name = logger.getName - - override def isInfoEnabled = logger.isInfoEnabled - override def info(msg: => AnyRef) = if (isInfoEnabled) logger.info(String.valueOf(msg)) - override def info(msg: => AnyRef, t: => Throwable) = if (isInfoEnabled) logger.info(String.valueOf(msg), t) - - override def isWarnEnabled = logger.isWarnEnabled - override def warn(msg: => AnyRef) = if (isWarnEnabled) logger.warn(String.valueOf(msg)) - override def warn(msg: => AnyRef, t: => Throwable) = if (isWarnEnabled) logger.warn(String.valueOf(msg), t) -} - diff --git a/persistence/ldap/src/main/scala/net/liftweb/ldap/LDAPProtoUser.scala b/persistence/ldap/src/main/scala/net/liftweb/ldap/LDAPProtoUser.scala index 1cdc0e0a16..6b97c7ca3a 100644 --- a/persistence/ldap/src/main/scala/net/liftweb/ldap/LDAPProtoUser.scala +++ b/persistence/ldap/src/main/scala/net/liftweb/ldap/LDAPProtoUser.scala @@ -126,7 +126,7 @@ trait MetaLDAPProtoUser[ModelType <: LDAPProtoUser[ModelType]] extends MetaMegaP if (users.size >= 1) { val userDn = users(0) if (ldapVendor.bindUser(userDn, password)) { - val completeDn = userDn + "," + ldapVendor.parameters().get("ldap.base").getOrElse("") + val completeDn = userDn + "," + ldapVendor.ldapBaseDn.vend //configure().get("ldap.base").getOrElse("") logUserIn(this) bindAttributes(_getUserAttributes(completeDn)) diff --git a/persistence/ldap/src/main/scala/net/liftweb/ldap/LdapVendor.scala b/persistence/ldap/src/main/scala/net/liftweb/ldap/LdapVendor.scala index 7c99b80032..b205d90a4b 100644 --- a/persistence/ldap/src/main/scala/net/liftweb/ldap/LdapVendor.scala +++ b/persistence/ldap/src/main/scala/net/liftweb/ldap/LdapVendor.scala @@ -28,37 +28,7 @@ import scala.collection.mutable.ListBuffer import scala.collection.JavaConversions._ import util.{ControlHelpers,Props,SimpleInjector,ThreadGlobal} -import common.{Box,Empty,Full,Loggable} - -/** - * A simple extension to LDAPVendor to provide configuration - * methods. The class, parameters* methods and variable are now - * deprecated in favor of the configure methods on LDAPVendor. - * See LDAPVendor for more details. - * - * @see LDAPVendor - */ -@deprecated("Instantiate directly from LDAPVendor") -object SimpleLDAPVendor extends LDAPVendor { - @deprecated("Use the configure(filename : String) method") - def parametersFromFile(filename: String) : Map[String, String] = { - val input = new FileInputStream(filename) - val params = parametersFromStream(input) - input.close() - params - } - - @deprecated("Use the configure(stream : InputStream method") - def parametersFromStream(stream: InputStream) : Map[String, String] = { - val p = new Properties() - p.load(stream) - - propertiesToMap(p) - } - - @deprecated("Use the configure() method") - def setupFromBoot = configure() -} +import common._ /** * This class provides functionality to allow us to search and @@ -126,19 +96,7 @@ class LDAPVendor extends Loggable with SimpleInjector { final val DEFAULT_RETRY_INTERVAL = 5000 final val DEFAULT_MAX_RETRIES = 6 - // =========== Configuration =========== - @deprecated("Use the configure(...) methods") - def parameters : () => Map[String,String] = - if (internal_config.isEmpty) { - () => null - } else { - () => internal_config - } - @deprecated("Use the configure(...) methods") - def parameters_= (newParams : () => Map[String,String]) { - internal_config = processConfig(newParams()) - } /** * Configure straight from the Props object. This allows @@ -393,12 +351,13 @@ class LDAPVendor extends Loggable with SimpleInjector { context = (currentInitialContext.box, testLookup.vend) match { // If we don't want to test an existing context, just return it case (Full(ctxt), Empty) => Full(ctxt) + case (Full(ctxt), Failure(_,_,_)) => Full(ctxt) case (Full(ctxt), Full(test)) => { logger.debug("Testing InitialContext prior to returning") ctxt.lookup(test) Full(ctxt) } - case (Empty,_) => { + case (Empty | Failure(_,_,_) ,_) => { // We'll just allocate a new InitialContext to the thread currentInitialContext(openInitialContext()) @@ -425,7 +384,7 @@ class LDAPVendor extends Loggable with SimpleInjector { // We have a final check on the context before returning context match { case Full(ctxt) => ctxt - case Empty => throw new CommunicationException("Failed to connect to '%s' after %d attempts". + case Empty | Failure(_,_,_) => throw new CommunicationException("Failed to connect to '%s' after %d attempts". format(ldapUrl.vend, attempts)) } } diff --git a/persistence/ldap/src/test/scala/net/liftweb/ldap/LdapSpec.scala b/persistence/ldap/src/test/scala/net/liftweb/ldap/LdapSpec.scala index 7a54076026..2c7a6d49ee 100644 --- a/persistence/ldap/src/test/scala/net/liftweb/ldap/LdapSpec.scala +++ b/persistence/ldap/src/test/scala/net/liftweb/ldap/LdapSpec.scala @@ -47,7 +47,7 @@ object LdapSpec extends Specification("LDAP Specification") { val service = new DefaultDirectoryService val ldap = new LdapServer - lazy val workingDir = Box[String](System.getProperty("apacheds.working.dir")) + lazy val workingDir = Box.legacyNullTest(System.getProperty("apacheds.working.dir")) /* * The following is taken from: diff --git a/persistence/mapper/src/main/scala/net/liftweb/mapper/HasManyThrough.scala b/persistence/mapper/src/main/scala/net/liftweb/mapper/HasManyThrough.scala index 33a1e66503..fdc5fa76b9 100644 --- a/persistence/mapper/src/main/scala/net/liftweb/mapper/HasManyThrough.scala +++ b/persistence/mapper/src/main/scala/net/liftweb/mapper/HasManyThrough.scala @@ -67,13 +67,13 @@ class HasManyThrough[From <: KeyedMapper[ThroughType, From], } override def beforeDelete { - through.findAll(By(throughFromField, owner.primaryKeyField)).foreach { + through.findAll(By(throughFromField, owner.primaryKeyField.get)).foreach { toDelete => toDelete.delete_! } } override def afterUpdate { - val current = through.findAll(By(throughFromField,owner.primaryKeyField)) + val current = through.findAll(By(throughFromField, owner.primaryKeyField.get)) val newKeys = new HashSet[ThroughType]; @@ -82,11 +82,11 @@ class HasManyThrough[From <: KeyedMapper[ThroughType, From], toDelete.foreach(_.delete_!) val oldKeys = new HashSet[ThroughType]; - current.foreach(i => oldKeys += throughToField.actualField(i)) + current.foreach(i => oldKeys += throughToField.actualField(i).get) theSetList.toList.distinct.filter(i => !oldKeys.contains(i)).foreach { i => val toCreate = through.createInstance - throughFromField.actualField(toCreate).set(owner.primaryKeyField) + throughFromField.actualField(toCreate).set(owner.primaryKeyField.get) throughToField.actualField(toCreate).set(i) toCreate.save } @@ -99,7 +99,7 @@ class HasManyThrough[From <: KeyedMapper[ThroughType, From], override def afterCreate { theSetList.toList.distinct.foreach { i => val toCreate = through.createInstance - throughFromField.actualField(toCreate)(owner.primaryKeyField) + throughFromField.actualField(toCreate)(owner.primaryKeyField.get) throughToField.actualField(toCreate)(i) toCreate.save } diff --git a/persistence/mapper/src/main/scala/net/liftweb/mapper/MappedForeignKey.scala b/persistence/mapper/src/main/scala/net/liftweb/mapper/MappedForeignKey.scala index b4050c08d1..d377fb07b9 100644 --- a/persistence/mapper/src/main/scala/net/liftweb/mapper/MappedForeignKey.scala +++ b/persistence/mapper/src/main/scala/net/liftweb/mapper/MappedForeignKey.scala @@ -212,7 +212,7 @@ with LifecycleCallbacks { @deprecated("Functionality folded into MappedForeignKey, so just use MappedLongForeignKey. Will be removed in 2.5") class LongMappedMapper[T<:Mapper[T], O<:KeyedMapper[Long,O]](theOwner: T, foreign: => KeyedMetaMapper[Long, O]) - extends MappedLongForeignKey[T,O](theOwner, foreign) with LongMappedForeignMapper[T,O] + extends MappedLongForeignKey[T,O](theOwner, foreign) @deprecated("Functionality folded into MappedForeignKey, so just use MappedLongForeignKey. Will be removed in 2.5") diff --git a/persistence/mapper/src/main/scala/net/liftweb/mapper/MappedPassword.scala b/persistence/mapper/src/main/scala/net/liftweb/mapper/MappedPassword.scala index f4bf88c171..fbb5e592ea 100644 --- a/persistence/mapper/src/main/scala/net/liftweb/mapper/MappedPassword.scala +++ b/persistence/mapper/src/main/scala/net/liftweb/mapper/MappedPassword.scala @@ -51,7 +51,7 @@ extends MappedField[String, T] { def salt = this.salt_i private var password = FatLazy(defaultValue) - private val salt_i = FatLazy(Safe.randomString(16)) + private val salt_i = FatLazy(util.Safe.randomString(16)) private var invalidPw = false private var invalidMsg = "" diff --git a/persistence/mapper/src/main/scala/net/liftweb/mapper/Mapper.scala b/persistence/mapper/src/main/scala/net/liftweb/mapper/Mapper.scala index 9b94a0081a..3b05d281b8 100644 --- a/persistence/mapper/src/main/scala/net/liftweb/mapper/Mapper.scala +++ b/persistence/mapper/src/main/scala/net/liftweb/mapper/Mapper.scala @@ -44,7 +44,7 @@ trait Mapper[A<:Mapper[A]] extends BaseMapper { def getSingleton : MetaMapper[A]; final def safe_? : Boolean = { - Safe.safe_?(System.identityHashCode(this)) + util.Safe.safe_?(System.identityHashCode(this)) } def dbName:String = getSingleton.dbName @@ -52,7 +52,7 @@ trait Mapper[A<:Mapper[A]] extends BaseMapper { implicit def thisToMappee(in: Mapper[A]): A = this.asInstanceOf[A] def runSafe[T](f : => T) : T = { - Safe.runSafe(System.identityHashCode(this))(f) + util.Safe.runSafe(System.identityHashCode(this))(f) } def connectionIdentifier(id: ConnectionIdentifier): A = { @@ -72,7 +72,7 @@ trait Mapper[A<:Mapper[A]] extends BaseMapper { * @param func - the function to perform after the commit happens */ def doPostCommit(func: () => Unit): A = { - DB.appendPostFunc(connectionIdentifier, func) + DB.appendPostTransaction(connectionIdentifier, dontUse => func()) this } @@ -404,7 +404,7 @@ trait KeyedMapper[KeyType, OwnerType<:KeyedMapper[KeyType, OwnerType]] extends M override def comparePrimaryKeys(other: OwnerType) = primaryKeyField.is == other.primaryKeyField.is - def reload: OwnerType = getSingleton.find(By(primaryKeyField, primaryKeyField)) openOr this + def reload: OwnerType = getSingleton.find(By(primaryKeyField, primaryKeyField.get)) openOr this def asSafeJs(f: KeyObfuscator): JsExp = getSingleton.asSafeJs(this, f) diff --git a/persistence/mapper/src/main/scala/net/liftweb/mapper/MetaMapper.scala b/persistence/mapper/src/main/scala/net/liftweb/mapper/MetaMapper.scala index b8d321a08e..d12014429b 100644 --- a/persistence/mapper/src/main/scala/net/liftweb/mapper/MetaMapper.scala +++ b/persistence/mapper/src/main/scala/net/liftweb/mapper/MetaMapper.scala @@ -1363,8 +1363,7 @@ trait MetaMapper[A<:Mapper[A]] extends BaseMetaMapper with Mapper[A] { case pcf => if (!inst.addedPostCommit) { - DB.appendPostFunc(inst.connectionIdentifier, - () => (clearPCFunc :: pcf).foreach(_(inst))) + DB.appendPostTransaction(inst.connectionIdentifier, dontUse => (clearPCFunc :: pcf).foreach(_(inst))) inst.addedPostCommit = true } } diff --git a/persistence/mapper/src/main/scala/net/liftweb/mapper/OneToMany.scala b/persistence/mapper/src/main/scala/net/liftweb/mapper/OneToMany.scala index 8e965389d9..e430df79e4 100644 --- a/persistence/mapper/src/main/scala/net/liftweb/mapper/OneToMany.scala +++ b/persistence/mapper/src/main/scala/net/liftweb/mapper/OneToMany.scala @@ -77,7 +77,7 @@ trait OneToMany[K,T<:KeyedMapper[K, T]] extends KeyedMapper[K,T] { this: T => class MappedOneToMany[O <: Mapper[O]](meta: MetaMapper[O], foreign: MappedForeignKey[K,O,T], qp: QueryParam[O]*) extends MappedOneToManyBase[O]( ()=>{ - val ret = meta.findAll(By(foreign, primaryKeyField) :: qp.toList : _*) + val ret = meta.findAll(By(foreign, primaryKeyField.get) :: qp.toList : _*) for(child <- ret) { foreign.actualField(child).asInstanceOf[MappedForeignKey[K,O,T]].primeObj(net.liftweb.common.Full(OneToMany.this : T)) } @@ -121,7 +121,7 @@ trait OneToMany[K,T<:KeyedMapper[K, T]] extends KeyedMapper[K,T] { this: T => case f: MappedLongForeignKey[O,T] with MappedForeignKey[_,_,T] => f.apply(OneToMany.this) case f => - f.set(OneToMany.this.primaryKeyField) + f.set(OneToMany.this.primaryKeyField.get) } if(!OneToMany.this.saved_?) unlinked ::= e diff --git a/persistence/mapper/src/main/scala/net/liftweb/mapper/ProtoExtendedSession.scala b/persistence/mapper/src/main/scala/net/liftweb/mapper/ProtoExtendedSession.scala index 34143dedd3..9845809070 100644 --- a/persistence/mapper/src/main/scala/net/liftweb/mapper/ProtoExtendedSession.scala +++ b/persistence/mapper/src/main/scala/net/liftweb/mapper/ProtoExtendedSession.scala @@ -90,7 +90,7 @@ KeyedMetaMapper[Long, T] { def userDidLogin(uid: UserType) { userDidLogout(Full(uid)) val inst = create.userId(uid.userIdAsString).saveMe - val cookie = HTTPCookie(CookieName, inst.cookieId). + val cookie = HTTPCookie(CookieName, inst.cookieId.get). setMaxAge(((inst.expiration.is - millis) / 1000L).toInt). setPath("/") S.addCookie(cookie) @@ -117,7 +117,7 @@ KeyedMetaMapper[Long, T] { case (Empty, Full(c)) => find(By(cookieId, c.value openOr "")) match { case Full(es) if es.expiration.is < millis => es.delete_! - case Full(es) => logUserIdIn(es.userId) + case Full(es) => logUserIdIn(es.userId.get) case _ => } diff --git a/persistence/mapper/src/main/scala/net/liftweb/mapper/ProtoUser.scala b/persistence/mapper/src/main/scala/net/liftweb/mapper/ProtoUser.scala index aa3eae52da..8807ccfbf5 100644 --- a/persistence/mapper/src/main/scala/net/liftweb/mapper/ProtoUser.scala +++ b/persistence/mapper/src/main/scala/net/liftweb/mapper/ProtoUser.scala @@ -229,27 +229,27 @@ trait MetaMegaProtoUser[ModelType <: MegaProtoUser[ModelType]] extends KeyedMeta /** * Return the user's first name */ - def getFirstName: String = in.firstName + def getFirstName: String = in.firstName.get /** * Return the user's last name */ - def getLastName: String = in.lastName + def getLastName: String = in.lastName.get /** * Get the user's email */ - def getEmail: String = in.email + def getEmail: String = in.email.get /** * Is the user a superuser */ - def superUser_? : Boolean = in.superUser + def superUser_? : Boolean = in.superUser.get /** * Has the user been validated? */ - def validated_? : Boolean = in.validated + def validated_? : Boolean = in.validated.get /** * Does the supplied password match the actual password? @@ -273,7 +273,7 @@ trait MetaMegaProtoUser[ModelType <: MegaProtoUser[ModelType]] extends KeyedMeta /** * Return the unique ID for the user */ - def getUniqueId(): String = in.uniqueId + def getUniqueId(): String = in.uniqueId.get /** * Validate the user diff --git a/persistence/mapper/src/test/scala/net/liftweb/mapper/MapperSpec.scala b/persistence/mapper/src/test/scala/net/liftweb/mapper/MapperSpec.scala index 7c66b4433e..2d3cc245bc 100644 --- a/persistence/mapper/src/test/scala/net/liftweb/mapper/MapperSpec.scala +++ b/persistence/mapper/src/test/scala/net/liftweb/mapper/MapperSpec.scala @@ -335,10 +335,10 @@ object MapperSpec extends Specification("Mapper Specification") { val oldUpdate = dog.updatedAt.is - val d1 = (now.getTime - dog.createdAt.getTime) / 100000L + val d1 = (now.getTime - dog.createdAt.get.getTime) / 100000L d1 must_== 0L - val d2 = (now.getTime - dog.updatedAt.getTime) / 100000L + val d2 = (now.getTime - dog.updatedAt.get.getTime) / 100000L d2 must_== 0L dog.name("ralph").save diff --git a/web/testkit/src/main/scala/net/liftweb/mocks/MockHttpServletRequest.scala b/web/testkit/src/main/scala/net/liftweb/mocks/MockHttpServletRequest.scala index f5088f30f5..b2787c08b4 100644 --- a/web/testkit/src/main/scala/net/liftweb/mocks/MockHttpServletRequest.scala +++ b/web/testkit/src/main/scala/net/liftweb/mocks/MockHttpServletRequest.scala @@ -425,7 +425,7 @@ class MockHttpServletRequest(val url : String = null, var contextPath : String = case (k,v) => newMap += k -> (newMap(k) ::: v :: Nil) // Ugly, but it works and keeps order } - asMap(newMap.map{case (k,v) => (k,v.toArray)}.asInstanceOf[Map[Object,Object]]) + asJavaMap(newMap.map{case (k,v) => (k,v.toArray)}.asInstanceOf[Map[Object,Object]]) } def getParameterNames(): JEnum[Object] = diff --git a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Embed.scala b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Embed.scala index 79913fa3a6..2143529812 100644 --- a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Embed.scala +++ b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Embed.scala @@ -43,7 +43,7 @@ object Embed extends DispatchSnippet { ctx <- S.session ?~ ("FIX"+"ME: session is invalid") what <- S.attr ~ ("what") ?~ ("FIX" + "ME The 'what' attribute not defined") templateOpt <- ctx.findTemplate(what.text) ?~ ("FIX"+"ME the "+what+" template not found") - } yield (what,LiftSession.checkForContentId(templateOpt))) match { + } yield (what, Templates.checkForContentId(templateOpt))) match { case Full((what,template)) => { val bindingMap : Map[String,NodeSeq] = Map(kids.flatMap({ case p : scala.xml.PCData => None // Discard whitespace and other non-tag junk diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala index 926eec7ce1..7b8b6fdae6 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala @@ -184,7 +184,7 @@ private[http] trait LiftMerge { } // Appends ajax stript to body - if (LiftRules.autoIncludeAjax(this)) { + if (LiftRules.autoIncludeAjaxCalc.vend().apply(this)) { bodyChildren += ) - HeadHelper.cleanHead(invariant) must equalIgnoreSpace(invariant) + HeadHelper.cleanHead(invariant) must beEqualToIgnoringSpace(invariant) val actual = () val expected = () - HeadHelper.cleanHead(actual) must equalIgnoreSpace(expected) + HeadHelper.cleanHead(actual) must beEqualToIgnoringSpace(expected) } "remove script tag with src attributes if src attributes are equals to previous script" >> { val actual = () val expected = () - HeadHelper.cleanHead(actual) must equalIgnoreSpace(expected) + HeadHelper.cleanHead(actual) must beEqualToIgnoringSpace(expected) val actual2 = () val expected2 = () - HeadHelper.cleanHead(actual2) must equalIgnoreSpace(expected2) + HeadHelper.cleanHead(actual2) must beEqualToIgnoringSpace(expected2) } "remove script tag if content are equals to previous script (need to trim each line ?)" >> { val actual = () val expected = () - HeadHelper.cleanHead(actual) must equalIgnoreSpace(expected) + HeadHelper.cleanHead(actual) must beEqualToIgnoringSpace(expected) } "remove link to css with same id as previous link tag" >> { val invariant = () - HeadHelper.cleanHead(invariant) must equalIgnoreSpace(invariant) + HeadHelper.cleanHead(invariant) must beEqualToIgnoringSpace(invariant) val actual = () val expected = () - HeadHelper.cleanHead(actual) must equalIgnoreSpace(expected) + HeadHelper.cleanHead(actual) must beEqualToIgnoringSpace(expected) } "remove link tag with href attributes if href attributes are equals to previous link" >> { val invariant = () - HeadHelper.cleanHead(invariant) must equalIgnoreSpace(invariant) + HeadHelper.cleanHead(invariant) must beEqualToIgnoringSpace(invariant) val actual = () val expected = () - HeadHelper.cleanHead(actual) must equalIgnoreSpace(expected) + HeadHelper.cleanHead(actual) must beEqualToIgnoringSpace(expected) } "remove style tag with same id as previous style tag" >> { val invariant = () - HeadHelper.cleanHead(invariant) must equalIgnoreSpace(invariant) + HeadHelper.cleanHead(invariant) must beEqualToIgnoringSpace(invariant) val actual = () val expected = () - HeadHelper.cleanHead(actual) must equalIgnoreSpace(expected) + HeadHelper.cleanHead(actual) must beEqualToIgnoringSpace(expected) } "remove style tag if content are equals to previous style (need to trim each line ?)" >> { val invariant = () - HeadHelper.cleanHead(invariant) must equalIgnoreSpace(invariant) + HeadHelper.cleanHead(invariant) must beEqualToIgnoringSpace(invariant) val actual = () val expected = () - HeadHelper.cleanHead(actual) must equalIgnoreSpace(expected) + HeadHelper.cleanHead(actual) must beEqualToIgnoringSpace(expected) } } */ diff --git a/core/util/src/test/scala/net/liftweb/util/VCardParserSpec.scala b/core/util/src/test/scala/net/liftweb/util/VCardParserSpec.scala index 830945d48c..9304f7c8fb 100644 --- a/core/util/src/test/scala/net/liftweb/util/VCardParserSpec.scala +++ b/core/util/src/test/scala/net/liftweb/util/VCardParserSpec.scala @@ -17,13 +17,14 @@ package net.liftweb package util -import org.specs.Specification +import org.specs2.mutable.Specification /** * Systems under specification for VCardParser. */ -object VCardParserSpec extends Specification("VCardParser Specification") { +object VCardParserSpec extends Specification { + "VCardParser Specification".title "VCard" should { "parse correctly" in { @@ -55,7 +56,7 @@ object VCardParserSpec extends Specification("VCardParser Specification") { VCardEntry(VCardKey("TEL", List(("HOME", ""), ("VOICE", ""))), List("(404) 555-1212")), VCardEntry(VCardKey("END", List()), List("VCARD"))) } - case Right(r) => fail(r toString) + case Right(r) => failure(r toString) } } diff --git a/core/util/src/test/scala/net/liftweb/util/XmlParserSpec.scala b/core/util/src/test/scala/net/liftweb/util/XmlParserSpec.scala index 15f3e6b9bd..750311e973 100644 --- a/core/util/src/test/scala/net/liftweb/util/XmlParserSpec.scala +++ b/core/util/src/test/scala/net/liftweb/util/XmlParserSpec.scala @@ -21,13 +21,14 @@ import java.io.ByteArrayInputStream import xml.{Text, Unparsed} -import org.specs.Specification +import org.specs2.mutable.Specification /** * Systems under specification for XmlParser, specifically PCDataMarkupParser. */ -object XmlParserSpec extends Specification("Xml Parser Specification") { +object XmlParserSpec extends Specification { + "Xml Parser Specification".title "Multiple attributes with same name, but different namespace" should { "parse correctly" >> { From 762f7463cd7708fe69d033190aa619ee014c34b6 Mon Sep 17 00:00:00 2001 From: Naftoli Gugenheim Date: Mon, 30 Jul 2012 21:11:58 -0400 Subject: [PATCH 0128/1949] lift-webkit --- .../http/testing/MockHttpRequestSpec.scala | 5 +- .../scala/net/liftweb/mockweb/MockWeb.scala | 2 +- .../scala/net/liftweb/mockweb/WebSpec.scala | 38 +++++-------- .../net/liftweb/builtin/snippet/MsgSpec.scala | 6 ++- .../liftweb/builtin/snippet/MsgsSpec.scala | 6 ++- .../scala/net/liftweb/http/BindingsSpec.scala | 7 +-- .../net/liftweb/http/FactoryMakerSpec.scala | 5 +- .../liftweb/http/NamedCometPerTabSpec.scala | 10 ++-- .../test/scala/net/liftweb/http/ReqSpec.scala | 12 ++--- .../net/liftweb/http/ResourceServerSpec.scala | 6 ++- .../scala/net/liftweb/http/SHtmlSpec.scala | 9 ++-- .../scala/net/liftweb/http/SnippetSpec.scala | 8 ++- .../scala/net/liftweb/http/js/JsExpSpec.scala | 6 ++- .../js/extcore/ExtCoreArtifactsSpec.scala | 5 +- .../net/liftweb/http/rest/XMLApiSpec.scala | 16 +++--- .../net/liftweb/mockweb/MockWebSpec.scala | 10 ++-- .../net/liftweb/mockweb/WebSpecSpec.scala | 29 +++++----- .../scala/net/liftweb/sitemap/LocSpec.scala | 5 +- .../net/liftweb/sitemap/MenuDSLSpec.scala | 5 +- .../liftweb/webapptest/JettyTestServer.scala | 2 +- .../net/liftweb/webapptest/MemoizeSpec.scala | 8 +-- .../net/liftweb/webapptest/OneShot.scala | 13 ++--- .../net/liftweb/webapptest/ToHeadUsages.scala | 53 ++++++++----------- .../scala/net/liftweb/wizard/WizardSpec.scala | 5 +- 24 files changed, 136 insertions(+), 135 deletions(-) diff --git a/web/testkit/src/test/scala/net/liftweb/http/testing/MockHttpRequestSpec.scala b/web/testkit/src/test/scala/net/liftweb/http/testing/MockHttpRequestSpec.scala index 094c740836..29bcdd376b 100644 --- a/web/testkit/src/test/scala/net/liftweb/http/testing/MockHttpRequestSpec.scala +++ b/web/testkit/src/test/scala/net/liftweb/http/testing/MockHttpRequestSpec.scala @@ -16,7 +16,7 @@ package net.liftweb package mocks -import org.specs.Specification +import org.specs2.mutable.Specification import json.JsonDSL._ @@ -24,7 +24,8 @@ import json.JsonDSL._ /** * System under specification for MockHttpRequest. */ -object MockHttpRequestSpec extends Specification("MockHttpRequest Specification") { +object MockHttpRequestSpec extends Specification { + "MockHttpRequest Specification".title val IF_MODIFIED_HEADER = "If-Modified-Since" val TEST_URL = "https://foo.com/test/this/page?a=b&b=a&a=c" diff --git a/web/webkit/src/main/scala/net/liftweb/mockweb/MockWeb.scala b/web/webkit/src/main/scala/net/liftweb/mockweb/MockWeb.scala index 62ac97d946..021c240924 100644 --- a/web/webkit/src/main/scala/net/liftweb/mockweb/MockWeb.scala +++ b/web/webkit/src/main/scala/net/liftweb/mockweb/MockWeb.scala @@ -29,7 +29,7 @@ import net.liftweb.mocks.MockHttpServletRequest import scala.xml.{MetaData,Null} -import org.specs._ +import org.specs2.mutable._ /** * The MockWeb object contains various methods to simplify diff --git a/web/webkit/src/main/scala/net/liftweb/mockweb/WebSpec.scala b/web/webkit/src/main/scala/net/liftweb/mockweb/WebSpec.scala index eb64c0ebef..fbb1d6d3c9 100644 --- a/web/webkit/src/main/scala/net/liftweb/mockweb/WebSpec.scala +++ b/web/webkit/src/main/scala/net/liftweb/mockweb/WebSpec.scala @@ -20,13 +20,13 @@ import javax.servlet.http.HttpServletRequest import scala.xml.NodeSeq -import org.specs._ +import org.specs2.mutable._ +import org.specs2.execute.Result import common.{Box,Empty,Full} import http._ -import json.JsonAST._ +import net.liftweb.json.JsonAST._ import mocks.MockHttpServletRequest -import specification.Example /** @@ -191,11 +191,8 @@ abstract class WebSpec(boot : () => Any = () => {}) extends Specification { def this (description : String, url : String, session : Box[LiftSession], contextPath : String) = this(description, new MockHttpServletRequest(url, contextPath), session) - def in [T](expectations : => T)(implicit m : scala.reflect.ClassManifest[T]) = { - val example = exampleContainer.createExample(description) - if (sequential) example.setSequential() - - example.in { + def in(expectations : => Result) = + exampleFactory newExample(description, { LiftRulesMocker.devTestLiftRulesInstance.doWith(liftRules) { MockWeb.useLiftRules.doWith(true) { MockWeb.testS(req, session) { @@ -203,8 +200,7 @@ abstract class WebSpec(boot : () => Any = () => {}) extends Specification { } } } - }(m) - } + }) } /** @@ -216,18 +212,14 @@ abstract class WebSpec(boot : () => Any = () => {}) extends Specification { def this (description : String, url : String, contextPath : String) = this(description, new MockHttpServletRequest(url, contextPath)) - def in [T](expectations : Req => T)(implicit m : scala.reflect.ClassManifest[T]) = { - val example = exampleContainer.createExample(description) - if (sequential) example.setSequential() - - example.in { + def in(expectations : Req => Result) = + exampleFactory newExample(description, { LiftRulesMocker.devTestLiftRulesInstance.doWith(liftRules) { MockWeb.useLiftRules.doWith(true) { MockWeb.testReq(req)(expectations) } } - }(m) - } + }) } /** @@ -240,23 +232,19 @@ abstract class WebSpec(boot : () => Any = () => {}) extends Specification { def this (description : String, url : String, session : Box[LiftSession], contextPath : String) = this(description, new MockHttpServletRequest(url, contextPath), session) - def in [T](expectations : Box[NodeSeq] => T)(implicit m : scala.reflect.ClassManifest[T]) = { - val example = exampleContainer.createExample(description) - if (sequential) example.setSequential() - - example.in { + def in(expectations : Box[NodeSeq] => Result) = + exampleFactory.newExample(description, { LiftRulesMocker.devTestLiftRulesInstance.doWith(liftRules) { MockWeb.useLiftRules.doWith(true) { MockWeb.testS(req, session) { S.request match { case Full(sReq) => expectations(S.runTemplate(sReq.path.partPath)) - case other => fail("Error: withTemplateFor call did not result in " + + case other => failure("Error: withTemplateFor call did not result in " + "request initialization (S.request = " + other + ")") } } } } - }(m) - } + }) } } diff --git a/web/webkit/src/test/scala/net/liftweb/builtin/snippet/MsgSpec.scala b/web/webkit/src/test/scala/net/liftweb/builtin/snippet/MsgSpec.scala index 9351c5d9bd..5dc335b037 100644 --- a/web/webkit/src/test/scala/net/liftweb/builtin/snippet/MsgSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/builtin/snippet/MsgSpec.scala @@ -18,7 +18,7 @@ package net.liftweb package builtin.snippet import xml._ -import org.specs.Specification +import org.specs2.mutable.Specification import common._ import http._ @@ -27,7 +27,9 @@ import http._ /** * System under specification for Msg. */ -object MsgSpec extends Specification("Msg Specification") { +object MsgSpec extends Specification { + "Msg Specification".title + def withSession[T](f: => T) : T = S.initIfUninitted(new LiftSession("test", "", Empty))(f) diff --git a/web/webkit/src/test/scala/net/liftweb/builtin/snippet/MsgsSpec.scala b/web/webkit/src/test/scala/net/liftweb/builtin/snippet/MsgsSpec.scala index fe2034f3c2..338f50be38 100644 --- a/web/webkit/src/test/scala/net/liftweb/builtin/snippet/MsgsSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/builtin/snippet/MsgsSpec.scala @@ -18,7 +18,7 @@ package net.liftweb package builtin.snippet import xml.XML -import org.specs.Specification +import org.specs2.mutable.Specification import common._ import http._ @@ -27,7 +27,8 @@ import http._ /** * System under specification for Msgs. */ -object MsgsSpec extends Specification("Msgs Specification") { +object MsgsSpec extends Specification { + "Msgs Specification".title def withSession[T](f: => T) : T = S.initIfUninitted(new LiftSession("test", "", Empty))(f) @@ -68,6 +69,7 @@ object MsgsSpec extends Specification("Msgs Specification") { "Properly render AJAX content" in { // TODO : Figure out how to test this + pending } } } diff --git a/web/webkit/src/test/scala/net/liftweb/http/BindingsSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/BindingsSpec.scala index 6c6d64d437..d9898729b2 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/BindingsSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/BindingsSpec.scala @@ -18,7 +18,7 @@ package net.liftweb package http import xml.{NodeSeq, Text} -import org.specs.Specification +import org.specs2.mutable.Specification import common._ import util.Helpers._ @@ -28,7 +28,8 @@ import Bindings._ /** * System under specification for Bindings. */ -object BindingsSpec extends Specification("Bindings Bindings") { +object BindingsSpec extends Specification { + "Bindings Bindings".title case class MyClass(str: String, i: Int, other: MyOtherClass) case class MyOtherClass(foo: String) @@ -73,7 +74,7 @@ object BindingsSpec extends Specification("Bindings Bindings") { "Bindings.binder with an available implicit databinding" should { "allow the application of that databinding to an appropriate object" in { - MyClass("hi", 1, MyOtherClass("bar")).bind(template) must equalIgnoreSpace(expected) + MyClass("hi", 1, MyOtherClass("bar")).bind(template) must beEqualToIgnoringSpace(expected) } } diff --git a/web/webkit/src/test/scala/net/liftweb/http/FactoryMakerSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/FactoryMakerSpec.scala index c6d8cf96d1..9cb911838f 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/FactoryMakerSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/FactoryMakerSpec.scala @@ -17,7 +17,7 @@ package net.liftweb package http -import _root_.org.specs.Specification +import org.specs2.mutable.Specification import common._ @@ -25,7 +25,8 @@ import common._ /** * System under specification for FactoryMaker. */ -object FactoryMakerSpec extends Specification("FactoryMaker Specification") { +object FactoryMakerSpec extends Specification { + "FactoryMaker Specification".title object MyFactory extends Factory { diff --git a/web/webkit/src/test/scala/net/liftweb/http/NamedCometPerTabSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/NamedCometPerTabSpec.scala index e4be5435e6..47dd58ae47 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/NamedCometPerTabSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/NamedCometPerTabSpec.scala @@ -19,14 +19,15 @@ package http import js.JsCmds import xml._ -import org.specs.Specification +import org.specs2.mutable.Specification import common._ /** * System under specification for NamedComet* files. */ -object NamedCometPerTabSpec extends Specification("NamedCometPerTabSpec Specification") { +object NamedCometPerTabSpec extends Specification { + "NamedCometPerTabSpec Specification".title class CometA extends NamedCometActorTrait{ override def lowPriority = { @@ -38,7 +39,7 @@ object NamedCometPerTabSpec extends Specification("NamedCometPerTabSpec Specific } "A NamedCometDispatcher" should { - doBefore { + step { val cometA= new CometA{override def name= Full("1")} cometA.localSetup @@ -50,16 +51,19 @@ object NamedCometPerTabSpec extends Specification("NamedCometPerTabSpec Specific NamedCometListener.getDispatchersFor(Full("1")).foreach( actor => actor.map(_.toString must startWith("net.liftweb.http.NamedCometDispatcher")) ) + success } "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") ) + success } "not be created for a non existing key" in { NamedCometListener.getDispatchersFor(Full("2")).foreach( actor => actor must_== Empty ) + success } } diff --git a/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala index ba415081a4..8773c9f95b 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/ReqSpec.scala @@ -17,7 +17,7 @@ package net.liftweb package http -import org.specs.Specification +import org.specs2.mutable.Specification import common._ @@ -25,7 +25,8 @@ import common._ /** * System under specification for Req. */ -object ReqSpec extends Specification("Req Specification") { +object ReqSpec extends Specification { + "Req Specification".title private val iPhoneUserAgents = List("Mozilla/5.0 (iPhone Simulator; U; CPU iPhone OS 3_0 like Mac OS X; en-us) AppleWebKit/528.18 (KHTML, like Gecko) Version/4.0 Mobile/7A341 Safari/528.16", @@ -40,17 +41,15 @@ object ReqSpec extends Specification("Req Specification") { val uac = new UserAgentCalculator { def userAgent = Full("Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-HK) AppleWebKit/533.18.1 (KHTML, like Gecko) Version/5.0.2 Safari/533.18.5") } - uac.safariVersion.open_! must_== 5 } "Do the right thing with iPhone" in { - iPhoneUserAgents.foreach { + iPhoneUserAgents map { agent => { val uac = new UserAgentCalculator { def userAgent = Full(agent) } - uac.isIPhone must_== true uac.isIPad must_== false } @@ -58,12 +57,11 @@ object ReqSpec extends Specification("Req Specification") { } "Do the right thing with iPad" in { - iPadUserAgents.foreach { + iPadUserAgents map { agent => { val uac = new UserAgentCalculator { def userAgent = Full(agent) } - uac.isIPhone must_== false uac.isIPad must_== true } diff --git a/web/webkit/src/test/scala/net/liftweb/http/ResourceServerSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/ResourceServerSpec.scala index c896a5140d..c7a34db924 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/ResourceServerSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/ResourceServerSpec.scala @@ -17,12 +17,14 @@ package net.liftweb package http -import org.specs.Specification +import org.specs2.mutable.Specification /** * System under specification for ResourceServer. */ -object ResourceServerSpec extends Specification("ResourceServer Specification") { +object ResourceServerSpec extends Specification { + "ResourceServer Specification".title + "ResourceServer.pathRewriter" should { "default jquery.js to jquery-1.3.2" in { ResourceServer.pathRewriter("jquery.js"::Nil) must_== List("jquery-1.3.2-min.js") diff --git a/web/webkit/src/test/scala/net/liftweb/http/SHtmlSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/SHtmlSpec.scala index 0e210fe463..1ff04b3b67 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/SHtmlSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/SHtmlSpec.scala @@ -17,17 +17,18 @@ package net.liftweb package http -import org.specs.Specification +import org.specs2.mutable.Specification import util._ import Helpers._ import net.liftweb.mockweb.MockWeb._ -object SHtmlSpec extends Specification("NamedCometPerTabSpec Specification") { +object SHtmlSpec extends Specification { + "NamedCometPerTabSpec Specification".title val html1= val inputField1= testS("/")( ("#number" #> SHtml.number(0, println(_), 0, 100))(html1) ) - val inputField2= testS("/")( ("#number" #> SHtml.number(0, println(_), 0, 100, 0.1))(html1) ) - val inputField3= testS("/")( ("#number" #> SHtml.number(0, println(_), 0, 100, 1))(html1) ) + val inputField2= testS("/")( ("#number" #> SHtml.number(0, println(_: Double), 0, 100, 0.1))(html1) ) + val inputField3= testS("/")( ("#number" #> SHtml.number(0, println(_: Double), 0, 100, 1))(html1) ) "SHtml" should { "create a number input field" in { diff --git a/web/webkit/src/test/scala/net/liftweb/http/SnippetSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/SnippetSpec.scala index fd7bf1753d..287cf6f88d 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/SnippetSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/SnippetSpec.scala @@ -18,7 +18,7 @@ package net.liftweb package http import xml._ -import org.specs.Specification +import org.specs2.mutable.Specification import common._ import util.Helpers._ @@ -27,7 +27,9 @@ import util.Helpers._ /** * System under specification for SnippetSpec. */ -object SnippetSpec extends Specification("SnippetSpec Specification") { +object SnippetSpec extends Specification { + "SnippetSpec Specification".title + def makeReq = new Req(Req.NilPath, "", GetRequest, Empty, null, System.nanoTime, System.nanoTime, false, () => ParamCalcInfo(Nil, Map.empty, Nil, Empty), Map()) @@ -366,6 +368,7 @@ object SnippetSpec extends Specification("SnippetSpec Specification") { ret.open_! must ==/ () */ + pending } "Check snippets via run" in { @@ -381,6 +384,7 @@ object SnippetSpec extends Specification("SnippetSpec Specification") { (ret.open_! \ "@name").text.length must be > 0 */ + pending } diff --git a/web/webkit/src/test/scala/net/liftweb/http/js/JsExpSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/js/JsExpSpec.scala index a67362ee60..6f29a1ec70 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/js/JsExpSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/js/JsExpSpec.scala @@ -18,7 +18,7 @@ package net.liftweb package http package js -import org.specs.Specification +import org.specs2.mutable.Specification import common._ import json._ @@ -29,7 +29,9 @@ import util.Helpers._ /** * System under specification for JsExp. */ -object JsExpSpec extends Specification("JsExp Specification") { +object JsExpSpec extends Specification { + "JsExp Specification".title + "JsExp" should { "Deal with lift-json" in { val json = ("a" -> 4) ~ ("b" -> 44) diff --git a/web/webkit/src/test/scala/net/liftweb/http/js/extcore/ExtCoreArtifactsSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/js/extcore/ExtCoreArtifactsSpec.scala index 7c88e8c4c3..c17a6656b2 100755 --- a/web/webkit/src/test/scala/net/liftweb/http/js/extcore/ExtCoreArtifactsSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/js/extcore/ExtCoreArtifactsSpec.scala @@ -19,13 +19,14 @@ package http package js package extcore -import org.specs.Specification +import org.specs2.mutable.Specification /** * System under specification for ExtCoreArtifacts. */ -object ExtCoreArtifactsSpec extends Specification("ExtCoreArtifacts Specification") { +object ExtCoreArtifactsSpec extends Specification { + "ExtCoreArtifacts Specification".title "ExtCoreArtifacts.toggle" should { "return the correct javascript expression" in { diff --git a/web/webkit/src/test/scala/net/liftweb/http/rest/XMLApiSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/rest/XMLApiSpec.scala index 4a746edb62..87f6e2251e 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/rest/XMLApiSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/rest/XMLApiSpec.scala @@ -20,8 +20,8 @@ package rest import xml._ -import org.specs.Specification -import org.specs.matcher.Matcher +import org.specs2.mutable.Specification +import org.specs2.matcher.Matcher import common._ import util.ControlHelpers.tryo @@ -29,7 +29,8 @@ import util.ControlHelpers.tryo /** * System under specification for XMLApi. */ -object XmlApiSpec extends Specification("XMLApi Specification") { +object XmlApiSpec extends Specification { + "XMLApi Specification".title object XMLApiExample extends XMLApiHelper { // Define our root tag @@ -83,17 +84,18 @@ object XmlApiSpec extends Specification("XMLApi Specification") { // A helper to simplify the specs matching case class matchXmlResponse(expected : Node) extends Matcher[LiftResponse] { - def apply (response : => LiftResponse) = response match { + def apply[T <: LiftResponse](response : org.specs2.matcher.Expectable[T]) = response.value match { case x : XmlResponse => { /* For some reason, the UnprefixedAttributes that Lift uses to merge in * new attributes makes comparison fail. Instead, we simply stringify and * reparse the response contents and that seems to fix the issue. */ val converted = XML.loadString(x.xml.toString) - (converted == expected, + result(converted == expected, "%s matches %s".format(converted,expected), - "%s does not match %s".format(converted, expected)) + "%s does not match %s".format(converted, expected), + response) } - case other => (false,"matches","not an XmlResponse") + case other => result(false,"matches","not an XmlResponse", response) } } diff --git a/web/webkit/src/test/scala/net/liftweb/mockweb/MockWebSpec.scala b/web/webkit/src/test/scala/net/liftweb/mockweb/MockWebSpec.scala index 1bce0b7a47..dbbefee878 100644 --- a/web/webkit/src/test/scala/net/liftweb/mockweb/MockWebSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/mockweb/MockWebSpec.scala @@ -18,7 +18,7 @@ package mockweb import scala.xml.{Null,Text,UnprefixedAttribute} -import org.specs.Specification +import org.specs2.mutable.Specification import common._ import util._ @@ -31,7 +31,9 @@ import mocks.MockHttpServletRequest * System under specification for MockWeb. This does the double duty as both a spec * against the MockWeb object as well as an example of how to use it. */ -object MockWebSpec extends Specification("MockWeb Specification") { +object MockWebSpec extends Specification { + "MockWeb Specification".title + import MockWeb._ /** We can create our own LiftRules instance for the purpose of this spec. In the @@ -73,8 +75,6 @@ object MockWebSpec extends Specification("MockWeb Specification") { } "MockWeb" should { - shareVariables() // Avoid setting up LiftRules multiple times - "provide a Req corresponding to a string url" in { testReq("http://foo.com/test/this?a=b&a=c", "/test") { req => @@ -144,6 +144,7 @@ object MockWebSpec extends Specification("MockWeb Specification") { } } } + success } "process S with stateful rewrites" in { @@ -154,6 +155,7 @@ object MockWebSpec extends Specification("MockWeb Specification") { } } } + success } "emulate a snippet invocation" in { diff --git a/web/webkit/src/test/scala/net/liftweb/mockweb/WebSpecSpec.scala b/web/webkit/src/test/scala/net/liftweb/mockweb/WebSpecSpec.scala index f8f3c67783..96296eba8d 100644 --- a/web/webkit/src/test/scala/net/liftweb/mockweb/WebSpecSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/mockweb/WebSpecSpec.scala @@ -69,9 +69,9 @@ object WebSpecSpecRest extends RestHelper { * WebSpec trait as well as an example of how to use it. */ class WebSpecSpec extends WebSpec(WebSpecSpecBoot.boot _) { - "WebSpec" should { - setSequential() // This is important for using SessionVars, etc. + sequential // This is important for using SessionVars, etc. + "WebSpec" should { val testUrl = "http://foo.com/test/stateless" val testReq = @@ -87,7 +87,7 @@ class WebSpecSpec extends WebSpec(WebSpecSpecBoot.boot _) { "properly set up S with a String url" withSFor(testUrl) in { S.request match { case Full(req) => req.path.partPath must_== List("stateless", "works") - case _ => fail("No request in S") + case _ => failure("No request in S") } } @@ -123,7 +123,7 @@ class WebSpecSpec extends WebSpec(WebSpecSpecBoot.boot _) { req.post_? must_== true req.body match { case Full(body) => (new String(body)) must_== "This is a test" - case _ => fail("No body set") + case _ => failure("No body set") } } @@ -133,18 +133,17 @@ class WebSpecSpec extends WebSpec(WebSpecSpecBoot.boot _) { req.put_? must_== true req.json match { case Full(jval) => jval must_== JObject(List(JField("name", JString("Joe")))) - case _ => fail("No body set") + case _ => failure("No body set") } } "properly set an XML body" withSFor(testUrl) withPost() in { S.request match { - case Full(req) => { + case Full(req) => req.xml_? must_== true req.post_? must_== true req.xml must_== Full() - } - case _ => fail("No request found in S") + case _ => failure("No request found in S") } } @@ -154,16 +153,14 @@ class WebSpecSpec extends WebSpec(WebSpecSpecBoot.boot _) { "process a JSON RestHelper Request" withReqFor("http://foo.com/api/info.json") in { req => (WebSpecSpecRest(req)() match { - case Full(JsonResponse(_, _, _, 200)) => true - case other => fail("Invalid response : " + other); false - }) must_== true + case Full(JsonResponse(_, _, _, 200)) => success + case other => failure("Invalid response : " + other) + }) } - "properly process a template" withTemplateFor("http://foo.com/net/liftweb/mockweb/webspecspectemplate") in ({ + "properly process a template" withTemplateFor("http://foo.com/net/liftweb/mockweb/webspecspectemplate") in { case Full(template) => template.toString.contains("Hello, WebSpec!") must_== true - case other => { - fail("Error on template : " + other) - } - } : PartialFunction[Box[NodeSeq],Unit]) + case other => failure("Error on template : " + other) + } } } diff --git a/web/webkit/src/test/scala/net/liftweb/sitemap/LocSpec.scala b/web/webkit/src/test/scala/net/liftweb/sitemap/LocSpec.scala index 8d1ccd9a53..d7e35281e5 100644 --- a/web/webkit/src/test/scala/net/liftweb/sitemap/LocSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/sitemap/LocSpec.scala @@ -18,13 +18,14 @@ package net.liftweb package sitemap import common._ -import org.specs.Specification +import org.specs2.mutable.Specification /** * Systems under specification for Loc. */ -object LocSpec extends Specification("Loc Specification") { +object LocSpec extends Specification { + "Loc Specification".title case class Param(s: String) diff --git a/web/webkit/src/test/scala/net/liftweb/sitemap/MenuDSLSpec.scala b/web/webkit/src/test/scala/net/liftweb/sitemap/MenuDSLSpec.scala index 0f7561f0d0..704c0ad6a2 100644 --- a/web/webkit/src/test/scala/net/liftweb/sitemap/MenuDSLSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/sitemap/MenuDSLSpec.scala @@ -17,13 +17,14 @@ package net.liftweb package sitemap -import org.specs.Specification +import org.specs2.mutable.Specification /** * Systems under specification for Menu DSL. */ -object MenuDslSpec extends Specification("Menu DSL Specification") { +object MenuDslSpec extends Specification { + "Menu DSL Specification".title "The Menu DSL" should { "allow basic menu definition via '/ path'" in { diff --git a/web/webkit/src/test/scala/net/liftweb/webapptest/JettyTestServer.scala b/web/webkit/src/test/scala/net/liftweb/webapptest/JettyTestServer.scala index 4c26440df5..75ea89d3fd 100644 --- a/web/webkit/src/test/scala/net/liftweb/webapptest/JettyTestServer.scala +++ b/web/webkit/src/test/scala/net/liftweb/webapptest/JettyTestServer.scala @@ -61,7 +61,7 @@ final class JettyTestServer(baseUrlBox: Box[URL]) { def running = server_.isRunning - def browse(startPath: String, f:(WebTester) => Unit) { + def browse[A](startPath: String, f:(WebTester) => A): A = { val wc = new WebTester() try { wc.setScriptingEnabled(false) diff --git a/web/webkit/src/test/scala/net/liftweb/webapptest/MemoizeSpec.scala b/web/webkit/src/test/scala/net/liftweb/webapptest/MemoizeSpec.scala index b8e5ce308e..f8d85b21b9 100644 --- a/web/webkit/src/test/scala/net/liftweb/webapptest/MemoizeSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/webapptest/MemoizeSpec.scala @@ -17,7 +17,7 @@ package net.liftweb package webapptest -import org.specs.Specification +import org.specs2.mutable.Specification import common._ import util._ @@ -35,9 +35,11 @@ object SessionInfo { /** * System under specification for Memoize. */ -object MemoizeSpec extends Specification("Memoize Specification") { - import SessionInfo._ +object MemoizeSpec extends Specification { + "Memoize Specification".title + sequential + import SessionInfo._ "Memoize" should { "Session memo should default to empty" >> { diff --git a/web/webkit/src/test/scala/net/liftweb/webapptest/OneShot.scala b/web/webkit/src/test/scala/net/liftweb/webapptest/OneShot.scala index 29c181a28c..baa0865510 100644 --- a/web/webkit/src/test/scala/net/liftweb/webapptest/OneShot.scala +++ b/web/webkit/src/test/scala/net/liftweb/webapptest/OneShot.scala @@ -17,7 +17,7 @@ package net.liftweb package webapptest -import org.specs.Specification +import org.specs2.mutable.Specification import util._ import http._ @@ -31,6 +31,7 @@ import snippet.Counter object OneShot extends Specification with RequestKit { + sequential private def reachableLocalAddress = { val l = InetAddress.getLocalHost @@ -49,12 +50,9 @@ object OneShot extends Specification with RequestKit { def baseUrl = jetty.baseUrl.toString - doBeforeSpec(jetty.start()) + step(jetty.start()) "ContainerVars" should { - - setSequential() - "have correct int default" in { val tmp = LiftRules.sessionCreator try { @@ -136,9 +134,6 @@ object OneShot extends Specification with RequestKit { } "OneShot" should { - - setSequential() - "fire once for oneshot" in { Counter.x = 0 @@ -175,7 +170,7 @@ object OneShot extends Specification with RequestKit { } } - doAfterSpec { + step { tryo { jetty.stop() } diff --git a/web/webkit/src/test/scala/net/liftweb/webapptest/ToHeadUsages.scala b/web/webkit/src/test/scala/net/liftweb/webapptest/ToHeadUsages.scala index 8c9d22b0ad..af08439c09 100644 --- a/web/webkit/src/test/scala/net/liftweb/webapptest/ToHeadUsages.scala +++ b/web/webkit/src/test/scala/net/liftweb/webapptest/ToHeadUsages.scala @@ -19,7 +19,7 @@ package webapptest import java.net.{URL, InetAddress} -import org.specs.Specification +import org.specs2.mutable.Specification import common.Full import util._ @@ -29,7 +29,9 @@ import Helpers.tryo /** * System under specification for ToHeadUsages. */ -object ToHeadUsages extends Specification("ToHeadUsages Specification") { +object ToHeadUsages extends Specification { + "ToHeadUsages Specification".title + sequential private def reachableLocalAddress = { val l = InetAddress.getLocalHost @@ -46,53 +48,50 @@ object ToHeadUsages extends Specification("ToHeadUsages Specification") { private lazy val jetty = new JettyTestServer(Full(baseUrl_)) - doBeforeSpec(jetty.start()) + step(jetty.start()) "lift merger" should { - setSequential() - "merge from html fragment" in { jetty.browse( "/htmlFragmentWithHead", html => - html.getElementByXPath("/html/head/script[@id='fromFrag']") must notBeNull.when(jetty.running) - ) + html.getElementByXPath("/html/head/script[@id='fromFrag']") must not(beNull when jetty.running)) } "merge from html fragment does not include head element in body" in { jetty.browse( "/htmlFragmentWithHead", html => - html.getElementsByXPath("/html/body/script[@id='fromFrag']").size must be_==(0).when(jetty.running) + html.getElementsByXPath("/html/body/script[@id='fromFrag']").size must (be_==(0) when jetty.running) ) } "merge from snippet" in { jetty.browse( "/htmlSnippetWithHead", html => - html.getElementByXPath("/html/head/script[@src='snippet.js']") must notBeNull.when(jetty.running) + html.getElementByXPath("/html/head/script[@src='snippet.js']") must not(beNull when jetty.running) ) } "not merge for bodyless html" in { jetty.browse( - "/basicDiv",html => { - html.getElementById("fruit") must notBeNull.when(jetty.running) - html.getElementById("bat") must notBeNull.when(jetty.running) + "/basicDiv", html => { + html.getElementById("fruit") must not(beNull when jetty.running) + html.getElementById("bat") must not(beNull when jetty.running) } ) } "not merge for headless bodyless html" in { jetty.browse( - "/h1",html => { - html.getElementById("h1") must notBeNull.when(jetty.running) + "/h1", html => { + html.getElementById("h1") must not(beNull when jetty.running) } ) } "not merge for headless body html" in { jetty.browse( - "/body_no_head",html => { + "/body_no_head", html => { // Note: The XPath expression "html/body/head/div" fails here with // HtmlUnit 2.5 since "head" is not recognized as a XHTML element // due to its incorrect position (under body instead of directly under html) @@ -103,8 +102,8 @@ object ToHeadUsages extends Specification("ToHeadUsages Specification") { "not merge non-html" in { jetty.browse( - "/non_html",html => { - html.getElementById("frog") must notBeNull.when(jetty.running) + "/non_html", html => { + html.getElementById("frog") must not(beNull when jetty.running) } ) } @@ -112,9 +111,6 @@ object ToHeadUsages extends Specification("ToHeadUsages Specification") { } "pages " should { - - setSequential() - "Templates should recognize entities" in { val ns = Templates(List("index")).open_! val str = AltXML.toXML(ns(0), false, false, false) @@ -144,29 +140,26 @@ object ToHeadUsages extends Specification("ToHeadUsages Specification") { } "deferred snippets" should { - - setSequential() - "render" in { jetty.browse( - "/deferred",html => { - html.getElementById("second") must notBeNull.when(jetty.running) + "/deferred", html => { + html.getElementById("second") must not(beNull when jetty.running) } ) } "not deferred not in actor" in { jetty.browse( - "/deferred",html => { - html.getElementByXPath("/html/body/span[@id='whack1']/span[@id='actor_false']") must notBeNull.when(jetty.running) + "/deferred", html => { + html.getElementByXPath("/html/body/span[@id='whack1']/span[@id='actor_false']") must not(beNull when jetty.running) } ) } "deferred in actor" in { jetty.browse( - "/deferred",html => { - html.getElementByXPath("/html/body/span[@id='whack2']/span[@id='actor_true']") must notBeNull.when(jetty.running) + "/deferred", html => { + html.getElementByXPath("/html/body/span[@id='whack2']/span[@id='actor_true']") must not(beNull when jetty.running) } ) } @@ -197,7 +190,7 @@ object ToHeadUsages extends Specification("ToHeadUsages Specification") { } } - doAfterSpec { + step { tryo { jetty.stop() } diff --git a/web/wizard/src/test/scala/net/liftweb/wizard/WizardSpec.scala b/web/wizard/src/test/scala/net/liftweb/wizard/WizardSpec.scala index eecaf25280..a967dda940 100644 --- a/web/wizard/src/test/scala/net/liftweb/wizard/WizardSpec.scala +++ b/web/wizard/src/test/scala/net/liftweb/wizard/WizardSpec.scala @@ -17,7 +17,7 @@ package net.liftweb package wizard -import org.specs.Specification +import org.specs2.mutable.Specification import common._ import util._ @@ -26,7 +26,8 @@ import http._ /** * System under specification for Wizard. */ -object WizardSpec extends Specification("Wizard Specification") { +object WizardSpec extends Specification { + "Wizard Specification".title val session: LiftSession = new LiftSession("", Helpers.randomString(20), Empty) From ea1c86821244b63771c24800304bd374b96a2ec1 Mon Sep 17 00:00:00 2001 From: Naftoli Gugenheim Date: Mon, 30 Jul 2012 21:14:54 -0400 Subject: [PATCH 0129/1949] Remove MailerSpec hack, now that testMode checking is fixed in Props --- core/util/src/test/scala/net/liftweb/util/MailerSpec.scala | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/util/src/test/scala/net/liftweb/util/MailerSpec.scala b/core/util/src/test/scala/net/liftweb/util/MailerSpec.scala index 5b91afffd9..d23de56a40 100644 --- a/core/util/src/test/scala/net/liftweb/util/MailerSpec.scala +++ b/core/util/src/test/scala/net/liftweb/util/MailerSpec.scala @@ -108,8 +108,6 @@ object MyMailer extends Mailer { lastMessage = Full(msg) // MailerSpec.this.notifyAll() }) - //FIXME _sometimes_ mode is not detected correctly here - devModeSend.default.set{ m: MimeMessage => lastMessage = Full(m) } def touch() { Props.testMode From 0b20cc8d55a21525aa6e8967b3636dfc38e474f4 Mon Sep 17 00:00:00 2001 From: Naftoli Gugenheim Date: Mon, 30 Jul 2012 23:49:47 -0400 Subject: [PATCH 0130/1949] lift-db, lift-mapper --- .../test/scala/net/liftweb/db/DBSpec.scala | 7 ++- .../scala/net/liftweb/mapper/DbSpec.scala | 6 ++- .../net/liftweb/mapper/ItemsListSpec.scala | 6 ++- .../net/liftweb/mapper/ManyToManySpecs.scala | 8 +-- .../liftweb/mapper/MappedBooleanSpec.scala | 7 ++- .../net/liftweb/mapper/MappedDateSpec.scala | 6 ++- .../liftweb/mapper/MappedDecimalSpec.scala | 9 ++-- .../net/liftweb/mapper/MappedEnumSpec.scala | 7 +-- .../mapper/MappedLongForeignKeySpec.scala | 20 ++++---- .../scala/net/liftweb/mapper/MapperSpec.scala | 50 ++++++++++--------- .../net/liftweb/mapper/OneToManySpecs.scala | 6 ++- .../net/liftweb/mapper/SchemifierSpec.scala | 7 ++- 12 files changed, 84 insertions(+), 55 deletions(-) diff --git a/persistence/db/src/test/scala/net/liftweb/db/DBSpec.scala b/persistence/db/src/test/scala/net/liftweb/db/DBSpec.scala index 09cf5dbe50..d2e0ca2358 100644 --- a/persistence/db/src/test/scala/net/liftweb/db/DBSpec.scala +++ b/persistence/db/src/test/scala/net/liftweb/db/DBSpec.scala @@ -17,8 +17,8 @@ package net.liftweb package db -import org.specs.Specification -import org.specs.mock.Mockito +import org.specs2.mutable.Specification +import org.specs2.mock.Mockito import org.mockito.Matchers._ import net.liftweb.common._ @@ -28,6 +28,8 @@ import net.liftweb.util.ControlHelpers._ import java.sql._ class DBSpec extends Specification with Mockito { + sequential + trait CommitFunc { def f(success: Boolean): Unit } @@ -142,6 +144,7 @@ class DBSpec extends Specification with Mockito { there was one(activeConnection).rollback there was one(m).f(false) + success } } diff --git a/persistence/mapper/src/test/scala/net/liftweb/mapper/DbSpec.scala b/persistence/mapper/src/test/scala/net/liftweb/mapper/DbSpec.scala index cf3c1a3ef7..516edfb94b 100644 --- a/persistence/mapper/src/test/scala/net/liftweb/mapper/DbSpec.scala +++ b/persistence/mapper/src/test/scala/net/liftweb/mapper/DbSpec.scala @@ -17,7 +17,7 @@ package net.liftweb package mapper -import org.specs.Specification +import org.specs2.mutable.Specification import common._ import util._ @@ -27,7 +27,9 @@ import http.{S, LiftSession} /** * Systems under specification for DB. */ -object DbSpec extends Specification("DB Specification") { +object DbSpec extends Specification { + "DB Specification".title + val provider = DbProviders.H2MemoryProvider val logF = Schemifier.infoF _ diff --git a/persistence/mapper/src/test/scala/net/liftweb/mapper/ItemsListSpec.scala b/persistence/mapper/src/test/scala/net/liftweb/mapper/ItemsListSpec.scala index f789647948..a6742189bf 100644 --- a/persistence/mapper/src/test/scala/net/liftweb/mapper/ItemsListSpec.scala +++ b/persistence/mapper/src/test/scala/net/liftweb/mapper/ItemsListSpec.scala @@ -17,7 +17,7 @@ package net.liftweb package mapper -import org.specs.Specification +import org.specs2.mutable.Specification import common._ import json._ @@ -29,7 +29,9 @@ import view._ /** * Systems under specification for ItemsList. */ -object ItemsListSpec extends Specification("ItemsList Specification") { +object ItemsListSpec extends Specification { + "ItemsList Specification".title + sequential val provider = DbProviders.H2MemoryProvider diff --git a/persistence/mapper/src/test/scala/net/liftweb/mapper/ManyToManySpecs.scala b/persistence/mapper/src/test/scala/net/liftweb/mapper/ManyToManySpecs.scala index d14ec87f0c..d9aa3c1f40 100644 --- a/persistence/mapper/src/test/scala/net/liftweb/mapper/ManyToManySpecs.scala +++ b/persistence/mapper/src/test/scala/net/liftweb/mapper/ManyToManySpecs.scala @@ -17,15 +17,17 @@ package net.liftweb package mapper -import org.specs.Specification +import org.specs2.mutable.Specification /** * Systems under specification for ManyToMany. */ -object ManyToManySpec extends Specification("ManyToMany Specification") { +object ManyToManySpec extends Specification { + "ManyToMany Specification".title + sequential val provider = DbProviders.H2MemoryProvider - + private def ignoreLogger(f: => AnyRef): Unit = () def setupDB { MapperRules.createForeignKeys_? = c => false diff --git a/persistence/mapper/src/test/scala/net/liftweb/mapper/MappedBooleanSpec.scala b/persistence/mapper/src/test/scala/net/liftweb/mapper/MappedBooleanSpec.scala index f318f2be33..9bb68777f9 100644 --- a/persistence/mapper/src/test/scala/net/liftweb/mapper/MappedBooleanSpec.scala +++ b/persistence/mapper/src/test/scala/net/liftweb/mapper/MappedBooleanSpec.scala @@ -17,7 +17,7 @@ package net.liftweb package mapper -import org.specs.Specification +import org.specs2.mutable.Specification import common._ @@ -25,7 +25,10 @@ import common._ /** * Systems under specification for MappedDate. */ -object MappedBooleanSpec extends Specification("MappedBoolean Specification") { +object MappedBooleanSpec extends Specification { + "MappedBoolean Specification".title + sequential + val provider = DbProviders.H2MemoryProvider private def ignoreLogger(f: => AnyRef): Unit = () diff --git a/persistence/mapper/src/test/scala/net/liftweb/mapper/MappedDateSpec.scala b/persistence/mapper/src/test/scala/net/liftweb/mapper/MappedDateSpec.scala index 8f424cdefd..d78c6053d3 100644 --- a/persistence/mapper/src/test/scala/net/liftweb/mapper/MappedDateSpec.scala +++ b/persistence/mapper/src/test/scala/net/liftweb/mapper/MappedDateSpec.scala @@ -17,7 +17,7 @@ package net.liftweb package mapper -import org.specs.Specification +import org.specs2.mutable.Specification import common._ @@ -25,7 +25,9 @@ import common._ /** * Systems under specification for MappedDate. */ -object MappedDateSpec extends Specification("MappedDate Specification") { +object MappedDateSpec extends Specification { + "MappedDate Specification".title + "MappedDate" should { "handle a Number in setFromAny" in { val dog = Dog2.create diff --git a/persistence/mapper/src/test/scala/net/liftweb/mapper/MappedDecimalSpec.scala b/persistence/mapper/src/test/scala/net/liftweb/mapper/MappedDecimalSpec.scala index 9186c8d47d..831fb459d2 100644 --- a/persistence/mapper/src/test/scala/net/liftweb/mapper/MappedDecimalSpec.scala +++ b/persistence/mapper/src/test/scala/net/liftweb/mapper/MappedDecimalSpec.scala @@ -17,7 +17,7 @@ package net.liftweb package mapper -import org.specs.Specification +import org.specs2.mutable.Specification import common._ @@ -25,9 +25,12 @@ import common._ /** * Systems under specification for MappedDate. */ -object MappedDecimalSpec extends Specification("MappedDecimal Specification") { +object MappedDecimalSpec extends Specification { + "MappedDecimal Specification".title + sequential + val provider = DbProviders.H2MemoryProvider - + private def ignoreLogger(f: => AnyRef): Unit = () def setupDB { provider.setupDB diff --git a/persistence/mapper/src/test/scala/net/liftweb/mapper/MappedEnumSpec.scala b/persistence/mapper/src/test/scala/net/liftweb/mapper/MappedEnumSpec.scala index 8e03d321d6..ba4e660cfe 100644 --- a/persistence/mapper/src/test/scala/net/liftweb/mapper/MappedEnumSpec.scala +++ b/persistence/mapper/src/test/scala/net/liftweb/mapper/MappedEnumSpec.scala @@ -17,7 +17,7 @@ package net.liftweb package mapper -import org.specs.Specification +import org.specs2.mutable.Specification import common._ @@ -37,10 +37,11 @@ class EnumObj extends LongKeyedMapper[EnumObj] with IdPK { object EnumObj extends EnumObj with LongKeyedMetaMapper[EnumObj] -object MappedEnumSpec extends Specification("MappedEnum Specification") { +object MappedEnumSpec extends Specification { + "MappedEnum Specification".title + "MappedEnum" should { "preserve enumeration order when building display list" in { - skip("Will not work with Scala 2.8.1 https://issues.scala-lang.org/browse/SI-3687") val v = EnumObj.create import MyEnum._ diff --git a/persistence/mapper/src/test/scala/net/liftweb/mapper/MappedLongForeignKeySpec.scala b/persistence/mapper/src/test/scala/net/liftweb/mapper/MappedLongForeignKeySpec.scala index e6cd6fe556..cec0327b10 100644 --- a/persistence/mapper/src/test/scala/net/liftweb/mapper/MappedLongForeignKeySpec.scala +++ b/persistence/mapper/src/test/scala/net/liftweb/mapper/MappedLongForeignKeySpec.scala @@ -17,7 +17,7 @@ package net.liftweb package mapper -import org.specs.Specification +import org.specs2.mutable.Specification import common._ import util._ @@ -26,21 +26,23 @@ import util._ /** * Systems under specification for MappedLongForeignKey. */ -object MappedLongForeignKeySpec extends Specification("MappedLongForeignKey Specification") { +object MappedLongForeignKeySpec extends Specification with org.specs2.specification.BeforeExample { + "MappedLongForeignKey Specification".title + sequential + // Make sure we have everything configured first MapperSpecsModel.setup() def provider = DbProviders.H2MemoryProvider + def before = MapperSpecsModel.cleanup() + "MappedLongForeignKey" should { - doBefore { (try { provider.setupDB - MapperSpecsModel.cleanup() } catch { - case e if !provider.required_? => skip("Provider %s not available: %s".format(provider, e)) - }) must not(throwAnException[Exception]).orSkipExample - } + case e if !provider.required_? => 1 must be_==(2).orSkip("Provider %s not available: %s".format(provider, e)) + }) must not(throwA[Exception]).orSkip "Not allow comparison to another FK" in { val dog = Dog.create.name("Froo").saveMe @@ -79,8 +81,8 @@ object MappedLongForeignKeySpec extends Specification("MappedLongForeignKey Spec val dog = Dog.create.owner(user) dog.owner(Empty) - dog.owner.obj mustBe Empty - dog.owner.is mustBe 0L + dog.owner.obj must_== Empty + dog.owner.is must_== 0L } } } diff --git a/persistence/mapper/src/test/scala/net/liftweb/mapper/MapperSpec.scala b/persistence/mapper/src/test/scala/net/liftweb/mapper/MapperSpec.scala index 2d3cc245bc..73383b2397 100644 --- a/persistence/mapper/src/test/scala/net/liftweb/mapper/MapperSpec.scala +++ b/persistence/mapper/src/test/scala/net/liftweb/mapper/MapperSpec.scala @@ -19,7 +19,8 @@ package mapper import java.util.Locale -import org.specs.Specification +import org.specs2.mutable.Specification +import org.specs2.specification.BeforeExample import common._ import json._ @@ -33,9 +34,10 @@ import http.provider.HTTPRequest * Systems under specification for Mapper. The model classes here are * defined in MapperSpecsModel.scala */ -object MapperSpec extends Specification("Mapper Specification") { +class MapperSpec extends Specification with BeforeExample { + "Mapper Specification".title // Do everything in order. - setSequential() + sequential // Make sure we have everything configured first MapperSpecsModel.setup() @@ -56,19 +58,14 @@ object MapperSpec extends Specification("Mapper Specification") { // if (!DB.loggingEnabled_? && doLog) DB.addLogFunc(logDBStuff) + def before = MapperSpecsModel.cleanup() // before each example + providers.foreach(provider => { + try { + provider.setupDB ("Mapper for " + provider.name) should { - doBefore { - (try { - provider.setupDB - MapperSpecsModel.cleanup() - } catch { - case e if !provider.required_? => skip("Provider %s not available: %s".format(provider, e)) - }) must not(throwAnException[Exception]).orSkipExample - } - "schemify" in { val elwood = SampleModel.find(By(SampleModel.firstName, "Elwood")).open_! val madeline = SampleModel.find(By(SampleModel.firstName, "Madeline")).open_! @@ -106,6 +103,7 @@ object MapperSpec extends Specification("Mapper Specification") { SampleModel.firstName.displayName must_== "da_DK:SampleModel.firstName" LiftRules.localeCalculator = localeCalculator + success } "snake connection should snakify default table & column names" in { @@ -176,7 +174,7 @@ object MapperSpec extends Specification("Mapper Specification") { for (t <- mm) (t.tag.is.startsWith("M")) must beTrue - for (t <- mm) { + for (t <- mm) yield { t.model.cached_? must beFalse t.model.obj t.model.cached_? must beTrue @@ -199,8 +197,8 @@ object MapperSpec extends Specification("Mapper Specification") { "enforce FK constraint on DefaultConnection" in { val supportsFK = DB.use(DefaultConnectionIdentifier) { conn => conn.driverType.supportsForeignKeys_? } - if (!supportsFK) skip("Driver %s does not support FK constraints".format(provider)) - + if (!supportsFK) skipped("Driver %s does not support FK constraints".format(provider)) + SampleTag.create.model(42).save must throwA[java.sql.SQLException] } @@ -212,7 +210,7 @@ object MapperSpec extends Specification("Mapper Specification") { val oo = SampleTag.findAll(By(SampleTag.tag, "Meow"), PreCache(SampleTag.model)) (oo.length > 0) must beTrue - for (t <- oo) t.model.cached_? must beTrue + for (t <- oo) yield t.model.cached_? must beTrue } "Precache works with OrderBy" in { @@ -227,6 +225,7 @@ object MapperSpec extends Specification("Mapper Specification") { (oo.length > 0) must beTrue for (t <- oo) t.model.cached_? must beTrue } + success } "Non-deterministic Precache works" in { @@ -234,7 +233,7 @@ object MapperSpec extends Specification("Mapper Specification") { val oo = SampleTag.findAll(By(SampleTag.tag, "Meow"), PreCache(SampleTag.model, false)) (oo.length > 0) must beTrue - for (t <- oo) t.model.cached_? must beTrue + for (t <- oo) yield t.model.cached_? must beTrue } "Non-deterministic Precache works with OrderBy" in { @@ -242,7 +241,7 @@ object MapperSpec extends Specification("Mapper Specification") { val oo = SampleTag.findAll(OrderBy(SampleTag.tag, Ascending), MaxRows(2), PreCache(SampleTag.model, false)) (oo.length > 0) must beTrue - for (t <- oo) t.model.cached_? must beTrue + for (t <- oo) yield t.model.cached_? must beTrue } "work with Mixed case" in { @@ -316,8 +315,9 @@ object MapperSpec extends Specification("Mapper Specification") { val oo = SampleTag.findAll(OrderBy(SampleTag.tag, Ascending), MaxRows(2), PreCache(SampleTag.model)) (oo.length > 0) must beTrue - for (t <- oo) t.model.cached_? must beTrue + for (t <- oo) yield t.model.cached_? must beTrue } + success } "Non-deterministic Precache works with Mixed Case" in { @@ -325,11 +325,11 @@ object MapperSpec extends Specification("Mapper Specification") { val oo = SampleTag.findAll(By(SampleTag.tag, "Meow"), PreCache(SampleTag.model, false)) (oo.length > 0) must beTrue - for (t <- oo) t.model.cached_? must beTrue + for (t <- oo) yield t.model.cached_? must beTrue } - "Createdat and updated at work" in { + "CreatedAt and UpdatedAt work" in { val now = Helpers.now val dog = Dog2.find().open_! @@ -355,10 +355,10 @@ object MapperSpec extends Specification("Mapper Specification") { val oo = SampleTag.findAll(OrderBy(SampleTag.tag, Ascending), MaxRows(2), PreCache(SampleTag.model, false)) (oo.length > 0) must beTrue - for (t <- oo) t.model.cached_? must beTrue + for (t <- oo) yield t.model.cached_? must beTrue } - "Save flag works" in { + "Save flag results in update rather than insert" in { val elwood = SampleModel.find(By(SampleModel.firstName, "Elwood")).open_! elwood.firstName.is must_== "Elwood" elwood.firstName("Frog").save @@ -377,6 +377,10 @@ object MapperSpec extends Specification("Mapper Specification") { result.length must_== 2 } } + } catch { + case e if !provider.required_? => skipped("Provider %s not available: %s".format(provider, e)) + case _ => skipped + } }) } diff --git a/persistence/mapper/src/test/scala/net/liftweb/mapper/OneToManySpecs.scala b/persistence/mapper/src/test/scala/net/liftweb/mapper/OneToManySpecs.scala index d957d85605..8270d4f804 100644 --- a/persistence/mapper/src/test/scala/net/liftweb/mapper/OneToManySpecs.scala +++ b/persistence/mapper/src/test/scala/net/liftweb/mapper/OneToManySpecs.scala @@ -17,9 +17,11 @@ package net.liftweb { package mapper { -import org.specs.Specification +import org.specs2.mutable.Specification -object OneToManySpecs extends Specification("One to Many Specification") { +object OneToManySpecs extends Specification { + "One to Many Specification".title + sequential val provider = DbProviders.H2MemoryProvider diff --git a/persistence/mapper/src/test/scala/net/liftweb/mapper/SchemifierSpec.scala b/persistence/mapper/src/test/scala/net/liftweb/mapper/SchemifierSpec.scala index 7baf9225f1..ede287a6b1 100644 --- a/persistence/mapper/src/test/scala/net/liftweb/mapper/SchemifierSpec.scala +++ b/persistence/mapper/src/test/scala/net/liftweb/mapper/SchemifierSpec.scala @@ -17,7 +17,7 @@ package net.liftweb package mapper -import org.specs.Specification +import org.specs2.mutable.Specification import common._ @@ -25,13 +25,16 @@ import common._ /** * Systems under specification for Schemifier. */ -object SchemifierSpec extends Specification("Schemifier Specification") { +object SchemifierSpec extends Specification { + "Schemifier Specification".title + val provider = DbProviders.H2MemoryProvider "Schemifier" should { "not crash in readonly if table doesn't exist" in { provider.setupDB Schemifier.schemify(false, Schemifier.neverF _, Thing) + success } } } From 47e4dbd522c00cf818489c69212f51164e2ec981 Mon Sep 17 00:00:00 2001 From: Naftoli Gugenheim Date: Tue, 31 Jul 2012 00:43:27 -0400 Subject: [PATCH 0131/1949] lift-ldap --- .../test/scala/net/liftweb/ldap/LdapSpec.scala | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/persistence/ldap/src/test/scala/net/liftweb/ldap/LdapSpec.scala b/persistence/ldap/src/test/scala/net/liftweb/ldap/LdapSpec.scala index 2c7a6d49ee..8933bb741a 100644 --- a/persistence/ldap/src/test/scala/net/liftweb/ldap/LdapSpec.scala +++ b/persistence/ldap/src/test/scala/net/liftweb/ldap/LdapSpec.scala @@ -30,7 +30,8 @@ import org.apache.directory.server.xdbm.Index import org.apache.directory.server.core.entry.ServerEntry import org.apache.directory.shared.ldap.name.LdapDN -import org.specs.Specification +import org.specs2.mutable.Specification +import org.specs2.specification.{ AfterExample, BeforeExample } import common._ import util.Helpers.tryo @@ -39,7 +40,10 @@ import util.Helpers.tryo /** * Systems under specification for Ldap. */ -object LdapSpec extends Specification("LDAP Specification") { +object LdapSpec extends Specification with AfterExample with BeforeExample { + "LDAP Specification".title + sequential + val ROOT_DN = "dc=ldap,dc=liftweb,dc=net" // Thanks to Francois Armand for pointing this utility out! @@ -54,7 +58,7 @@ object LdapSpec extends Specification("LDAP Specification") { * http://directory.apache.org/apacheds/1.5/41-embedding-apacheds-into-an-application.html * http://stackoverflow.com/questions/1560230/running-apache-ds-embedded-in-my-application */ - doBeforeSpec { + def before = { (try { // Disable changelog service.getChangeLog.setEnabled(false) @@ -66,7 +70,7 @@ object LdapSpec extends Specification("LDAP Specification") { val dir = new java.io.File(d) dir.mkdirs service.setWorkingDirectory(dir) - case _ => fail("No working dir set for ApacheDS!") + case _ => failure("No working dir set for ApacheDS!") } // Set up a partition @@ -102,12 +106,10 @@ object LdapSpec extends Specification("LDAP Specification") { ldap.start() - }) must not(throwAn[Exception]).orSkipExample + }) must not(throwAn[Exception]).orSkip } "LDAPVendor" should { - shareVariables() - object myLdap extends LDAPVendor myLdap.configure(Map("ldap.url" -> "ldap://localhost:%d/".format(service_port), @@ -136,7 +138,7 @@ object LdapSpec extends Specification("LDAP Specification") { } - doAfterSpec { + def after = { ldap.stop() service.shutdown() From d8a612084bfa8245ac59eab85c56224e29fc61a1 Mon Sep 17 00:00:00 2001 From: Naftoli Gugenheim Date: Tue, 31 Jul 2012 04:04:13 -0400 Subject: [PATCH 0132/1949] Fix Props.mode calculatation to work with specs2 concurrent test running Note: this is needed by the last two commits (MailerSpec and ResourceServerSpec) --- .../src/main/scala/net/liftweb/util/Props.scala | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/Props.scala b/core/util/src/main/scala/net/liftweb/util/Props.scala index 5004ec3d21..b9799ca293 100644 --- a/core/util/src/main/scala/net/liftweb/util/Props.scala +++ b/core/util/src/main/scala/net/liftweb/util/Props.scala @@ -116,11 +116,16 @@ object Props extends Logger { case Full("profile") => Profile case Full("development") => Development case _ => { - val exp = new Exception - exp.fillInStackTrace - if (exp.getStackTrace.find(st => st.getClassName.indexOf("SurefireBooter") >= 0).isDefined) Test - else if (exp.getStackTrace.find(st => st.getClassName.indexOf("sbt.TestRunner") >= 0).isDefined) Test - else Development + val st = Thread.currentThread.getStackTrace + val names = List( + "org.apache.maven.surefire.booter.SurefireBooter", + "sbt.TestRunner", + "org.specs2.runner.TestInterfaceRunner" // sometimes specs2 runs tests on another thread + ) + if(st.exists(e => names.exists(e.getClassName.startsWith))) + Test + else + Development } } } From 026252a18fbafbc2fe6e01cca9835f9260b116eb Mon Sep 17 00:00:00 2001 From: Naftoli Gugenheim Date: Tue, 31 Jul 2012 04:22:05 -0400 Subject: [PATCH 0133/1949] lift-couchdb --- .../liftweb/couchdb/CouchDatabaseSpec.scala | 26 +++--- .../liftweb/couchdb/CouchDocumentSpec.scala | 26 +++--- .../net/liftweb/couchdb/CouchQuerySpec.scala | 27 +++--- .../net/liftweb/couchdb/CouchRecordSpec.scala | 86 ++++++++++--------- .../net/liftweb/couchdb/JsonRecordSpec.scala | 38 ++++---- 5 files changed, 113 insertions(+), 90 deletions(-) diff --git a/persistence/couchdb/src/test/scala/net/liftweb/couchdb/CouchDatabaseSpec.scala b/persistence/couchdb/src/test/scala/net/liftweb/couchdb/CouchDatabaseSpec.scala index 6bbe62555c..01bddbeecb 100644 --- a/persistence/couchdb/src/test/scala/net/liftweb/couchdb/CouchDatabaseSpec.scala +++ b/persistence/couchdb/src/test/scala/net/liftweb/couchdb/CouchDatabaseSpec.scala @@ -18,33 +18,39 @@ import java.net.ConnectException import dispatch.{Http, StatusCode} -import org.specs.Specification +import org.specs2.mutable.Specification /** * Systems under specification for CouchDatabase. */ -object CouchDatabaseSpec extends Specification("CouchDatabase Specification") { +object CouchDatabaseSpec extends Specification { + "CouchDatabase Specification".title + sequential + def setup = { val http = new Http val database = new Database("test") - (try { http(database delete) } catch { case StatusCode(_, _) => () }) must not(throwAnException[ConnectException]).orSkipExample + (try { http(database delete) } catch { case StatusCode(_, _) => () }) must not(throwA[ConnectException]).orSkip (http, database) } + def hasCode(i: Int): PartialFunction[Throwable, org.specs2.matcher.MatchResult[Any]] = + { case StatusCode(c, _) => c must_== i } + "A database" should { "give 404 when info called and nonexistant" in { setup val (http, database) = setup - http(database info) must throwAnException[StatusCode].like { case StatusCode(404, _) => true } + http(database info) must throwA[StatusCode].like(hasCode(404)) } "give 404 when deleted but nonexistant" in { val (http, database) = setup - http(database delete) must throwAnException[StatusCode].like { case StatusCode(404, _) => true } + http(database delete) must throwA[StatusCode].like(hasCode(404)) } "succeed being created" in { @@ -57,9 +63,9 @@ object CouchDatabaseSpec extends Specification("CouchDatabase Specification") { val (http, database) = setup http(database create) must_== () - http(database create) must throwAnException[StatusCode].like { case StatusCode(412, _) => true } + http(database create) must throwA[StatusCode].like(hasCode(412)) } - + "have info when created" in { val (http, database) = setup @@ -70,16 +76,16 @@ object CouchDatabaseSpec extends Specification("CouchDatabase Specification") { "succeed in being deleted" in { val (http, database) = setup - http(database create) must_== () + http(database create) must_== () http(database delete) must_== () } "succeed being recreated" in { val (http, database) = setup - http(database create) must_== () + http(database create) must_== () http(database delete) must_== () - http(database create) must_== () + http(database create) must_== () } } } diff --git a/persistence/couchdb/src/test/scala/net/liftweb/couchdb/CouchDocumentSpec.scala b/persistence/couchdb/src/test/scala/net/liftweb/couchdb/CouchDocumentSpec.scala index 579f1b4f6d..ed616d6f79 100644 --- a/persistence/couchdb/src/test/scala/net/liftweb/couchdb/CouchDocumentSpec.scala +++ b/persistence/couchdb/src/test/scala/net/liftweb/couchdb/CouchDocumentSpec.scala @@ -18,7 +18,7 @@ import java.net.ConnectException import dispatch.{Http, StatusCode} -import org.specs.Specification +import org.specs2.mutable.Specification import common._ import json._ @@ -29,18 +29,24 @@ import DocumentHelpers._ /** * Systems under specification for CouchDocument. */ -object CouchDocumentSpec extends Specification("CouchDocument Specification") { +object CouchDocumentSpec extends Specification { + "CouchDocument Specification".title + sequential + + def hasCode(i: Int): PartialFunction[Throwable, org.specs2.matcher.MatchResult[Any]] = + { case StatusCode(c, _) => c must_== i } + def setup = { val http = new Http val database = new Database("test") - (try { http(database delete) } catch { case StatusCode(_, _) => () }) must not(throwAnException[ConnectException]).orSkipExample + (try { http(database delete) } catch { case StatusCode(_, _) => () }) must not(throwA[ConnectException]).orSkip http(database create) (http, database) } private final def verifyAndOpen[A](b: Box[A]): A = { - b must verify(_.isDefined) + b.isDefined must_== true b.open_! } @@ -51,7 +57,7 @@ object CouchDocumentSpec extends Specification("CouchDocument Specification") { "give 404 on get when nonexistant" in { val (http, database) = setup - http(database("testdoc") fetch) must throwAnException[StatusCode].like { case StatusCode(404, _) => true } + http(database("testdoc") fetch) must throwA[StatusCode].like(hasCode(404)) } "be insertable" in { @@ -69,7 +75,7 @@ object CouchDocumentSpec extends Specification("CouchDocument Specification") { val (http, database) = setup val firstDocBox = http(database post testDoc1) - firstDocBox must verify(_.isDefined) + firstDocBox.isDefined must_== true val Full(firstDoc) = firstDocBox val Full(id) = firstDoc._id val Full(rev) = firstDoc._rev @@ -87,16 +93,16 @@ object CouchDocumentSpec extends Specification("CouchDocument Specification") { val (http, database) = setup val newDoc = verifyAndOpen(http(database store testDoc1)) - http(database(newDoc) @@ newDoc delete) must be () - http(database(newDoc) fetch) must throwAnException[StatusCode].like { case StatusCode(404, _) => true } + http(database(newDoc) @@ newDoc delete) must_== () + http(database(newDoc) fetch) must throwA[StatusCode].like(hasCode(404)) } "give 404 on delete when nonexistant" in { val (http, database) = setup val newDoc = verifyAndOpen(http(database store testDoc1)) - http(database(newDoc) @@ newDoc delete) must be () - http(database(newDoc) @@ newDoc delete) must throwAnException[StatusCode].like { case StatusCode(404, _) => true } + http(database(newDoc) @@ newDoc delete) must_== () + http(database(newDoc) @@ newDoc delete) must throwA[StatusCode].like(hasCode(404)) } "be force storable" in { diff --git a/persistence/couchdb/src/test/scala/net/liftweb/couchdb/CouchQuerySpec.scala b/persistence/couchdb/src/test/scala/net/liftweb/couchdb/CouchQuerySpec.scala index 795576de51..f34d168c43 100644 --- a/persistence/couchdb/src/test/scala/net/liftweb/couchdb/CouchQuerySpec.scala +++ b/persistence/couchdb/src/test/scala/net/liftweb/couchdb/CouchQuerySpec.scala @@ -21,42 +21,45 @@ import dispatch.{Http, StatusCode} import common._ import json._ import JsonDSL._ -import org.specs.Specification +import org.specs2.mutable.Specification import DocumentHelpers.jobjectToJObjectExtension /** * Systems under specification for CouchQuery. */ -object CouchQuerySpec extends Specification("CouchQuery Specification") { +object CouchQuerySpec extends Specification { + "CouchQuery Specification".title + sequential + def setup = { val http = new Http val database = new Database("test") - (try { http(database delete) } catch { case StatusCode(_, _) => () }) must not(throwAnException[ConnectException]).orSkipExample + (try { http(database delete) } catch { case StatusCode(_, _) => () }) must not(throwA[ConnectException]).orSkip http(database create) (http, database) } private def verifyAndOpen[A](b: Box[A]): A = { - b must verify(_.isDefined) + b.isDefined must_== true b.open_! } "Queries" should { - val design: JObject = + val design: JObject = ("language" -> "javascript") ~ ("views" -> (("all_students" -> ("map" -> "function(doc) { if (doc.type == 'student') { emit(null, doc); } }")) ~ ("students_by_age" -> ("map" -> "function(doc) { if (doc.type == 'student') { emit(doc.age, doc); } }")) ~ ("students_by_age_and_name" -> ("map" -> "function(doc) { if (doc.type == 'student') { emit([doc.age, doc.name], doc); } }")))) val docs: List[JObject] = - (("type" -> "student") ~ ("name" -> "Alice") ~ ("age" -> 10)) :: - (("type" -> "student") ~ ("name" -> "Bob") ~ ("age" -> 11)) :: - (("type" -> "student") ~ ("name" -> "Charlie") ~ ("age" -> 11)) :: - (("type" -> "student") ~ ("name" -> "Donna") ~ ("age" -> 12)) :: - (("type" -> "student") ~ ("name" -> "Eric") ~ ("age" -> 12)) :: - (("type" -> "student") ~ ("name" -> "Fran") ~ ("age" -> 13)) :: + (("type" -> "student") ~ ("name" -> "Alice") ~ ("age" -> 10)) :: + (("type" -> "student") ~ ("name" -> "Bob") ~ ("age" -> 11)) :: + (("type" -> "student") ~ ("name" -> "Charlie") ~ ("age" -> 11)) :: + (("type" -> "student") ~ ("name" -> "Donna") ~ ("age" -> 12)) :: + (("type" -> "student") ~ ("name" -> "Eric") ~ ("age" -> 12)) :: + (("type" -> "student") ~ ("name" -> "Fran") ~ ("age" -> 13)) :: (("type" -> "class") ~ ("name" -> "Astronomy")) :: (("type" -> "class") ~ ("name" -> "Baking")) :: (("type" -> "class") ~ ("name" -> "Chemistry")) :: @@ -64,7 +67,7 @@ object CouchQuerySpec extends Specification("CouchQuery Specification") { def findStudents(docs: List[JObject]): List[JObject] = docs.filter(_.isA("student")) - def compareName(a: JObject, b: JObject): Boolean = + def compareName(a: JObject, b: JObject): Boolean = (a.getString("name") openOr "design") < (b.getString("name") openOr "design") def prep(http: Http, database: Database): (JObject, List[JObject]) = { diff --git a/persistence/couchdb/src/test/scala/net/liftweb/couchdb/CouchRecordSpec.scala b/persistence/couchdb/src/test/scala/net/liftweb/couchdb/CouchRecordSpec.scala index 27073a03a2..2da027d618 100644 --- a/persistence/couchdb/src/test/scala/net/liftweb/couchdb/CouchRecordSpec.scala +++ b/persistence/couchdb/src/test/scala/net/liftweb/couchdb/CouchRecordSpec.scala @@ -18,7 +18,7 @@ import java.net.ConnectException import dispatch.{Http, StatusCode} -import org.specs.Specification +import org.specs2.mutable.Specification import common._ import json._ @@ -30,30 +30,33 @@ import DocumentHelpers.stripIdAndRev package couchtestrecords { class Person private () extends CouchRecord[Person] { def meta = Person - + object name extends StringField(this, 200) object age extends IntField(this) } - + object Person extends Person with CouchMetaRecord[Person] - + class Company private () extends CouchRecord[Company] { def meta = Company - + object name extends StringField(this, 200) - } - + } + object Company extends Company with CouchMetaRecord[Company] } /** * Systems under specification for CouchRecord. */ -object CouchRecordSpec extends Specification("CouchRecord Specification") { +object CouchRecordSpec extends Specification { + "CouchRecord Specification".title + sequential + import CouchDB.defaultDatabase import couchtestrecords._ - - val design: JObject = + + val design: JObject = ("language" -> "javascript") ~ ("views" -> (("people_by_age" -> ("map" -> "function(doc) { if (doc.type == 'Person') { emit(doc.age, doc); } }")) ~ ("oldest" -> (("map" -> "function(doc) { if (doc.type == 'Person') { emit(doc.name, doc.age); } }") ~ @@ -61,7 +64,7 @@ object CouchRecordSpec extends Specification("CouchRecord Specification") { def setup = { val database = new Database("test") - (try { Http(database delete) } catch { case StatusCode(_, _) => () }) must not(throwAnException[ConnectException]).orSkipExample + (try { Http(database delete) } catch { case StatusCode(_, _) => () }) must not(throwA[ConnectException]).orSkip Http(database create) Http(database.design("test") put design) defaultDatabase = database @@ -80,7 +83,7 @@ object CouchRecordSpec extends Specification("CouchRecord Specification") { assertEqualPerson(found, expected) } } - + "A couch record" should { def testRec1: Person = Person.createRecord.name("Alice").age(25) val testDoc1: JObject = ("age" -> 25) ~ ("name" -> "Alice") ~ ("type" -> "Person") @@ -90,26 +93,25 @@ object CouchRecordSpec extends Specification("CouchRecord Specification") { "give emtpy box on get when nonexistant" in { setup must_== () - - Person.fetch("testdoc") must verify (!_.isDefined) + + Person.fetch("testdoc").isDefined must_== false } "be insertable" in { setup - + val newRec = testRec1 newRec save assertEqualPerson(newRec, testRec1) newRec.saved_? must_== true - newRec.id.valueBox must verify (_.isDefined) - newRec.rev.valueBox must verify (_.isDefined) + newRec.id.valueBox.isDefined must_== true + newRec.rev.valueBox.isDefined must_== true val Full(foundRec) = Person.fetch(newRec.id.valueBox.open_!) assertEqualPerson(foundRec, testRec1) foundRec.id.valueBox must_== newRec.id.valueBox foundRec.rev.valueBox must_== newRec.rev.valueBox - } "generate the right JSON" in { @@ -126,17 +128,17 @@ object CouchRecordSpec extends Specification("CouchRecord Specification") { val newRec = testRec1 newRec.save - newRec.id.valueBox must verify(_.isDefined) + newRec.id.valueBox.isDefined must_== true val id = newRec.id.valueBox.open_! - Person.fetch(id) must verify(_.isDefined) - newRec.delete_! must verify(_.isDefined) - Person.fetch(id) must not(verify(_.isDefined)) - newRec.delete_! must not(verify(_.isDefined)) + Person.fetch(id).isDefined must_== true + newRec.delete_!.isDefined must_== true + Person.fetch(id).isDefined must_== false + newRec.delete_!.isDefined must_== false newRec.save Http(defaultDatabase(newRec.id.valueBox.open_!) @@ newRec.rev.valueBox.open_! delete) - newRec.delete_! must not(verify(_.isDefined)) + newRec.delete_!.isDefined must_== false } "be fetchable in bulk" in { @@ -154,7 +156,7 @@ object CouchRecordSpec extends Specification("CouchRecord Specification") { val expectedRows = newRec1::newRec3::Nil Person.fetchMany(newRec1.id.valueBox.open_!, newRec3.id.valueBox.open_!).map(_.toList) must beLike { - case Full(foundRows) => assertEqualRows(foundRows, expectedRows); true + case Full(foundRows) => assertEqualRows(foundRows, expectedRows); 1 must_== 1 } } @@ -174,7 +176,7 @@ object CouchRecordSpec extends Specification("CouchRecord Specification") { val expectedRows = newRec2::Nil Person.queryView("test", "people_by_age", _.key(JInt(30))) must beLike { - case Full(foundRows) => assertEqualRows(foundRows, expectedRows); true + case Full(foundRows) => assertEqualRows(foundRows, expectedRows); 1 must_== 1 } } @@ -192,7 +194,7 @@ object CouchRecordSpec extends Specification("CouchRecord Specification") { val expectedRows = newRec1::newRec2::Nil Person.queryViewDocs("test", "oldest", _.dontReduce) must beLike { - case Full(foundRows) => assertEqualRows(foundRows, expectedRows); true + case Full(foundRows) => assertEqualRows(foundRows, expectedRows); 1 must_== 1 } } @@ -210,14 +212,14 @@ object CouchRecordSpec extends Specification("CouchRecord Specification") { val expectedRows = newRec1::newRec2::Nil Person.queryViewDocs("test", "people_by_age", identity) must beLike { - case Full(foundRows) => assertEqualRows(foundRows, expectedRows); true + case Full(foundRows) => assertEqualRows(foundRows, expectedRows); 1 must_== 1 } } "support multiple databases for fetching" in { setup val database2 = new Database("test2") - (try { Http(database2 delete) } catch { case StatusCode(_, _) => () }) must not(throwAnException[ConnectException]).orSkipExample + (try { Http(database2 delete) } catch { case StatusCode(_, _) => () }) must not(throwA[ConnectException]).orSkip Http(database2 create) val newRec = testRec1 @@ -227,19 +229,19 @@ object CouchRecordSpec extends Specification("CouchRecord Specification") { newRec.saved_? must_== true val foundRecBox = Person.fetchFrom(database2, newRec.id.valueBox.open_!) - foundRecBox must verify(_.isDefined) + foundRecBox.isDefined must_== true val Full(foundRec) = foundRecBox assertEqualPerson(foundRec, testRec1) foundRec.id.valueBox must_== newRec.id.valueBox foundRec.rev.valueBox must_== newRec.rev.valueBox - - Person.fetch(newRec.id.valueBox.open_!) must not(verify(_.isDefined)) + + Person.fetch(newRec.id.valueBox.open_!).isDefined must_== false } "support multiple databases for fetching in bulk" in { setup val database2 = new Database("test2") - (try { Http(database2 delete) } catch { case StatusCode(_, _) => () }) must not(throwAnException[ConnectException]).orSkipExample + (try { Http(database2 delete) } catch { case StatusCode(_, _) => () }) must not(throwA[ConnectException]).orSkip Http(database2 create) val newRec1, newRec2, newRec3 = testRec1 @@ -257,16 +259,18 @@ object CouchRecordSpec extends Specification("CouchRecord Specification") { val expectedRows = newRec1::newRec3::Nil Person.fetchManyFrom(database2, newRec1.id.valueBox.open_!, newRec3.id.valueBox.open_!).map(_.toList) must beLike { - case Full(foundRows) => assertEqualRows(foundRows, expectedRows); true + case Full(foundRows) => assertEqualRows(foundRows, expectedRows); 1 must_== 1 } - Person.fetchMany(newRec1.id.valueBox.open_!, newRec3.id.valueBox.open_!) must beLike { case Full(seq) if seq.isEmpty => true } + Person.fetchMany(newRec1.id.valueBox.open_!, newRec3.id.valueBox.open_!) must beLike { + case Full(seq) => seq.isEmpty must_== true + } } "support multiple databases for queries" in { setup val database2 = new Database("test2") - (try { Http(database2 delete) } catch { case StatusCode(_, _) => () }) must not(throwAnException[ConnectException]).orSkipExample + (try { Http(database2 delete) } catch { case StatusCode(_, _) => () }) must not(throwA[ConnectException]).orSkip Http(database2 create) Http(database2.design("test") put design) @@ -286,16 +290,16 @@ object CouchRecordSpec extends Specification("CouchRecord Specification") { val expectedRows = newRec2::Nil Person.queryViewFrom(database2, "test", "people_by_age", _.key(JInt(30))) must beLike { - case Full(foundRows) => assertEqualRows(foundRows, expectedRows); true + case Full(foundRows) => assertEqualRows(foundRows, expectedRows); 1 must_== 1 } - Person.queryView("test", "people_by_age", _.key(JInt(30))) must beLike { case Full(seq) if seq.isEmpty => true } + Person.queryView("test", "people_by_age", _.key(JInt(30))) must beLike { case Full(seq) => seq.isEmpty must_== true } } "support multiple databases for queries returning documents" in { setup val database2 = new Database("test2") - (try { Http(database2 delete) } catch { case StatusCode(_, _) => () }) must not(throwAnException[ConnectException]).orSkipExample + (try { Http(database2 delete) } catch { case StatusCode(_, _) => () }) must not(throwA[ConnectException]).orSkip Http(database2 create) Http(database2.design("test") put design) @@ -312,10 +316,10 @@ object CouchRecordSpec extends Specification("CouchRecord Specification") { val expectedRows = newRec1::newRec2::Nil Person.queryViewDocsFrom(database2, "test", "oldest", _.dontReduce) must beLike { - case Full(foundRows) => assertEqualRows(foundRows, expectedRows); true + case Full(foundRows) => assertEqualRows(foundRows, expectedRows); 1 must_== 1 } - Person.queryViewDocs("test", "oldest", _.dontReduce) must beLike { case Full(seq) if seq.isEmpty => true } + Person.queryViewDocs("test", "oldest", _.dontReduce) must beLike { case Full(seq) => seq.isEmpty must_== true } } } } diff --git a/persistence/couchdb/src/test/scala/net/liftweb/couchdb/JsonRecordSpec.scala b/persistence/couchdb/src/test/scala/net/liftweb/couchdb/JsonRecordSpec.scala index 028e351595..756b02b9e6 100644 --- a/persistence/couchdb/src/test/scala/net/liftweb/couchdb/JsonRecordSpec.scala +++ b/persistence/couchdb/src/test/scala/net/liftweb/couchdb/JsonRecordSpec.scala @@ -17,7 +17,8 @@ package net.liftweb package couchdb -import org.specs.Specification +import org.specs2.mutable.Specification +import org.specs2.execute.Result import common._ import json._ @@ -37,7 +38,6 @@ package jsontestrecords { object favoriteColor extends OptionalStringField(this, 200) object address extends JSONSubRecordField(this, Address, Empty) { override def optional_? = true - } } @@ -61,10 +61,13 @@ package jsontestrecords { /** * Systems under specification for JsonRecord. */ -object JsonRecordSpec extends Specification("JsonRecord Specification") { +object JsonRecordSpec extends Specification { + "JsonRecord Specification".title + sequential + import jsontestrecords._ - def assertEqualPerson(a: Person, b: Person) = { + def assertEqualPerson(a: Person, b: Person): Result = { a.name.valueBox must_== b.name.valueBox a.age.valueBox must_== b.age.valueBox a.favoriteColor.valueBox must_== b.favoriteColor.valueBox @@ -74,7 +77,8 @@ object JsonRecordSpec extends Specification("JsonRecord Specification") { aa.postalCode.valueBox must_== aa.postalCode.valueBox aa.city.valueBox must_== aa.city.valueBox aa.street.valueBox must_== aa.street.valueBox - } + } + success } "A JSON record" should { @@ -94,19 +98,19 @@ object JsonRecordSpec extends Specification("JsonRecord Specification") { "encode record with subrecord correctly" in { compact(render(testRec4.asJValue)) must_== compact(render(testDoc4)) } - + "decode basic records correctly" in { val recBox = Person.fromJValue(testDoc1) - recBox must verify (_.isDefined) + recBox.isDefined must_== true val Full(rec) = recBox assertEqualPerson(rec, testRec1) } "preserve extra fields from JSON" in { val recBox = Person.fromJValue(testDoc2) - recBox must verify (_.isDefined) + recBox.isDefined must_== true val Full(rec) = recBox - rec.additionalJFields must_== List(JField("extra1", JString("value1")), + rec.additionalJFields must_== List(JField("extra1", JString("value1")), JField("extra2", JString("value2"))) rec.age.set(1) @@ -115,15 +119,15 @@ object JsonRecordSpec extends Specification("JsonRecord Specification") { "support unset optional fields" in { val recBox = Person.fromJValue(testDoc1) - recBox must verify (_.isDefined) + recBox.isDefined must_== true val Full(rec) = recBox - rec.favoriteColor.value must not (verify (_.isDefined)) + rec.favoriteColor.value.isDefined must_== false } "support set optional fields" in { val recBox = Person.fromJValue(testDoc2) - recBox must verify (_.isDefined) + recBox.isDefined must_== true val Full(rec) = recBox rec.favoriteColor.value must_== Some("blue") @@ -131,7 +135,7 @@ object JsonRecordSpec extends Specification("JsonRecord Specification") { "support set subRecord field" in { val recBox = Person.fromJValue(testDoc4) - recBox must verify (_.isDefined) + recBox.isDefined must_== true val Full(rec) = recBox rec.address.valueBox.flatMap(_.street.valueBox) must_== Full("my street") @@ -146,22 +150,22 @@ object JsonRecordSpec extends Specification("JsonRecord Specification") { "honor overrideIgnoreExtraJSONFields == true" in { val recBox = JSONMetaRecord.overrideIgnoreExtraJSONFields.doWith(true) { Person.fromJValue(testDoc2) } - recBox must verify (_.isDefined) + recBox.isDefined must_== true } "honor overrideIgnoreExtraJSONFields == false" in { val recBox = JSONMetaRecord.overrideIgnoreExtraJSONFields.doWith(false) { Person.fromJValue(testDoc2) } - recBox must not (verify (_.isDefined)) + recBox.isDefined must_== false } "honor overrideNeedAllJSONFields == true" in { val recBox = JSONMetaRecord.overrideNeedAllJSONFields.doWith(true) { Person.fromJValue(testDoc3) } - recBox must not (verify (_.isDefined)) + recBox.isDefined must_== false } "honor overrideNeedAllJSONFields == false" in { val recBox = JSONMetaRecord.overrideNeedAllJSONFields.doWith(false) { Person.fromJValue(testDoc3) } - recBox must verify (_.isDefined) + recBox.isDefined must_== true } } } From 0e1e2a390f18c467267a04eefdcdb0e160d28786 Mon Sep 17 00:00:00 2001 From: David Pollak Date: Tue, 31 Jul 2012 08:59:09 -0700 Subject: [PATCH 0134/1949] Enhanced the opionation of Box --- .../main/scala/net/liftweb/common/Box.scala | 146 ++++++++++++++---- 1 file changed, 112 insertions(+), 34 deletions(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index 2a04e77861..5bcf52f033 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -206,6 +206,33 @@ sealed abstract class Box[+A] extends Product with Serializable{ */ def isDefined: Boolean = !isEmpty + /** + * Yeah, Yeah, Yeah... you think it's alright to access references that may or may not be legal to access... + * well, it's really stupid idea. + * + * One of the major points of using Scala is to avoid things like Null Pointer Exceptions and Box + * is a key way to do this. You can access the value of the Box (if and only if the Box contains a value) + * with a for comprehension or with the map, flatMap and foreach methods. Using this method, + * however is the worst way to access to contents of the Box. + * + * But, but, but you say, this causes a fail-fast exception. + * + * Dude... you don't want exceptions in your code. Exceptions are for exceptional situations such as + * your disk failing or something on an external system. Exceptions are not about the logic of your application. + * Your logic should work with all input values in your application. + * + * This method name reflects the design quality related to using this method. It's also easy to + * find by grep'ing through your code... and when people see the method call in a code review... well... + * + * @param nameOfPersonThatThoughtThisWasAGoodIdea -- yes... your name will be included in every exception + * thrown by calling this method + * @param lameButSemiPlausableReasonWhyYouThinkThisIsAGoodIdea -- Okay, Mr. Smartypants, you think it's a good idea + * calling this method. Tell us why. + * @return The contents of the Box if it has one or an exception if not + */ + def openTheBoxButThisMightResultInAnExceptionSoDontUseThisMethod(nameOfPersonThatThoughtThisWasAGoodIdea: String, + lameButSemiPlausableReasonWhyYouThinkThisIsAGoodIdea: String): A + /** * Return the value contained in this Box if it is Full; * throw an exception otherwise. @@ -221,8 +248,8 @@ sealed abstract class Box[+A] extends Product with Serializable{ * * @return the value contained in this Box if it is full; throw an exception otherwise */ - @deprecated("Don't use this method unless you are guaranteed of a Full Box", "2.4") - def open_! : A + @deprecated("use openTheBoxButThisMightResultInAnExceptionSoDontUseThisMethod, or better yet, do the right thing with your code and use map, flatMap or foreach", "2.4") + final def open_! : A = openTheBoxButThisMightResultInAnExceptionSoDontUseThisMethod("dpp", "Implementing the deprecated method that you shouldn't be using") /** * Return the value contained in this Box if it is Full; @@ -235,7 +262,8 @@ sealed abstract class Box[+A] extends Product with Serializable{ * * @return the value contained in this Box if it is full; throw an exception otherwise */ - def openTheBox: A = this.open_! + @deprecated("use openTheBoxButThisMightResultInAnExceptionSoDontUseThisMethod, or better yet, do the right thing with your code and use map, flatMap or foreach", "2.4") + final def openTheBox: A = openTheBoxButThisMightResultInAnExceptionSoDontUseThisMethod("dpp", "Implementing the deprecated method that you shouldn't be using") /** * Return the value contained in this Box if it is full; otherwise return the specified default @@ -480,19 +508,34 @@ final case class Full[+A](value: A) extends Box[A]{ def isEmpty: Boolean = false - + /** - * Return the value contained in this Box if it is Full; - * throw an exception otherwise. - * The method has a '!' in its name. This means "don't use it unless - * you are 100% sure that the Box is Full and you should probably - * comment your code with the explanation of the guaranty. - * The better case for extracting the value out of a Box can - * be found at http://lift.la/scala-option-lift-box-and-how-to-make-your-co + * Yeah, Yeah, Yeah... you think it's alright to access references that may or may not be legal to access... + * well, it's really stupid idea. * - * @return the value contained in this Box if it is full; throw an exception otherwise + * One of the major points of using Scala is to avoid things like Null Pointer Exceptions and Box + * is a key way to do this. You can access the value of the Box (if and only if the Box contains a value) + * with a for comprehension or with the map, flatMap and foreach methods. Using this method, + * however is the worst way to access to contents of the Box. + * + * But, but, but you say, this causes a fail-fast exception. + * + * Dude... you don't want exceptions in your code. Exceptions are for exceptional situations such as + * your disk failing or something on an external system. Exceptions are not about the logic of your application. + * Your logic should work with all input values in your application. + * + * This method name reflects the design quality related to using this method. It's also easy to + * find by grep'ing through your code... and when people see the method call in a code review... well... + * + * @param nameOfPersonThatThoughtThisWasAGoodIdea -- yes... your name will be included in every exception + * thrown by calling this method + * @param lameButPlausableReasonWhyYouThinkThisIsAGoodIdea -- Okay, Mr. Smartypants, you think it's a good idea + * calling this method. Tell us why. + * @return The contents of the Box if it has one or an exception if not */ - def open_! : A = value + def openTheBoxButThisMightResultInAnExceptionSoDontUseThisMethod(nameOfPersonThatThoughtThisWasAGoodIdea: String, + lameButPlausableReasonWhyYouThinkThisIsAGoodIdea: String): A = value + override def openOr[B >: A](default: => B): B = value @@ -564,17 +607,34 @@ sealed abstract class EmptyBox extends Box[Nothing] with Serializable { def isEmpty: Boolean = true /** - * Return the value contained in this Box if it is Full; - * throw an exception otherwise. - * The method has a '!' in its name. This means "don't use it unless - * you are 100% sure that the Box is Full and you should probably - * comment your code with the explanation of the guaranty. - * The better case for extracting the value out of a Box can - * be found at http://lift.la/scala-option-lift-box-and-how-to-make-your-co + * Yeah, Yeah, Yeah... you think it's alright to access references that may or may not be legal to access... + * well, it's really stupid idea. * - * @return the value contained in this Box if it is full; throw an exception otherwise + * One of the major points of using Scala is to avoid things like Null Pointer Exceptions and Box + * is a key way to do this. You can access the value of the Box (if and only if the Box contains a value) + * with a for comprehension or with the map, flatMap and foreach methods. Using this method, + * however is the worst way to access to contents of the Box. + * + * But, but, but you say, this causes a fail-fast exception. + * + * Dude... you don't want exceptions in your code. Exceptions are for exceptional situations such as + * your disk failing or something on an external system. Exceptions are not about the logic of your application. + * Your logic should work with all input values in your application. + * + * This method name reflects the design quality related to using this method. It's also easy to + * find by grep'ing through your code... and when people see the method call in a code review... well... + * + * @param nameOfPersonThatThoughtThisWasAGoodIdea -- yes... your name will be included in every exception + * thrown by calling this method + * @param lameButPlausableReasonWhyYouThinkThisIsAGoodIdea -- Okay, Mr. Smartypants, you think it's a good idea + * calling this method. Tell us why. + * @return The contents of the Box if it has one or an exception if not */ - def open_! = throw new NullPointerException("Trying to open an empty Box") + def openTheBoxButThisMightResultInAnExceptionSoDontUseThisMethod(nameOfPersonThatThoughtThisWasAGoodIdea: String, + lameButPlausableReasonWhyYouThinkThisIsAGoodIdea: String) = + throw new NullPointerException("So, "+nameOfPersonThatThoughtThisWasAGoodIdea+" thought it might be a good idea to open a Box even though it could be empty. The justification was '"+lameButPlausableReasonWhyYouThinkThisIsAGoodIdea+"'. Turns out it was a bad idea") + + override def openOr[B >: Nothing](default: => B): B = default @@ -604,20 +664,38 @@ object Failure { sealed case class Failure(msg: String, exception: Box[Throwable], chain: Box[Failure]) extends EmptyBox{ type A = Nothing + /** - * Return the value contained in this Box if it is Full; - * throw an exception otherwise. - * The method has a '!' in its name. This means "don't use it unless - * you are 100% sure that the Box is Full and you should probably - * comment your code with the explanation of the guaranty. - * The better case for extracting the value out of a Box can - * be found at http://lift.la/scala-option-lift-box-and-how-to-make-your-co + * Yeah, Yeah, Yeah... you think it's alright to access references that may or may not be legal to access... + * well, it's really stupid idea. * - * @return the value contained in this Box if it is full; throw an exception otherwise - */ - override def open_! = throw new NullPointerException("Trying to open a Failure Box: " + msg) { - override def getCause() = exception openOr null - } + * One of the major points of using Scala is to avoid things like Null Pointer Exceptions and Box + * is a key way to do this. You can access the value of the Box (if and only if the Box contains a value) + * with a for comprehension or with the map, flatMap and foreach methods. Using this method, + * however is the worst way to access to contents of the Box. + * + * But, but, but you say, this causes a fail-fast exception. + * + * Dude... you don't want exceptions in your code. Exceptions are for exceptional situations such as + * your disk failing or something on an external system. Exceptions are not about the logic of your application. + * Your logic should work with all input values in your application. + * + * This method name reflects the design quality related to using this method. It's also easy to + * find by grep'ing through your code... and when people see the method call in a code review... well... + * + * @param nameOfPersonThatThoughtThisWasAGoodIdea -- yes... your name will be included in every exception + * thrown by calling this method + * @param lameButPlausableReasonWhyYouThinkThisIsAGoodIdea -- Okay, Mr. Smartypants, you think it's a good idea + * calling this method. Tell us why. + * @return The contents of the Box if it has one or an exception if not + */ + override def openTheBoxButThisMightResultInAnExceptionSoDontUseThisMethod(nameOfPersonThatThoughtThisWasAGoodIdea: String, + lameButPlausableReasonWhyYouThinkThisIsAGoodIdea: String) = + throw new NullPointerException("So, "+nameOfPersonThatThoughtThisWasAGoodIdea+ + " thought it might be a good idea to open a Box even though it could be empty. The justification was '"+ + lameButPlausableReasonWhyYouThinkThisIsAGoodIdea+"'. Turns out it was a bad idea. By the way, the Failure that was opened contained the error message: '"+msg+"'.") { + override def getCause() = exception openOr null + } override def map[B](f: A => B): Box[B] = this From 8779c8ff30b4c8199e127eb13e88cbddb654c3c1 Mon Sep 17 00:00:00 2001 From: David Pollak Date: Tue, 31 Jul 2012 09:08:25 -0700 Subject: [PATCH 0135/1949] Enhanced the opionation of Box -- fixed typos --- .../main/scala/net/liftweb/common/Box.scala | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index 5bcf52f033..6849632962 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -226,12 +226,12 @@ sealed abstract class Box[+A] extends Product with Serializable{ * * @param nameOfPersonThatThoughtThisWasAGoodIdea -- yes... your name will be included in every exception * thrown by calling this method - * @param lameButSemiPlausableReasonWhyYouThinkThisIsAGoodIdea -- Okay, Mr. Smartypants, you think it's a good idea + * @param lameButSemiPlausibleReasonWhyYouThinkThisIsAGoodIdea -- Okay, Mr. Smartypants, you think it's a good idea * calling this method. Tell us why. * @return The contents of the Box if it has one or an exception if not */ def openTheBoxButThisMightResultInAnExceptionSoDontUseThisMethod(nameOfPersonThatThoughtThisWasAGoodIdea: String, - lameButSemiPlausableReasonWhyYouThinkThisIsAGoodIdea: String): A + lameButSemiPlausibleReasonWhyYouThinkThisIsAGoodIdea: String): A /** * Return the value contained in this Box if it is Full; @@ -529,12 +529,12 @@ final case class Full[+A](value: A) extends Box[A]{ * * @param nameOfPersonThatThoughtThisWasAGoodIdea -- yes... your name will be included in every exception * thrown by calling this method - * @param lameButPlausableReasonWhyYouThinkThisIsAGoodIdea -- Okay, Mr. Smartypants, you think it's a good idea + * @param lameButSemiPlausibleReasonWhyYouThinkThisIsAGoodIdea -- Okay, Mr. Smartypants, you think it's a good idea * calling this method. Tell us why. * @return The contents of the Box if it has one or an exception if not */ def openTheBoxButThisMightResultInAnExceptionSoDontUseThisMethod(nameOfPersonThatThoughtThisWasAGoodIdea: String, - lameButPlausableReasonWhyYouThinkThisIsAGoodIdea: String): A = value + lameButSemiPlausibleReasonWhyYouThinkThisIsAGoodIdea: String): A = value override def openOr[B >: A](default: => B): B = value @@ -626,13 +626,15 @@ sealed abstract class EmptyBox extends Box[Nothing] with Serializable { * * @param nameOfPersonThatThoughtThisWasAGoodIdea -- yes... your name will be included in every exception * thrown by calling this method - * @param lameButPlausableReasonWhyYouThinkThisIsAGoodIdea -- Okay, Mr. Smartypants, you think it's a good idea + * @param lameButSemiPlausibleReasonWhyYouThinkThisIsAGoodIdea -- Okay, Mr. Smartypants, you think it's a good idea * calling this method. Tell us why. * @return The contents of the Box if it has one or an exception if not */ def openTheBoxButThisMightResultInAnExceptionSoDontUseThisMethod(nameOfPersonThatThoughtThisWasAGoodIdea: String, - lameButPlausableReasonWhyYouThinkThisIsAGoodIdea: String) = - throw new NullPointerException("So, "+nameOfPersonThatThoughtThisWasAGoodIdea+" thought it might be a good idea to open a Box even though it could be empty. The justification was '"+lameButPlausableReasonWhyYouThinkThisIsAGoodIdea+"'. Turns out it was a bad idea") + lameButSemiPlausibleReasonWhyYouThinkThisIsAGoodIdea: String) = + throw new NullPointerException("So, "+nameOfPersonThatThoughtThisWasAGoodIdea+ + " thought it might be a good idea to open a Box even though it could be empty. The justification was '"+ + lameButSemiPlausibleReasonWhyYouThinkThisIsAGoodIdea+"'. Turns out it was a bad idea") @@ -685,15 +687,17 @@ sealed case class Failure(msg: String, exception: Box[Throwable], chain: Box[Fai * * @param nameOfPersonThatThoughtThisWasAGoodIdea -- yes... your name will be included in every exception * thrown by calling this method - * @param lameButPlausableReasonWhyYouThinkThisIsAGoodIdea -- Okay, Mr. Smartypants, you think it's a good idea + * @param lameButSemiPlausibleReasonWhyYouThinkThisIsAGoodIdea -- Okay, Mr. Smartypants, you think it's a good idea * calling this method. Tell us why. * @return The contents of the Box if it has one or an exception if not */ override def openTheBoxButThisMightResultInAnExceptionSoDontUseThisMethod(nameOfPersonThatThoughtThisWasAGoodIdea: String, - lameButPlausableReasonWhyYouThinkThisIsAGoodIdea: String) = + lameButSemiPlausibleReasonWhyYouThinkThisIsAGoodIdea: String) = throw new NullPointerException("So, "+nameOfPersonThatThoughtThisWasAGoodIdea+ " thought it might be a good idea to open a Box even though it could be empty. The justification was '"+ - lameButPlausableReasonWhyYouThinkThisIsAGoodIdea+"'. Turns out it was a bad idea. By the way, the Failure that was opened contained the error message: '"+msg+"'.") { + lameButSemiPlausibleReasonWhyYouThinkThisIsAGoodIdea+ + "'. Turns out it was a bad idea. By the way, the Failure that was opened contained the error message: '"+ + msg+"'.") { override def getCause() = exception openOr null } From 077e0db26f0035851f1a10cdb374d02c63e4cd82 Mon Sep 17 00:00:00 2001 From: David Pollak Date: Tue, 31 Jul 2012 10:30:33 -0700 Subject: [PATCH 0136/1949] Reduced opinionation for Box opening methods --- .../main/scala/net/liftweb/common/Box.scala | 169 +++++++++--------- 1 file changed, 85 insertions(+), 84 deletions(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index 6849632962..d4a74d7635 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -207,31 +207,32 @@ sealed abstract class Box[+A] extends Product with Serializable{ def isDefined: Boolean = !isEmpty /** - * Yeah, Yeah, Yeah... you think it's alright to access references that may or may not be legal to access... - * well, it's really stupid idea. + * If you grew up on Java, you're used to Exceptions as part of your program logic. + * The Scala philosophy and the Lift philosophy is that exceptions are for exceptional + * conditions such as failure of an external resource (e.g., your database goes offline) + * rather than simply indicating that a parameter wasn't supplied or couldn't be parsed. * - * One of the major points of using Scala is to avoid things like Null Pointer Exceptions and Box - * is a key way to do this. You can access the value of the Box (if and only if the Box contains a value) - * with a for comprehension or with the map, flatMap and foreach methods. Using this method, - * however is the worst way to access to contents of the Box. + * Lift's Box and Scala's Option provide a mechanism for being explicit about a value + * existing or not existing rather than relying on a reference being not-null. However, + * extracting a value from a Box should be done correctly. Correctly can be (in order of use + * in David Pollak's code): a for comprehension; using map, flatMap or foreach; or using pattern matching. * - * But, but, but you say, this causes a fail-fast exception. + * The only times when you should be using this method are: the value is guaranteed to be available based + * on a guard outside of the method using the Box or in tests. For example, + * User.currentUser.openOrThrowException("This snippet is used on pages where the user is logged in") * - * Dude... you don't want exceptions in your code. Exceptions are for exceptional situations such as - * your disk failing or something on an external system. Exceptions are not about the logic of your application. - * Your logic should work with all input values in your application. + * A valid justification for using this method should not be "I want my code to fail fast when I call it." + * Using exceptions in the core logic of your application should be strongly discouraged. * - * This method name reflects the design quality related to using this method. It's also easy to - * find by grep'ing through your code... and when people see the method call in a code review... well... + * This method replaces open_! because people used open_! and generally ignored the reason for the "!", + * so we're making it more explicit that this method should not commonly be used and should be justified + * when used. + * + * @param justification Justify why calling this method is okay and why it will not result in an Exception * - * @param nameOfPersonThatThoughtThisWasAGoodIdea -- yes... your name will be included in every exception - * thrown by calling this method - * @param lameButSemiPlausibleReasonWhyYouThinkThisIsAGoodIdea -- Okay, Mr. Smartypants, you think it's a good idea - * calling this method. Tell us why. * @return The contents of the Box if it has one or an exception if not */ - def openTheBoxButThisMightResultInAnExceptionSoDontUseThisMethod(nameOfPersonThatThoughtThisWasAGoodIdea: String, - lameButSemiPlausibleReasonWhyYouThinkThisIsAGoodIdea: String): A + def openOrThrowException(justification: String): A /** * Return the value contained in this Box if it is Full; @@ -248,8 +249,8 @@ sealed abstract class Box[+A] extends Product with Serializable{ * * @return the value contained in this Box if it is full; throw an exception otherwise */ - @deprecated("use openTheBoxButThisMightResultInAnExceptionSoDontUseThisMethod, or better yet, do the right thing with your code and use map, flatMap or foreach", "2.4") - final def open_! : A = openTheBoxButThisMightResultInAnExceptionSoDontUseThisMethod("dpp", "Implementing the deprecated method that you shouldn't be using") + @deprecated("use openOrThrowException, or better yet, do the right thing with your code and use map, flatMap or foreach", "2.4") + final def open_! : A = openOrThrowException("Legacy method implementation") /** * Return the value contained in this Box if it is Full; @@ -262,8 +263,8 @@ sealed abstract class Box[+A] extends Product with Serializable{ * * @return the value contained in this Box if it is full; throw an exception otherwise */ - @deprecated("use openTheBoxButThisMightResultInAnExceptionSoDontUseThisMethod, or better yet, do the right thing with your code and use map, flatMap or foreach", "2.4") - final def openTheBox: A = openTheBoxButThisMightResultInAnExceptionSoDontUseThisMethod("dpp", "Implementing the deprecated method that you shouldn't be using") + @deprecated("use openOrThrowException, or better yet, do the right thing with your code and use map, flatMap or foreach", "2.4") + final def openTheBox: A = openOrThrowException("Legacy method implementation") /** * Return the value contained in this Box if it is full; otherwise return the specified default @@ -509,32 +510,34 @@ final case class Full[+A](value: A) extends Box[A]{ def isEmpty: Boolean = false + /** - * Yeah, Yeah, Yeah... you think it's alright to access references that may or may not be legal to access... - * well, it's really stupid idea. + * If you grew up on Java, you're used to Exceptions as part of your program logic. + * The Scala philosophy and the Lift philosophy is that exceptions are for exceptional + * conditions such as failure of an external resource (e.g., your database goes offline) + * rather than simply indicating that a parameter wasn't supplied or couldn't be parsed. + * + * Lift's Box and Scala's Option provide a mechanism for being explicit about a value + * existing or not existing rather than relying on a reference being not-null. However, + * extracting a value from a Box should be done correctly. Correctly can be (in order of use + * in David Pollak's code): a for comprehension; using map, flatMap or foreach; or using pattern matching. * - * One of the major points of using Scala is to avoid things like Null Pointer Exceptions and Box - * is a key way to do this. You can access the value of the Box (if and only if the Box contains a value) - * with a for comprehension or with the map, flatMap and foreach methods. Using this method, - * however is the worst way to access to contents of the Box. + * The only times when you should be using this method are: the value is guaranteed to be available based + * on a guard outside of the method using the Box or in tests. For example, + * User.currentUser.openOrThrowException("This snippet is used on pages where the user is logged in") * - * But, but, but you say, this causes a fail-fast exception. + * A valid justification for using this method should not be "I want my code to fail fast when I call it." + * Using exceptions in the core logic of your application should be strongly discouraged. * - * Dude... you don't want exceptions in your code. Exceptions are for exceptional situations such as - * your disk failing or something on an external system. Exceptions are not about the logic of your application. - * Your logic should work with all input values in your application. + * This method replaces open_! because people used open_! and generally ignored the reason for the "!", + * so we're making it more explicit that this method should not commonly be used and should be justified + * when used. * - * This method name reflects the design quality related to using this method. It's also easy to - * find by grep'ing through your code... and when people see the method call in a code review... well... + * @param justification Justify why calling this method is okay and why it will not result in an Exception * - * @param nameOfPersonThatThoughtThisWasAGoodIdea -- yes... your name will be included in every exception - * thrown by calling this method - * @param lameButSemiPlausibleReasonWhyYouThinkThisIsAGoodIdea -- Okay, Mr. Smartypants, you think it's a good idea - * calling this method. Tell us why. * @return The contents of the Box if it has one or an exception if not */ - def openTheBoxButThisMightResultInAnExceptionSoDontUseThisMethod(nameOfPersonThatThoughtThisWasAGoodIdea: String, - lameButSemiPlausibleReasonWhyYouThinkThisIsAGoodIdea: String): A = value + def openOrThrowException(justification: String): A = value override def openOr[B >: A](default: => B): B = value @@ -606,35 +609,35 @@ sealed abstract class EmptyBox extends Box[Nothing] with Serializable { def isEmpty: Boolean = true + /** - * Yeah, Yeah, Yeah... you think it's alright to access references that may or may not be legal to access... - * well, it's really stupid idea. + * If you grew up on Java, you're used to Exceptions as part of your program logic. + * The Scala philosophy and the Lift philosophy is that exceptions are for exceptional + * conditions such as failure of an external resource (e.g., your database goes offline) + * rather than simply indicating that a parameter wasn't supplied or couldn't be parsed. + * + * Lift's Box and Scala's Option provide a mechanism for being explicit about a value + * existing or not existing rather than relying on a reference being not-null. However, + * extracting a value from a Box should be done correctly. Correctly can be (in order of use + * in David Pollak's code): a for comprehension; using map, flatMap or foreach; or using pattern matching. * - * One of the major points of using Scala is to avoid things like Null Pointer Exceptions and Box - * is a key way to do this. You can access the value of the Box (if and only if the Box contains a value) - * with a for comprehension or with the map, flatMap and foreach methods. Using this method, - * however is the worst way to access to contents of the Box. + * The only times when you should be using this method are: the value is guaranteed to be available based + * on a guard outside of the method using the Box or in tests. For example, + * User.currentUser.openOrThrowException("This snippet is used on pages where the user is logged in") * - * But, but, but you say, this causes a fail-fast exception. + * A valid justification for using this method should not be "I want my code to fail fast when I call it." + * Using exceptions in the core logic of your application should be strongly discouraged. * - * Dude... you don't want exceptions in your code. Exceptions are for exceptional situations such as - * your disk failing or something on an external system. Exceptions are not about the logic of your application. - * Your logic should work with all input values in your application. + * This method replaces open_! because people used open_! and generally ignored the reason for the "!", + * so we're making it more explicit that this method should not commonly be used and should be justified + * when used. * - * This method name reflects the design quality related to using this method. It's also easy to - * find by grep'ing through your code... and when people see the method call in a code review... well... + * @param justification Justify why calling this method is okay and why it will not result in an Exception * - * @param nameOfPersonThatThoughtThisWasAGoodIdea -- yes... your name will be included in every exception - * thrown by calling this method - * @param lameButSemiPlausibleReasonWhyYouThinkThisIsAGoodIdea -- Okay, Mr. Smartypants, you think it's a good idea - * calling this method. Tell us why. * @return The contents of the Box if it has one or an exception if not */ - def openTheBoxButThisMightResultInAnExceptionSoDontUseThisMethod(nameOfPersonThatThoughtThisWasAGoodIdea: String, - lameButSemiPlausibleReasonWhyYouThinkThisIsAGoodIdea: String) = - throw new NullPointerException("So, "+nameOfPersonThatThoughtThisWasAGoodIdea+ - " thought it might be a good idea to open a Box even though it could be empty. The justification was '"+ - lameButSemiPlausibleReasonWhyYouThinkThisIsAGoodIdea+"'. Turns out it was a bad idea") + def openOrThrowException(justification: String) = + throw new NullPointerException("An Empty Box was opened. The justifcation for allowing the openOrThrowException was "+justification) @@ -668,36 +671,34 @@ sealed case class Failure(msg: String, exception: Box[Throwable], chain: Box[Fai /** - * Yeah, Yeah, Yeah... you think it's alright to access references that may or may not be legal to access... - * well, it's really stupid idea. + * If you grew up on Java, you're used to Exceptions as part of your program logic. + * The Scala philosophy and the Lift philosophy is that exceptions are for exceptional + * conditions such as failure of an external resource (e.g., your database goes offline) + * rather than simply indicating that a parameter wasn't supplied or couldn't be parsed. + * + * Lift's Box and Scala's Option provide a mechanism for being explicit about a value + * existing or not existing rather than relying on a reference being not-null. However, + * extracting a value from a Box should be done correctly. Correctly can be (in order of use + * in David Pollak's code): a for comprehension; using map, flatMap or foreach; or using pattern matching. * - * One of the major points of using Scala is to avoid things like Null Pointer Exceptions and Box - * is a key way to do this. You can access the value of the Box (if and only if the Box contains a value) - * with a for comprehension or with the map, flatMap and foreach methods. Using this method, - * however is the worst way to access to contents of the Box. + * The only times when you should be using this method are: the value is guaranteed to be available based + * on a guard outside of the method using the Box or in tests. For example, + * User.currentUser.openOrThrowException("This snippet is used on pages where the user is logged in") * - * But, but, but you say, this causes a fail-fast exception. + * A valid justification for using this method should not be "I want my code to fail fast when I call it." + * Using exceptions in the core logic of your application should be strongly discouraged. * - * Dude... you don't want exceptions in your code. Exceptions are for exceptional situations such as - * your disk failing or something on an external system. Exceptions are not about the logic of your application. - * Your logic should work with all input values in your application. + * This method replaces open_! because people used open_! and generally ignored the reason for the "!", + * so we're making it more explicit that this method should not commonly be used and should be justified + * when used. * - * This method name reflects the design quality related to using this method. It's also easy to - * find by grep'ing through your code... and when people see the method call in a code review... well... + * @param justification Justify why calling this method is okay and why it will not result in an Exception * - * @param nameOfPersonThatThoughtThisWasAGoodIdea -- yes... your name will be included in every exception - * thrown by calling this method - * @param lameButSemiPlausibleReasonWhyYouThinkThisIsAGoodIdea -- Okay, Mr. Smartypants, you think it's a good idea - * calling this method. Tell us why. * @return The contents of the Box if it has one or an exception if not */ - override def openTheBoxButThisMightResultInAnExceptionSoDontUseThisMethod(nameOfPersonThatThoughtThisWasAGoodIdea: String, - lameButSemiPlausibleReasonWhyYouThinkThisIsAGoodIdea: String) = - throw new NullPointerException("So, "+nameOfPersonThatThoughtThisWasAGoodIdea+ - " thought it might be a good idea to open a Box even though it could be empty. The justification was '"+ - lameButSemiPlausibleReasonWhyYouThinkThisIsAGoodIdea+ - "'. Turns out it was a bad idea. By the way, the Failure that was opened contained the error message: '"+ - msg+"'.") { + override def openOrThrowException(justification: String) = + throw new NullPointerException("An Failure Box was opened. Failure Message: "+msg+ + ". The justifcation for allowing the openOrThrowException was "+justification) { override def getCause() = exception openOr null } From c1164fe5b0eb8aa798a2a9c4c570612883af0c73 Mon Sep 17 00:00:00 2001 From: David Pollak Date: Tue, 31 Jul 2012 11:04:53 -0700 Subject: [PATCH 0137/1949] Fixed a type-o --- core/common/src/main/scala/net/liftweb/common/Box.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index d4a74d7635..0e6af67052 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -637,7 +637,7 @@ sealed abstract class EmptyBox extends Box[Nothing] with Serializable { * @return The contents of the Box if it has one or an exception if not */ def openOrThrowException(justification: String) = - throw new NullPointerException("An Empty Box was opened. The justifcation for allowing the openOrThrowException was "+justification) + throw new NullPointerException("An Empty Box was opened. The justification for allowing the openOrThrowException was "+justification) @@ -698,7 +698,7 @@ sealed case class Failure(msg: String, exception: Box[Throwable], chain: Box[Fai */ override def openOrThrowException(justification: String) = throw new NullPointerException("An Failure Box was opened. Failure Message: "+msg+ - ". The justifcation for allowing the openOrThrowException was "+justification) { + ". The justification for allowing the openOrThrowException was "+justification) { override def getCause() = exception openOr null } From 15b7d7312ae0d1f708d6f2c0a69b06b0301ef042 Mon Sep 17 00:00:00 2001 From: Naftoli Gugenheim Date: Tue, 31 Jul 2012 19:20:54 -0400 Subject: [PATCH 0138/1949] lift-mongo (tests pass now) --- .../net/liftweb/mongodb/BsonDSLSpec.scala | 8 +++-- .../mongodb/CustomSerializersSpec.scala | 13 ++++---- .../liftweb/mongodb/JObjectParserSpec.scala | 13 ++++---- .../net/liftweb/mongodb/MongoDirectSpec.scala | 7 +++-- .../mongodb/MongoDocumentExamplesSpec.scala | 31 ++++++++++++++----- .../liftweb/mongodb/MongoDocumentSpec.scala | 8 +++-- .../scala/net/liftweb/mongodb/MongoSpec.scala | 15 ++++++--- .../net/liftweb/mongodb/MongoTestKit.scala | 13 ++++---- .../liftweb/mongodb/QueryExamplesSpec.scala | 6 ++-- 9 files changed, 73 insertions(+), 41 deletions(-) diff --git a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/BsonDSLSpec.scala b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/BsonDSLSpec.scala index 5c176e868c..d2230093c4 100644 --- a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/BsonDSLSpec.scala +++ b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/BsonDSLSpec.scala @@ -27,11 +27,13 @@ import java.util.{Date, UUID} import java.util.regex.Pattern import org.bson.types.ObjectId -import org.specs.Specification +import org.specs2.mutable.Specification import com.mongodb.{BasicDBList, DBObject} -object BsonDSLSpec extends Specification("BsonDSL Specification") { +object BsonDSLSpec extends Specification { + "BsonDSL Specification".title + "BsonDSL" should { "Convert ObjectId properly" in { val oid: ObjectId = ObjectId.get @@ -69,7 +71,7 @@ object BsonDSLSpec extends Specification("BsonDSL Specification") { val dbo: DBObject = JObjectParser.parse(qry)(DefaultFormats) val ptrnList2: List[Pattern] = dbo.get("ptrns").asInstanceOf[BasicDBList].toList.map(_.asInstanceOf[Pattern]) - for (i <- 0 to 2) { + for (i <- 0 to 2) yield { ptrnList(i).pattern must_== ptrnList2(i).pattern ptrnList(i).flags must_== ptrnList2(i).flags } diff --git a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/CustomSerializersSpec.scala b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/CustomSerializersSpec.scala index f4e80972d0..cf53193974 100644 --- a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/CustomSerializersSpec.scala +++ b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/CustomSerializersSpec.scala @@ -21,7 +21,7 @@ import java.util.{Calendar, Date, TimeZone} import org.bson.types.ObjectId -import org.specs.Specification +import org.specs2.mutable.Specification package customserializersspecs { @@ -51,7 +51,8 @@ package customserializersspecs { /** * Systems under specification for CustomSerializers. */ -object CustomSerializersSpec extends Specification("CustomSerializers Specification") with MongoTestKit { +class CustomSerializersSpec extends Specification with MongoTestKit { + "CustomSerializers Specification".title import customserializersspecs._ @@ -72,8 +73,8 @@ object CustomSerializersSpec extends Specification("CustomSerializers Specificat // retrieve it and compare val jack2 = Person.find(jack._id) - jack2 must notBeEmpty - jack2 foreach { j => + jack2.isDefined must_== true + jack2.toList map { j => j._id mustEqual jack._id j.birthDate mustEqual jack.birthDate } @@ -93,8 +94,8 @@ object CustomSerializersSpec extends Specification("CustomSerializers Specificat // retrieve it and compare val findJack = Person2.find(jack._id) - findJack must notBeEmpty - findJack foreach { j => + findJack.isDefined must_== true + findJack.toList map { j => j._id mustEqual jack._id j.birthDate mustEqual jack.birthDate } diff --git a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/JObjectParserSpec.scala b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/JObjectParserSpec.scala index 5d65b85b8b..da650061b0 100644 --- a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/JObjectParserSpec.scala +++ b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/JObjectParserSpec.scala @@ -22,11 +22,12 @@ import JsonDSL._ import util.Helpers._ import org.bson.types.ObjectId -import org.specs.Specification +import org.specs2.mutable.Specification import com.mongodb.DBObject -object JObjectParserSpec extends Specification("JObjectParser Specification") { +object JObjectParserSpec extends Specification { + "JObjectParser Specification".title def buildTestData: (ObjectId, DBObject) = { val oid = ObjectId.get @@ -39,8 +40,8 @@ object JObjectParserSpec extends Specification("JObjectParser Specification") { val (oid, dbo) = buildTestData val xval = tryo(dbo.get("x").asInstanceOf[ObjectId]) - xval must notBeEmpty - xval.foreach { x => + xval.isDefined must_== true + xval.toList map { x => x must_== oid } } @@ -49,8 +50,8 @@ object JObjectParserSpec extends Specification("JObjectParser Specification") { val (oid, dbo) = buildTestData val xval = tryo(dbo.get("x").asInstanceOf[String]) - xval must notBeEmpty - xval.foreach { x => + xval.isDefined must_== true + xval.toList map { x => x must_== oid.toString } } diff --git a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDirectSpec.scala b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDirectSpec.scala index 6d22260245..9b47534c86 100644 --- a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDirectSpec.scala +++ b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDirectSpec.scala @@ -22,7 +22,7 @@ import java.util.regex.Pattern import com.mongodb.{BasicDBObject, BasicDBObjectBuilder} -import org.specs.Specification +import org.specs2.mutable.Specification import json.DefaultFormats @@ -30,7 +30,8 @@ import json.DefaultFormats /** * System under specification for MongoDirect. */ -object MongoDirectSpec extends Specification("MongoDirect Specification") with MongoTestKit { +class MongoDirectSpec extends Specification with MongoTestKit { + "MongoDirect Specification".title def date(s: String) = DefaultFormats.dateFormat.parse(s).get @@ -193,6 +194,7 @@ object MongoDirectSpec extends Specification("MongoDirect Specification") with M coll.drop } }) + success } "Mongo useSession example" in { @@ -271,6 +273,7 @@ object MongoDirectSpec extends Specification("MongoDirect Specification") with M coll.drop } }) + success } "UUID Example" in { diff --git a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDocumentExamplesSpec.scala b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDocumentExamplesSpec.scala index 1123f572a4..8711c9b8c8 100644 --- a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDocumentExamplesSpec.scala +++ b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDocumentExamplesSpec.scala @@ -25,7 +25,7 @@ import java.util.regex.Pattern import org.bson.types.ObjectId import com.mongodb.{BasicDBList, BasicDBObject, DBObject, MongoException} -import org.specs.Specification +import org.specs2.mutable.Specification import json.DefaultFormats import json.JsonParser._ @@ -176,7 +176,9 @@ package mongotestdocs { /** * Systems under specification for MongoDocumentExamples. */ -class MongoDocumentExamplesSpec extends Specification("MongoDocumentExamples Specification") with MongoTestKit { +class MongoDocumentExamplesSpec extends Specification with MongoTestKit { + "MongoDocumentExamples Specification".title + import mongotestdocs._ override def dbName = "lift_mongodocumentexamples" @@ -231,6 +233,8 @@ class MongoDocumentExamplesSpec extends Specification("MongoDocumentExamples Spe if (!debug) { SimplePerson.drop } + + success } "Multiple Simple Person example" in { @@ -282,6 +286,8 @@ class MongoDocumentExamplesSpec extends Specification("MongoDocumentExamples Spe SimplePerson.drop } + + success } "Person example" in { @@ -317,6 +323,8 @@ class MongoDocumentExamplesSpec extends Specification("MongoDocumentExamples Spe Person.drop } + + success } "Mongo tutorial example" in { @@ -332,7 +340,7 @@ class MongoDocumentExamplesSpec extends Specification("MongoDocumentExamples Spe // unique index on name val ixName = ixs.find(dbo => dbo.get("name") == "name_1") - ixName must notBeEmpty + ixName.isDefined must_== true ixName foreach { ix => ix.containsField("unique") must beTrue ix.get("unique").asInstanceOf[Boolean] must beTrue @@ -340,7 +348,7 @@ class MongoDocumentExamplesSpec extends Specification("MongoDocumentExamples Spe // non-unique index on dbtype val ixDbtype = ixs.find(dbo => dbo.get("name") == "dbtype_1") - ixDbtype must notBeEmpty + ixDbtype.isDefined must_== true ixDbtype foreach { ix => ix.containsField("unique") must beFalse } @@ -465,6 +473,8 @@ class MongoDocumentExamplesSpec extends Specification("MongoDocumentExamples Spe IDoc.findAll.length must_== 50 IDoc.drop + + success } "Mongo useSession example" in { @@ -527,6 +537,8 @@ class MongoDocumentExamplesSpec extends Specification("MongoDocumentExamples Spe } }) + + success } "Primitives example" in { @@ -554,6 +566,8 @@ class MongoDocumentExamplesSpec extends Specification("MongoDocumentExamples Spe pFromDb.isEmpty must_== true Primitive.drop } + + success } "Ref example" in { @@ -609,6 +623,8 @@ class MongoDocumentExamplesSpec extends Specification("MongoDocumentExamples Spe MainJDoc.drop RefJDoc.drop + + success } "Pattern example" in { @@ -618,7 +634,7 @@ class MongoDocumentExamplesSpec extends Specification("MongoDocumentExamples Spe val pdoc1 = PatternDoc(ObjectId.get, Pattern.compile("^Mo", Pattern.CASE_INSENSITIVE)) pdoc1.save - PatternDoc.find(pdoc1._id).map { + PatternDoc.find(pdoc1._id).toList map { pdoc => pdoc._id must_== pdoc1._id pdoc.regx.pattern must_== pdoc1.regx.pattern @@ -643,17 +659,16 @@ class MongoDocumentExamplesSpec extends Specification("MongoDocumentExamples Spe val fromDb = StringDateDoc.find(newId) fromDb.isDefined must_== true - fromDb.foreach { + fromDb.toList flatMap { sdd => sdd._id must_== newId sdd.dt must_== newDt sdd.save - StringDateDoc.find(newId).foreach { + StringDateDoc.find(newId) map { sdd2 => sdd2.dt must_== sdd.dt } } - } } diff --git a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDocumentSpec.scala b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDocumentSpec.scala index ea54098e46..8b4947eeaa 100644 --- a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDocumentSpec.scala +++ b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDocumentSpec.scala @@ -18,7 +18,8 @@ package net.liftweb package mongodb import org.bson.types.ObjectId -import org.specs.Specification +import org.specs2.mutable.Specification +import org.specs2.execute.Result import common._ import json.ext.JsonBoxSerializer @@ -90,9 +91,10 @@ package mongodocumentspecs { /** * System specification for MongoDocument */ -object MongoDocumentSpec extends Specification("MongoDocument Specification") with MongoTestKit { +class MongoDocumentSpec extends Specification with MongoTestKit { + "MongoDocument Specification".title - def passSaveAndRetrieveTests(obj: MongoDocument[_], meta: MongoDocumentMeta[_]): Unit = { + def passSaveAndRetrieveTests(obj: MongoDocument[_], meta: MongoDocumentMeta[_]): Result = { obj.save val objFromDb = meta.find(obj._id.asInstanceOf[ObjectId]) objFromDb.isDefined must_== true diff --git a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoSpec.scala b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoSpec.scala index bef5ab1a7f..936d5ba501 100644 --- a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoSpec.scala +++ b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoSpec.scala @@ -17,17 +17,19 @@ package net.liftweb package mongodb -import org.specs.Specification +import org.specs2.mutable.Specification +import org.specs2.execute.Result import com.mongodb._ -object MongoSpec extends Specification("Mongo Specification") { +class MongoSpec extends Specification { + "Mongo Specification".title case object TestMongoIdentifier extends MongoIdentifier { val jndiName = "test_a" } - def passDefinitionTests(id: MongoIdentifier, ma: MongoAddress): Unit = { + def passDefinitionTests(id: MongoIdentifier, ma: MongoAddress): Result = { // define the db MongoDB.close MongoDB.defineDb(id, ma) @@ -39,7 +41,7 @@ object MongoSpec extends Specification("Mongo Specification") { } } catch { - case e: Exception => skip("MongoDB is not running") + case e: Exception => skipped("MongoDB is not running") } // using an undefined identifier throws an exception @@ -48,6 +50,7 @@ object MongoSpec extends Specification("Mongo Specification") { } must throwA(new MongoException("Mongo not found: MongoIdentifier(test)")) // remove defined db MongoDB.close + success } "Mongo" should { @@ -101,7 +104,7 @@ object MongoSpec extends Specification("Mongo Specification") { } } catch { - case e: Exception => skip("MongoDB is not running") + case e: Exception => skipped("MongoDB is not running") } // using an undefined identifier throws an exception @@ -110,7 +113,9 @@ object MongoSpec extends Specification("Mongo Specification") { } must throwA(new MongoException("Mongo not found: MongoIdentifier(test)")) // remove defined db MongoDB.close + success } + /* Requires a server other than localhost with auth setup. "Define and authenticate DB with Mongo instance" in { MongoDB.close diff --git a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoTestKit.scala b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoTestKit.scala index affc6eb3ff..59eff6a09e 100644 --- a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoTestKit.scala +++ b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoTestKit.scala @@ -17,11 +17,12 @@ package net.liftweb package mongodb -import org.specs.Specification +import org.specs2.mutable.Specification +import org.specs2.specification.BeforeAfterExample -trait MongoTestKit { - this: Specification => +trait MongoTestKit extends Specification with BeforeAfterExample { + sequential def dbName = "lift_"+this.getClass.getName .replace("$", "") @@ -36,7 +37,7 @@ trait MongoTestKit { def debug = false - doBeforeSpec { + def before = { // define the dbs dbs foreach { dbtuple => MongoDB.defineDb(dbtuple._1, MongoAddress(dbtuple._2, dbtuple._3)) @@ -57,9 +58,9 @@ trait MongoTestKit { case e: Exception => false } - def checkMongoIsRunning = isMongoRunning must beEqualTo(true).orSkipExample + def checkMongoIsRunning = isMongoRunning must beEqualTo(true).orSkip - doAfterSpec { + def after = { if (!debug && isMongoRunning) { // drop the databases dbs foreach { dbtuple => diff --git a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/QueryExamplesSpec.scala b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/QueryExamplesSpec.scala index 8b1e04ed39..0ddfb90f9e 100644 --- a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/QueryExamplesSpec.scala +++ b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/QueryExamplesSpec.scala @@ -24,7 +24,7 @@ import java.util.{Calendar, Date, UUID} import java.util.regex.Pattern import org.bson.types.ObjectId -import org.specs.Specification +import org.specs2.mutable.Specification package queryexamplesfixtures { case class Person(_id: ObjectId, name: String, birthDate: Date, childId: UUID, petId: Option[ObjectId]) extends MongoDocument[Person] { @@ -40,7 +40,9 @@ package queryexamplesfixtures { } } -object QueryExamplesSpec extends Specification("QueryExamples Specification") with MongoTestKit { +object QueryExamplesSpec extends Specification with MongoTestKit { + "QueryExamples Specification".title + import queryexamplesfixtures._ "Query examples" in { From ff654188a87aae41583ea6a3a56f58c322272dfe Mon Sep 17 00:00:00 2001 From: Naftoli Gugenheim Date: Wed, 1 Aug 2012 00:43:52 -0400 Subject: [PATCH 0139/1949] lift-record --- .../scala/net/liftweb/record/FieldSpec.scala | 79 ++++++++++--------- .../scala/net/liftweb/record/Fixtures.scala | 3 +- .../scala/net/liftweb/record/RecordSpec.scala | 39 +++++---- 3 files changed, 63 insertions(+), 58 deletions(-) diff --git a/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala b/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala index 828dc7e8cb..7988b69354 100644 --- a/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala +++ b/persistence/record/src/test/scala/net/liftweb/record/FieldSpec.scala @@ -1,5 +1,5 @@ /* - * Copyright 2010-2011 WorldWide Conferencing, LLC + * 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. @@ -27,8 +27,8 @@ import json.JsonAST._ import util.Helpers._ import java.util.Calendar -import org.specs._ -import org.specs.runner.{ConsoleRunner, JUnit3} +import org.specs2.mutable._ +import org.specs2.specification.Fragment import fixtures._ import net.liftweb.util.{Helpers, FieldError} @@ -38,20 +38,20 @@ import scala.xml.{NodeSeq, Elem, Node, Text} /** * Systems under specification for RecordField. */ -object FieldSpec extends Specification("Record Field Specification") { - val session = new LiftSession("", randomString(20), Empty) - def inLiftSession(a: =>Any) = S.initIfUninitted(session) { a } - new SpecContext { - aroundExpectations(inLiftSession(_)) - } - def passBasicTests[A](example: A, mandatory: MandatoryTypedField[A], legacyOptional: MandatoryTypedField[A], optional: OptionalTypedField[A])(implicit m: scala.reflect.Manifest[A]): Unit = { +object FieldSpec extends Specification { + "Record Field Specification".title + sequential + + lazy val session = new LiftSession("", randomString(20), Empty) + + def passBasicTests[A](example: A, mandatory: MandatoryTypedField[A], legacyOptional: MandatoryTypedField[A], optional: OptionalTypedField[A])(implicit m: scala.reflect.Manifest[A]): Fragment = { val canCheckDefaultValues = !mandatory.defaultValue.isInstanceOf[Calendar] // don't try to use the default value of date/time typed fields, because it changes from moment to moment! - + def commonBehaviorsForMandatory(in: MandatoryTypedField[A]): Unit = { if (canCheckDefaultValues) { - "which have the correct initial value" in { + "which have the correct initial value" in S.initIfUninitted(session) { in.get must_== in.defaultValue } } @@ -63,6 +63,8 @@ object FieldSpec extends Specification("Record Field Specification") { in.get must_!= example in.setBox(Box !! example) in.get must_== example + in.clear + success } if (canCheckDefaultValues) { @@ -73,9 +75,8 @@ object FieldSpec extends Specification("Record Field Specification") { } } } - + def commonBehaviorsForAllFlavors(in: TypedField[A]): Unit = { - if (canCheckDefaultValues) { "which have the correct initial boxed value" in { in match { @@ -87,28 +88,31 @@ object FieldSpec extends Specification("Record Field Specification") { } } - "which have readable and writable boxed values" in { + "which have readable and writable boxed values" in S.initIfUninitted(session) { in.setBox(Full(example)) - in.valueBox must verify(_.isDefined) + in.valueBox.isDefined must_== true in.valueBox must_== Full(example) in.clear in.valueBox must_!= Full(example) } if (canCheckDefaultValues) { - "which correctly clear back to the default box value" in { + "which correctly clear back to the default box value" in S.initIfUninitted(session) { in.setBox(Full(example)) - in.valueBox must verify(_.isDefined) + in.valueBox.isDefined must_== true in.clear in.valueBox must_== in.defaultValueBox } } "which capture error conditions set in" in { + val old = in.valueBox in.setBox(Failure("my failure")) in.valueBox must_== Failure("my failure") + in.setBox(old) + success } - + if(canCheckDefaultValues) { "which are only flagged as dirty_? when setBox is called with a different value" in { in.clear @@ -123,9 +127,11 @@ object FieldSpec extends Specification("Record Field Specification") { in.setBox(valueBox) in.dirty_? must_== false val exampleBox = Full(example) - valueBox must verify { v => ! (exampleBox === v) } + (valueBox === exampleBox) must_== false in.setBox(exampleBox) in.dirty_? must_== true + in.setBox(valueBox) + success } } } @@ -137,22 +143,22 @@ object FieldSpec extends Specification("Record Field Specification") { "which are configured correctly" in { mandatory.optional_? must_== false } - + "which initialize to some value" in { - mandatory.valueBox must verify(_.isDefined) + mandatory.valueBox.isDefined must_== true } "which correctly fail to be set to Empty" in { - mandatory.valueBox must verify(_.isDefined) + mandatory.valueBox.isDefined must_== true mandatory.setBox(Empty) - mandatory.valueBox must beLike { case Failure(s, _, _) if s == mandatory.notOptionalErrorMessage => true } + mandatory.valueBox must beLike { case Failure(s, _, _) => s must_== mandatory.notOptionalErrorMessage} } } "support 'legacy' optional fields (override optional_?)" in { commonBehaviorsForAllFlavors(legacyOptional) commonBehaviorsForMandatory(legacyOptional) - + "which are configured correctly" in { legacyOptional.optional_? must_== true } @@ -178,6 +184,7 @@ object FieldSpec extends Specification("Record Field Specification") { legacyOptional.value must_== legacyOptional.defaultValue legacyOptional.valueBox must_== legacyOptional.defaultValueBox } + success } } @@ -209,9 +216,9 @@ object FieldSpec extends Specification("Record Field Specification") { } } - def passConversionTests[A](example: A, mandatory: MandatoryTypedField[A], jsexp: JsExp, jvalue: JValue, formPattern: Box[NodeSeq]): Unit = { + def passConversionTests[A](example: A, mandatory: MandatoryTypedField[A], jsexp: JsExp, jvalue: JValue, formPattern: Box[NodeSeq]): Fragment = { - "convert to JsExp" in { + "convert to JsExp" in S.initIfUninitted(session) { mandatory.set(example) mandatory.asJs mustEqual jsexp } @@ -226,7 +233,6 @@ object FieldSpec extends Specification("Record Field Specification") { "get set from JValue" in { mandatory.setFromJValue(jvalue) mustEqual Full(example) mandatory.value mustEqual example - () // does not compile without this: no implicit argument matching parameter type scala.reflect.Manifest[org.specs.specification.Result[mandatory.MyType]] } } @@ -236,8 +242,8 @@ object FieldSpec extends Specification("Record Field Specification") { val session = new LiftSession("", randomString(20), Empty) S.initIfUninitted(session) { val formXml = mandatory.toForm - formXml must notBeEmpty - formXml foreach { fprime => + formXml.isDefined must_== true + formXml.toList map { fprime => val f = ("* [name]" #> ".*" & "select *" #> (((ns: NodeSeq) => ns.filter { case e: Elem => e.attribute("selected").map(_.text) == Some("selected") case _ => false @@ -248,6 +254,7 @@ object FieldSpec extends Specification("Record Field Specification") { } } } + success } /* Since Array[Byte]s cannot be compared, commenting out this test for now @@ -407,7 +414,7 @@ object FieldSpec extends Specification("Record Field Specification") { } "PasswordField" should { - "require a nonempty password" in { + "require a nonempty password" in S.initIfUninitted(session) { val rec = PasswordTestRecord.createRecord.password("") rec.validate must_== ( @@ -416,10 +423,10 @@ object FieldSpec extends Specification("Record Field Specification") { ) } - "correctly validate the unencrypted value" in { + "correctly validate the unencrypted value" in S.initIfUninitted(session) { val rec = PasswordTestRecord.createRecord.password("testvalue") rec.validate must_== Nil - + rec.password("1234") rec.validate must_== ( FieldError(rec.password, Text(S.?("password.too.short"))) :: @@ -430,10 +437,10 @@ object FieldSpec extends Specification("Record Field Specification") { "match with encrypted value" in { val rec = PasswordTestRecord.createRecord.password("testpassword") rec.password.match_?("testpassword") must_== true - + rec.password.set("$2a$10$6CJWdXpKoP8bVTjGH8SbKOWevNQVL8MkYVlBLmqtywVi7dp/YgPXC") rec.password.match_?("dummyPassw0rd") must_== true - } + } } "PostalCodeField" should { @@ -476,7 +483,7 @@ object FieldSpec extends Specification("Record Field Specification") { "honor harnessed validators" in { val rec = ValidationTestRecord.createRecord val field = rec.stringFieldWithValidation - + "which always succeed" in { field.validationHarness = _ => Nil rec.validate must_== Nil diff --git a/persistence/record/src/test/scala/net/liftweb/record/Fixtures.scala b/persistence/record/src/test/scala/net/liftweb/record/Fixtures.scala index aee83476d2..54fea1faf5 100644 --- a/persistence/record/src/test/scala/net/liftweb/record/Fixtures.scala +++ b/persistence/record/src/test/scala/net/liftweb/record/Fixtures.scala @@ -22,8 +22,7 @@ import java.math.MathContext import scala.xml.Text import common.{Box, Empty, Full} import util.{FieldError, Helpers} -import org.specs._ -import org.specs.runner.{ConsoleRunner, JUnit3} +import org.specs2.mutable._ import field._ diff --git a/persistence/record/src/test/scala/net/liftweb/record/RecordSpec.scala b/persistence/record/src/test/scala/net/liftweb/record/RecordSpec.scala index 622889db0a..470f162ad3 100644 --- a/persistence/record/src/test/scala/net/liftweb/record/RecordSpec.scala +++ b/persistence/record/src/test/scala/net/liftweb/record/RecordSpec.scala @@ -19,7 +19,9 @@ package record import java.util.Calendar -import org.specs.Specification +import org.specs2.mutable.Specification +import org.specs2.specification.Fragment + import util.Helpers._ import json.JsonAST.JField import json.JsonAST.JInt @@ -29,8 +31,6 @@ import json.JsonAST.JObject import json.JsonAST.JDouble import json.JsonAST.JBool -//import org.specs.Specification. - import json.JsonAST._ import util._ import http.js.JE._ @@ -44,12 +44,9 @@ import common.Empty /** * Systems under specification for Record. */ -object RecordSpec extends Specification("Record Specification") { - val session = new LiftSession("", randomString(20), Empty) - def inLiftSession(a: =>Any) = S.initIfUninitted(session) { a } - new SpecContext { - aroundExpectations(inLiftSession(_)) - } +object RecordSpec extends Specification { + "Record Specification".title + "Record field introspection" should { val rec = FieldTypeTestRecord.createRecord val allExpectedFieldNames: List[String] = (for { @@ -62,14 +59,14 @@ object RecordSpec extends Specification("Record Specification") { } "correctly look up fields by name" in { - for (name <- allExpectedFieldNames) { - rec.fieldByName(name) must verify(_.isDefined) + for (name <- allExpectedFieldNames) yield { + rec.fieldByName(name).isDefined must_== true } } "not look up fields by bogus names" in { - for (name <- allExpectedFieldNames) { - rec.fieldByName("x" + name + "y") must not(verify(_.isDefined)) + for (name <- allExpectedFieldNames) yield { + rec.fieldByName("x" + name + "y").isDefined must_== false } } @@ -80,7 +77,7 @@ object RecordSpec extends Specification("Record Specification") { } "Record lifecycle callbacks" should { - def testOneHarness(scope: String, f: LifecycleTestRecord => HarnessedLifecycleCallbacks): Unit = { + def testOneHarness(scope: String, f: LifecycleTestRecord => HarnessedLifecycleCallbacks): Fragment = { ("be called before validation when specified at " + scope) in { val rec = LifecycleTestRecord.createRecord var triggered = false @@ -251,7 +248,9 @@ object RecordSpec extends Specification("Record Specification") { ) "convert to JsExp (via asJSON)" in { - fttr.asJSON mustEqual fttrAsJsObj + S.initIfUninitted(new LiftSession("", randomString(20), Empty)) { + fttr.asJSON mustEqual fttrAsJsObj + } } /* Test broken @@ -311,8 +310,8 @@ object RecordSpec extends Specification("Record Specification") { "get set from json string using lift-json parser" in { val fttrFromJson = FieldTypeTestRecord.fromJsonString(json) - fttrFromJson must notBeEmpty - fttrFromJson foreach { r => + fttrFromJson.isDefined must_== true + fttrFromJson.toList map { r => r.mandatoryDecimalField.value mustEqual fttr.mandatoryDecimalField.value r mustEqual fttr } @@ -320,14 +319,14 @@ object RecordSpec extends Specification("Record Specification") { "get set from json string using util.JSONParser" in { val fttrFromJSON = FieldTypeTestRecord.fromJSON(json) - fttrFromJSON must notBeEmpty - fttrFromJSON foreach { r => + fttrFromJSON.isDefined must_== true + fttrFromJSON.toList map { r => r mustEqual fttr } } } } - + "basic record" should { "order fields according to fieldOrder" in { BasicTestRecord.metaFields must_== List(BasicTestRecord.field2, BasicTestRecord.field1, BasicTestRecord.field3) From d9dacced699a073f91846c345f6e33c196af1c38 Mon Sep 17 00:00:00 2001 From: Naftoli Gugenheim Date: Wed, 1 Aug 2012 01:49:24 -0400 Subject: [PATCH 0140/1949] lift-mongodb-record --- .../record/CustomSerializersSpec.scala | 21 +-- .../mongodb/record/MongoFieldSpec.scala | 46 ++++--- .../record/MongoRecordExamplesSpec.scala | 18 ++- .../mongodb/record/MongoRecordSpec.scala | 125 +++++++----------- .../liftweb/mongodb/record/MongoTestKit.scala | 21 +-- .../mongodb/record/QueryExamplesSpec.scala | 6 +- .../mongodb/record/field/EnumFieldSpec.scala | 21 +-- .../record/field/EnumNameFieldSpec.scala | 23 ++-- 8 files changed, 139 insertions(+), 142 deletions(-) diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/CustomSerializersSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/CustomSerializersSpec.scala index e9987d9a94..f8c7bc119b 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/CustomSerializersSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/CustomSerializersSpec.scala @@ -28,7 +28,7 @@ import util.Helpers._ import java.util.{Calendar, Date} import org.bson.types.ObjectId -import org.specs.Specification +import org.specs2.mutable.Specification import net.liftweb.record.field._ import xml.{Elem, NodeSeq} @@ -152,7 +152,8 @@ object EnumRec extends EnumRec with MongoMetaRecord[EnumRec] { /** * Systems under specification for CustomSerializers. */ -object CustomSerializersSpec extends Specification("CustomSerializers Specification") with MongoTestKit { +object CustomSerializersSpec extends Specification with MongoTestKit { + "CustomSerializers Specification".title import customserializersspecs._ @@ -176,7 +177,7 @@ object CustomSerializersSpec extends Specification("CustomSerializers Specificat // retrieve it and compare val mother2 = Person.find(mother.id) - mother2 must notBeEmpty + mother2.isDefined must_== true mother2 foreach { m => m.children.value mustEqual mother.children.value @@ -231,7 +232,7 @@ object CustomSerializersSpec extends Specification("CustomSerializers Specificat // retrieve it and compare val mother2 = Person2.find(mother.id) - mother2 must notBeEmpty + mother2.isDefined must_== true mother2 foreach { m => m.children.value mustEqual mother.children.value @@ -288,7 +289,7 @@ object CustomSerializersSpec extends Specification("CustomSerializers Specificat // retrieve it and compare val nfl2 = League.find(nfl.id) - nfl2 must notBeEmpty + nfl2.isDefined must_== true nfl2 foreach { l => l.teams.value mustEqual nfl.teams.value @@ -297,7 +298,7 @@ object CustomSerializersSpec extends Specification("CustomSerializers Specificat // find a player val vqb = Player.find(vikes.qb) - vqb must notBeEmpty + vqb.isDefined must_== true vqb foreach { p => p.name.value mustEqual "Brett Favre" @@ -310,7 +311,7 @@ object CustomSerializersSpec extends Specification("CustomSerializers Specificat val formPattern = S.initIfUninitted(session) { val form = nfl._id.toForm - form must notBeEmpty + form.isDefined must_== true form foreach { fprime => val f = ("* [name]" #> ".*" & "select *" #> (((ns: NodeSeq) => ns.filter { @@ -359,7 +360,7 @@ object CustomSerializersSpec extends Specification("CustomSerializers Specificat // retrieve it and compare val nfl2 = League2.find(nfl.id.toString) - nfl2 must notBeEmpty + nfl2.isDefined must_== true nfl2 foreach { l => l.teams.value mustEqual nfl.teams.value @@ -368,7 +369,7 @@ object CustomSerializersSpec extends Specification("CustomSerializers Specificat // find a player val vqb = Player.find(vikes.qb) - vqb must notBeEmpty + vqb.isDefined must_== true vqb foreach { p => p.name.value mustEqual "Brett Favre" @@ -381,7 +382,7 @@ object CustomSerializersSpec extends Specification("CustomSerializers Specificat val formPattern = S.initIfUninitted(session) { val form = nfl._id.toForm - form must notBeEmpty + form.isDefined must_== true form foreach { fprime => val f = ("* [name]" #> ".*" & "select *" #> (((ns: NodeSeq) => ns.filter { diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala index 4438b58f11..72913b67e7 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala @@ -22,7 +22,9 @@ import java.util.{Date, UUID} import java.util.regex.Pattern import org.bson.types.ObjectId -import org.specs.Specification +import org.specs2.mutable.Specification +import org.specs2.specification.Fragment +import org.specs2.specification.AroundExample import common._ import json._ @@ -40,14 +42,16 @@ import Helpers._ /** * Systems under specification for MongoField. */ -object MongoFieldSpec extends Specification("MongoField Specification") with MongoTestKit { +object MongoFieldSpec extends Specification with MongoTestKit with AroundExample { + "MongoField Specification".title + sequential + import fixtures._ - val session = new LiftSession("", randomString(20), Empty) - def inLiftSession(a: =>Any) = S.initIfUninitted(session) { a } - new SpecContext { - aroundExpectations(inLiftSession(_)) - } + lazy val session = new LiftSession("", randomString(20), Empty) + + protected def around[T <% org.specs2.execute.Result](t: =>T) = S.initIfUninitted(session) { t } + def passBasicTests[A]( example: A, mandatory: MandatoryTypedField[A], @@ -58,8 +62,8 @@ object MongoFieldSpec extends Specification("MongoField Specification") with Mon def commonBehaviorsForAllFlavors(field: MandatoryTypedField[A]) = { "which have the correct initial value" in { - field.value must beEqual(field.defaultValue).when(canCheckDefaultValues) - field.valueBox must beEqual(field.defaultValueBox).when(canCheckDefaultValues) + field.value must be_==(field.defaultValue).when(canCheckDefaultValues) + field.valueBox must be_==(field.defaultValueBox).when(canCheckDefaultValues) } "which are readable and writable" in { @@ -75,25 +79,24 @@ object MongoFieldSpec extends Specification("MongoField Specification") with Mon } "which correctly clear back to the default" in { - { field.clear; field.valueBox } must beEqual(field.defaultValueBox).when(canCheckDefaultValues) + { field.clear; field.valueBox } must be_==(field.defaultValueBox).when(canCheckDefaultValues) } "which capture error conditions set in" in { // FIXME: This needs to be rearranged just so that it doesn't foul with subsequent examples // field.setBox(Failure("my failure")) // Failure("my failure") must_== Failure("my failure") + pending } } "support mandatory fields" in { - setSequential() - "which are configured correctly" in { mandatory.optional_? must_== false } "which initialize to some value" in { - mandatory.valueBox must verify(_.isDefined) + mandatory.valueBox.isDefined must_== true } "common behaviors for all flavors" in { @@ -101,15 +104,13 @@ object MongoFieldSpec extends Specification("MongoField Specification") with Mon } "which correctly fail to be set to Empty" in { - mandatory.valueBox must verify(_.isDefined) + mandatory.valueBox.isDefined must_== true mandatory.setBox(Empty) - mandatory.valueBox must beLike { case Failure(s, _, _) if s == mandatory.notOptionalErrorMessage => true } + mandatory.valueBox must beLike { case Failure(s, _, _) => s must_== mandatory.notOptionalErrorMessage } } } "support 'legacy' optional fields (override optional_?)" in { - setSequential() - "which are configured correctly" in { legacyOptional.optional_? must_== true } @@ -139,11 +140,12 @@ object MongoFieldSpec extends Specification("MongoField Specification") with Mon legacyOptional.value must_== legacyOptional.defaultValue legacyOptional.valueBox must_== legacyOptional.defaultValueBox } + success } } } - def passConversionTests[A](example: A, mandatory: MandatoryTypedField[A], jsexp: JsExp, jvalue: JValue, formPattern: Box[NodeSeq]): Unit = { + def passConversionTests[A](example: A, mandatory: MandatoryTypedField[A], jsexp: JsExp, jvalue: JValue, formPattern: Box[NodeSeq]): Fragment = { /* "convert to JsExp" in { @@ -160,17 +162,16 @@ object MongoFieldSpec extends Specification("MongoField Specification") with Mon "get set from JValue" in { mandatory.setFromJValue(jvalue) mustEqual Full(example) mandatory.value mustEqual example - () // does not compile without this: no implicit argument matching parameter type scala.reflect.Manifest[org.specs.specification.Result[mandatory.MyType]] } } - formPattern foreach { fp => - "convert to form XML" in { + "convert to form XML" in { + formPattern foreach { fp => mandatory.set(example) val session = new LiftSession("", randomString(20), Empty) S.initIfUninitted(session) { val formXml = mandatory.toForm - formXml must notBeEmpty + formXml.isDefined must_== true formXml foreach { fprime => val f = ("* [name]" #> ".*" & "select *" #> (((ns: NodeSeq) => ns.filter { case e: Elem => e.attribute("selected").map(_.text) == Some("selected") @@ -182,6 +183,7 @@ object MongoFieldSpec extends Specification("MongoField Specification") with Mon } } } + success } } diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordExamplesSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordExamplesSpec.scala index e13ee3cbee..e993d8746c 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordExamplesSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordExamplesSpec.scala @@ -28,7 +28,7 @@ import net.liftweb.json.JsonAST.JObject import net.liftweb.record.field._ import net.liftweb.util.TimeHelpers._ -import org.specs.Specification +import org.specs2.mutable.Specification import com.mongodb._ import org.bson.types.ObjectId @@ -237,7 +237,9 @@ package mongotestrecords { /** * Systems under specification for MongoRecordExamples. */ -object MongoRecordExamplesSpec extends Specification("MongoRecordExamples Specification") with MongoTestKit { +class MongoRecordExamplesSpec extends Specification with MongoTestKit { + "MongoRecordExamples Specification".title + import mongotestrecords._ import net.liftweb.util.TimeHelpers._ @@ -305,6 +307,8 @@ object MongoRecordExamplesSpec extends Specification("MongoRecordExamples Specif if (!debug) TstRecord.drop } + + success } "Ref example" in { @@ -457,6 +461,8 @@ object MongoRecordExamplesSpec extends Specification("MongoRecordExamples Specif RefDoc.drop RefStringDoc.drop } + + success } "List example" in { @@ -513,6 +519,8 @@ object MongoRecordExamplesSpec extends Specification("MongoRecordExamples Specif ListDoc.drop RefDoc.drop } + + success } "Map Example" in { @@ -527,6 +535,8 @@ object MongoRecordExamplesSpec extends Specification("MongoRecordExamples Specif md1.delete_! if (!debug) MapDoc.drop + + success } "Optional Example" in { @@ -554,6 +564,8 @@ object MongoRecordExamplesSpec extends Specification("MongoRecordExamples Specif } if (!debug) OptionalDoc.drop + + success } "Strict Example" in { @@ -572,5 +584,7 @@ object MongoRecordExamplesSpec extends Specification("MongoRecordExamples Specif sd2.save(true) must_== sd2 if (!debug) StrictDoc.drop + + success } } diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala index bc7952c28f..096f9ecbd3 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala @@ -22,7 +22,8 @@ import java.util.{Date, Locale, UUID} import java.util.regex.Pattern import org.bson.types.ObjectId -import org.specs.Specification +import org.specs2.mutable.Specification +import org.specs2.specification.Fragment import common._ import http.js.JsExp @@ -37,13 +38,18 @@ import http.{S, LiftSession} /** * Systems under specification for MongoRecord. */ -object MongoRecordSpec extends Specification("MongoRecord Specification") with MongoTestKit { +class MongoRecordSpec extends Specification with MongoTestKit { + "MongoRecord Specification".title + import fixtures._ val session = new LiftSession("hello", "", Empty) - "MongoRecord field introspection" should { + override def before = { + super.before checkMongoIsRunning + } + "MongoRecord field introspection" should { val rec = MongoFieldTypeTestRecord.createRecord val allExpectedFieldNames: List[String] = "_id" :: "mandatoryMongoCaseClassField" :: (for { @@ -56,22 +62,20 @@ object MongoRecordSpec extends Specification("MongoRecord Specification") with M } "correctly look up fields by name" in { - for (name <- allExpectedFieldNames) { - rec.fieldByName(name) must verify(_.isDefined) + for (name <- allExpectedFieldNames) yield { + rec.fieldByName(name).isDefined must_== true } } "not look up fields by bogus names" in { - for (name <- allExpectedFieldNames) { - rec.fieldByName("x" + name + "y") must not(verify(_.isDefined)) + for (name <- allExpectedFieldNames) yield { + rec.fieldByName("x" + name + "y").isDefined must_== false } } } "MongoRecord lifecycle callbacks" should { - checkMongoIsRunning - - def testOneHarness(scope: String, f: LifecycleTestRecord => HarnessedLifecycleCallbacks): Unit = { + def testOneHarness(scope: String, f: LifecycleTestRecord => HarnessedLifecycleCallbacks): Fragment = { ("be called before validation when specified at " + scope) in { val rec = LifecycleTestRecord.createRecord var triggered = false @@ -168,8 +172,6 @@ object MongoRecordSpec extends Specification("MongoRecord Specification") with M } "MongoRecord" should { - checkMongoIsRunning - val binData: Array[Byte] = Array(18, 19, 20) val fttr = FieldTypeTestRecord.createRecord @@ -334,7 +336,7 @@ object MongoRecordSpec extends Specification("MongoRecord Specification") with M fttr.save val fttrFromDb = FieldTypeTestRecord.find(fttr.id.value) - fttrFromDb must notBeEmpty + fttrFromDb.isDefined must_== true fttrFromDb foreach { tr => tr mustEqual fttr } @@ -342,8 +344,8 @@ object MongoRecordSpec extends Specification("MongoRecord Specification") with M bftr.save val bftrFromDb = BinaryFieldTestRecord.find(bftr.id.value) - bftrFromDb must notBeEmpty - bftrFromDb foreach { tr => + bftrFromDb.isDefined must_== true + bftrFromDb.toList map { tr => tr mustEqual bftr } } @@ -354,19 +356,17 @@ object MongoRecordSpec extends Specification("MongoRecord Specification") with M S.initIfUninitted(session) { fttr.save - FieldTypeTestRecord.find(fttr.id.value) must notBeEmpty + FieldTypeTestRecord.find(fttr.id.value).isDefined must_== true fttr.delete_! FieldTypeTestRecord.find(fttr.id.value) must beEmpty } } "save and retrieve Mongo type fields with set values" in { - checkMongoIsRunning - mfttr.save val mfttrFromDb = MongoFieldTypeTestRecord.find(mfttr.id.value) - mfttrFromDb must notBeEmpty + mfttrFromDb.isDefined must_== true mfttrFromDb foreach { tr => tr mustEqual mfttr } @@ -374,7 +374,7 @@ object MongoRecordSpec extends Specification("MongoRecord Specification") with M ltr.save val ltrFromDb = ListTestRecord.find(ltr.id.value) - ltrFromDb must notBeEmpty + ltrFromDb.isDefined must_== true ltrFromDb foreach { tr => tr mustEqual ltr } @@ -382,7 +382,7 @@ object MongoRecordSpec extends Specification("MongoRecord Specification") with M mtr.save val mtrFromDb = MapTestRecord.find(mtr.id.value) - mtrFromDb must notBeEmpty + mtrFromDb.isDefined must_== true mtrFromDb foreach { tr => tr mustEqual mtr } @@ -390,20 +390,18 @@ object MongoRecordSpec extends Specification("MongoRecord Specification") with M srtr.save val srtrFromDb = SubRecordTestRecord.find(srtr.id.value) - srtrFromDb must notBeEmpty - srtrFromDb foreach { tr => + srtrFromDb.isDefined must_== true + srtrFromDb.toList map { tr => tr mustEqual srtr } } "save and retrieve Mongo type fields with default values" in { - checkMongoIsRunning - val mfttrDef = MongoFieldTypeTestRecord.createRecord mfttrDef.save val mfttrFromDb = MongoFieldTypeTestRecord.find(mfttrDef.id.value) - mfttrFromDb must notBeEmpty + mfttrFromDb.isDefined must_== true mfttrFromDb foreach { tr => tr mustEqual mfttrDef } @@ -412,7 +410,7 @@ object MongoRecordSpec extends Specification("MongoRecord Specification") with M ltrDef.save val ltrFromDb = ListTestRecord.find(ltrDef.id.value) - ltrFromDb must notBeEmpty + ltrFromDb.isDefined must_== true ltrFromDb foreach { tr => tr mustEqual ltrDef } @@ -421,7 +419,7 @@ object MongoRecordSpec extends Specification("MongoRecord Specification") with M mtrDef.save val mtrFromDb = MapTestRecord.find(mtrDef.id.value) - mtrFromDb must notBeEmpty + mtrFromDb.isDefined must_== true mtrFromDb foreach { tr => tr mustEqual mtrDef } @@ -430,15 +428,13 @@ object MongoRecordSpec extends Specification("MongoRecord Specification") with M srtrDef.save val srtrFromDb = SubRecordTestRecord.find(srtrDef.id.value) - srtrFromDb must notBeEmpty - srtrFromDb foreach { tr => + srtrFromDb.isDefined must_== true + srtrFromDb.toList map { tr => tr mustEqual srtrDef } } "convert Mongo type fields to JValue" in { - checkMongoIsRunning - mfttr.asJValue mustEqual mfttrJson ltr.asJValue mustEqual ltrJson @@ -454,30 +450,26 @@ object MongoRecordSpec extends Specification("MongoRecord Specification") with M } "get set from json string using lift-json parser" in { - checkMongoIsRunning - val mfftrFromJson = MongoFieldTypeTestRecord.fromJsonString(compact(render(mfttrJson))) - mfftrFromJson must notBeEmpty + mfftrFromJson.isDefined must_== true mfftrFromJson foreach { tr => tr mustEqual mfttr } val ltrFromJson = ListTestRecord.fromJsonString(compact(render(ltrJson))) - ltrFromJson must notBeEmpty + ltrFromJson.isDefined must_== true ltrFromJson foreach { tr => tr mustEqual ltr } val mtrFromJson = MapTestRecord.fromJsonString(compact(render(mtrJson))) - mtrFromJson must notBeEmpty - mtrFromJson foreach { tr => + mtrFromJson.isDefined must_== true + mtrFromJson.toList map { tr => tr mustEqual mtr } } "handle null" in { - checkMongoIsRunning - val ntr = NullTestRecord.createRecord ntr.nullstring.set(null) ntr.jsonobjlist.set(List(JsonObj("1", null), JsonObj("2", "jsonobj2"))) @@ -486,9 +478,9 @@ object MongoRecordSpec extends Specification("MongoRecord Specification") with M val ntrFromDb = NullTestRecord.find(ntr.id.value) - ntrFromDb must notBeEmpty + ntrFromDb.isDefined must_== true - ntrFromDb foreach { n => + ntrFromDb.toList map { n => // goes in as ntr.nullstring.valueBox.map(_ must beNull) ntr.nullstring.value must beNull @@ -507,8 +499,6 @@ object MongoRecordSpec extends Specification("MongoRecord Specification") with M } "handle Box using JsonBoxSerializer" in { - checkMongoIsRunning - val btr = BoxTestRecord.createRecord btr.jsonobjlist.set( BoxTestJsonObj("1", Empty, Full("Full String1"), Failure("Failure1")) :: @@ -520,9 +510,9 @@ object MongoRecordSpec extends Specification("MongoRecord Specification") with M val btrFromDb = BoxTestRecord.find(btr.id.value) - btrFromDb must notBeEmpty + btrFromDb.isDefined must_== true - btrFromDb foreach { b => + btrFromDb.toList map { b => b.jsonobjlist.value.size must_== 2 btr.jsonobjlist.value.size must_== 2 val sortedList = b.jsonobjlist.value.sortWith(_.id < _.id) @@ -533,8 +523,6 @@ object MongoRecordSpec extends Specification("MongoRecord Specification") with M } "retrieve MongoRef objects properly" in { - checkMongoIsRunning - S.initIfUninitted(session) { val ntr = NullTestRecord.createRecord val btr = BoxTestRecord.createRecord @@ -593,7 +581,6 @@ object MongoRecordSpec extends Specification("MongoRecord Specification") with M } "use defaultValue when field is not present in the database" in { - checkMongoIsRunning S.initIfUninitted(session) { val missingFieldDocId = ObjectId.get @@ -605,9 +592,9 @@ object MongoRecordSpec extends Specification("MongoRecord Specification") with M val recFromDb = FieldTypeTestRecord.find(missingFieldDocId) - recFromDb must notBeEmpty + recFromDb.isDefined must_== true - recFromDb foreach { r => + recFromDb.toList map { r => r.mandatoryBooleanField.is must_== false r.legacyOptionalBooleanField r.optionalBooleanField.is must beEmpty @@ -652,8 +639,6 @@ object MongoRecordSpec extends Specification("MongoRecord Specification") with M } "reset dirty flags on save" in { - checkMongoIsRunning - val fttr = FieldTypeTestRecord.createRecord.save fttr.mandatoryDecimalField(BigDecimal("3.14")) fttr.dirtyFields.length must_== 1 @@ -662,8 +647,6 @@ object MongoRecordSpec extends Specification("MongoRecord Specification") with M } "update dirty fields for a FieldTypeTestRecord" in { - checkMongoIsRunning - S.initIfUninitted(session) { val fttr = FieldTypeTestRecord.createRecord .legacyOptionalStringField("legacy optional string") @@ -702,7 +685,7 @@ object MongoRecordSpec extends Specification("MongoRecord Specification") with M fttr.dirtyFields.length must_== 0 val fromDb = FieldTypeTestRecord.find(fttr.id.is) - fromDb must notBeEmpty + fromDb.isDefined must_== true fromDb foreach { rec => rec must_== fttr rec.dirtyFields.length must_== 0 @@ -722,8 +705,8 @@ object MongoRecordSpec extends Specification("MongoRecord Specification") with M fttr2.dirtyFields.length must_== 0 val fromDb2 = FieldTypeTestRecord.find(fttr2.id.is) - fromDb2 must notBeEmpty - fromDb2 foreach { rec => + fromDb2.isDefined must_== true + fromDb2.toList map { rec => rec must_== fttr2 rec.dirtyFields.length must_== 0 } @@ -731,8 +714,6 @@ object MongoRecordSpec extends Specification("MongoRecord Specification") with M } "update dirty fields for a MongoFieldTypeTestRecord" in { - checkMongoIsRunning - val mfttr = MongoFieldTypeTestRecord.createRecord .legacyOptionalDateField(new Date) .legacyOptionalObjectIdField(ObjectId.get) @@ -766,7 +747,7 @@ object MongoRecordSpec extends Specification("MongoRecord Specification") with M mfttr.dirtyFields.length must_== 0 val fromDb = MongoFieldTypeTestRecord.find(mfttr.id.is) - fromDb must notBeEmpty + fromDb.isDefined must_== true fromDb foreach { rec => rec must_== mfttr rec.dirtyFields.length must_== 0 @@ -785,16 +766,14 @@ object MongoRecordSpec extends Specification("MongoRecord Specification") with M mfttr2.dirtyFields.length must_== 0 val fromDb2 = MongoFieldTypeTestRecord.find(mfttr2.id.is) - fromDb2 must notBeEmpty - fromDb2 foreach { rec => + fromDb2.isDefined must_== true + fromDb2.toList map { rec => rec must_== mfttr2 rec.dirtyFields.length must_== 0 } } "update dirty fields for a ListTestRecord" in { - checkMongoIsRunning - val ltr = ListTestRecord.createRecord.save ltr.mandatoryStringListField(List("abc", "def", "ghi")) @@ -814,16 +793,14 @@ object MongoRecordSpec extends Specification("MongoRecord Specification") with M ltr.dirtyFields.length must_== 0 val fromDb = ListTestRecord.find(ltr.id.is) - fromDb must notBeEmpty - fromDb foreach { rec => + fromDb.isDefined must_== true + fromDb.toList map { rec => rec must_== ltr rec.dirtyFields.length must_== 0 } } "update dirty fields for a MapTestRecord" in { - checkMongoIsRunning - val mtr = MapTestRecord.save mtr.mandatoryStringMapField(Map("a" -> "abc", "b" -> "def", "c" -> "ghi")) @@ -837,16 +814,14 @@ object MongoRecordSpec extends Specification("MongoRecord Specification") with M mtr.dirtyFields.length must_== 0 val fromDb = MapTestRecord.find(mtr.id.is) - fromDb must notBeEmpty - fromDb foreach { rec => + fromDb.isDefined must_== true + fromDb.toList map { rec => rec must_== mtr rec.dirtyFields.length must_== 0 } } "update dirty fields for a SubRecordTestRecord" in { - checkMongoIsRunning - val srtr = SubRecordTestRecord.createRecord.save val ssr1 = SubSubRecord.createRecord.name("SubSubRecord1") @@ -873,8 +848,8 @@ object MongoRecordSpec extends Specification("MongoRecord Specification") with M srtr.dirtyFields.length must_== 0 val fromDb = SubRecordTestRecord.find(srtr.id.is) - fromDb must notBeEmpty - fromDb foreach { rec => + fromDb.isDefined must_== true + fromDb.toList map { rec => rec must_== srtr rec.dirtyFields.length must_== 0 } diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoTestKit.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoTestKit.scala index 53a502d816..81593f0e75 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoTestKit.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoTestKit.scala @@ -14,16 +14,17 @@ * limitations under the License. */ -package net.liftweb -package mongodb -package record +package net.liftweb +package mongodb +package record -import org.specs.Specification +import org.specs2.mutable.Specification +import org.specs2.specification.BeforeAfterExample import com.mongodb._ -trait MongoTestKit { - this: Specification => +trait MongoTestKit extends Specification with BeforeAfterExample { + sequential def dbName = "lift_record_"+this.getClass.getName .replace("$", "") @@ -34,12 +35,12 @@ trait MongoTestKit { val defaultHost = MongoHost("127.0.0.1", 27017) // If you need more than one db, override this - def dbs: List[(MongoIdentifier, MongoHost, String)] = + def dbs: List[(MongoIdentifier, MongoHost, String)] = List((DefaultMongoIdentifier, defaultHost, dbName)) def debug = false - doBeforeSpec { + def before = { // define the dbs dbs foreach { dbtuple => MongoDB.defineDb(dbtuple._1, MongoAddress(dbtuple._2, dbtuple._3)) @@ -61,9 +62,9 @@ trait MongoTestKit { } def checkMongoIsRunning = - isMongoRunning must beEqualTo(true).orSkipExample + isMongoRunning must beEqualTo(true).orSkip - doAfterSpec { + def after = { if (!debug && isMongoRunning) { // drop the databases dbs foreach { dbtuple => diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/QueryExamplesSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/QueryExamplesSpec.scala index 95f535cc2e..7e0d0ea29d 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/QueryExamplesSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/QueryExamplesSpec.scala @@ -27,7 +27,7 @@ import java.util.{Calendar, Date, UUID} import java.util.regex.Pattern import org.bson.types.ObjectId -import org.specs.Specification +import org.specs2.mutable.Specification package queryexamplesfixtures { class Person private () extends MongoRecord[Person] with ObjectIdPk[Person] { @@ -49,7 +49,9 @@ package queryexamplesfixtures { } } -object QueryExamplesSpec extends Specification("QueryExamples Specification") with MongoTestKit { +object QueryExamplesSpec extends Specification with MongoTestKit { + "QueryExamples Specification".title + import queryexamplesfixtures._ "Query examples" in { diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/field/EnumFieldSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/field/EnumFieldSpec.scala index dee5068e75..d5a13fd195 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/field/EnumFieldSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/field/EnumFieldSpec.scala @@ -20,7 +20,7 @@ package record package field import org.bson.types.ObjectId -import org.specs.Specification +import org.specs2.mutable.Specification import net.liftweb.common._ import net.liftweb.json.ext.EnumSerializer @@ -68,7 +68,8 @@ package enumfieldspecs { /** * Systems under specification for EnumField. */ -object EnumFieldSpec extends Specification("EnumField Specification") with MongoTestKit { +class EnumFieldSpec extends Specification with MongoTestKit { + "EnumField Specification".title import enumfieldspecs._ @@ -80,8 +81,8 @@ object EnumFieldSpec extends Specification("EnumField Specification") with Mongo val er = EnumRec.createRecord.save val erFromDb = EnumRec.find(er.id) - erFromDb must notBeEmpty - erFromDb foreach { er2 => + erFromDb.isDefined must_== true + erFromDb.toList map { er2 => er2 mustEqual er er2.dow.value mustEqual WeekDay.Mon er2.dowOptional.valueBox mustEqual Empty @@ -98,8 +99,8 @@ object EnumFieldSpec extends Specification("EnumField Specification") with Mongo .save val erFromDb = EnumRec.find(er.id) - erFromDb must notBeEmpty - erFromDb foreach { er2 => + erFromDb.isDefined must_== true + erFromDb.toList map { er2 => er2 mustEqual er er2.dow.value mustEqual WeekDay.Tue er2.jsonobj.value mustEqual JsonObj(WeekDay.Sun) @@ -114,8 +115,8 @@ object EnumFieldSpec extends Specification("EnumField Specification") with Mongo er.save val erFromDb = EnumRec.find(er.id) - erFromDb must notBeEmpty - erFromDb foreach { er2 => + erFromDb.isDefined must_== true + erFromDb.toList map { er2 => er2 mustEqual er er2.dowOptional.valueBox mustEqual Empty } @@ -129,8 +130,8 @@ object EnumFieldSpec extends Specification("EnumField Specification") with Mongo er.save val erFromDb = EnumRec.find(er.id) - erFromDb must notBeEmpty - erFromDb foreach { er2 => + erFromDb.isDefined must_== true + erFromDb.toList map { er2 => er2 mustEqual er er2.dowOptional.valueBox mustEqual Full(WeekDay.Sat) } diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/field/EnumNameFieldSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/field/EnumNameFieldSpec.scala index 326973cea0..daf54ad368 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/field/EnumNameFieldSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/field/EnumNameFieldSpec.scala @@ -1,5 +1,5 @@ /* - * Copyright 2010 WorldWide Conferencing, LLC + * 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. @@ -20,7 +20,7 @@ package record package field import org.bson.types.ObjectId -import org.specs.Specification +import org.specs2.mutable.Specification import net.liftweb.common._ import net.liftweb.json.ext.EnumNameSerializer @@ -68,7 +68,8 @@ package enumnamefieldspecs { /** * Systems under specification for EnumNameField. */ -object EnumNameFieldSpec extends Specification("EnumNameField Specification") with MongoTestKit { +object EnumNameFieldSpec extends Specification with MongoTestKit { + "EnumNameField Specification".title import enumnamefieldspecs._ @@ -80,8 +81,8 @@ object EnumNameFieldSpec extends Specification("EnumNameField Specification") wi val er = EnumNameRec.createRecord.save val erFromDb = EnumNameRec.find(er.id) - erFromDb must notBeEmpty - erFromDb foreach { er2 => + erFromDb.isDefined must_== true + erFromDb.toList map { er2 => er2 mustEqual er er2.dow.value mustEqual WeekDay.Mon er2.dowOptional.valueBox mustEqual Empty @@ -98,8 +99,8 @@ object EnumNameFieldSpec extends Specification("EnumNameField Specification") wi .save val erFromDb = EnumNameRec.find(er.id) - erFromDb must notBeEmpty - erFromDb foreach { er2 => + erFromDb.isDefined must_== true + erFromDb.toList map { er2 => er2 mustEqual er er2.dow.value mustEqual WeekDay.Tue er2.jsonobj.value mustEqual JsonObj(WeekDay.Sun) @@ -114,8 +115,8 @@ object EnumNameFieldSpec extends Specification("EnumNameField Specification") wi er.save val erFromDb = EnumNameRec.find(er.id) - erFromDb must notBeEmpty - erFromDb foreach { er2 => + erFromDb.isDefined must_== true + erFromDb.toList map { er2 => er2 mustEqual er er2.dowOptional.valueBox mustEqual Empty } @@ -129,8 +130,8 @@ object EnumNameFieldSpec extends Specification("EnumNameField Specification") wi er.save val erFromDb = EnumNameRec.find(er.id) - erFromDb must notBeEmpty - erFromDb foreach { er2 => + erFromDb.isDefined must_== true + erFromDb.toList map { er2 => er2 mustEqual er er2.dowOptional.valueBox mustEqual Full(WeekDay.Sat) } From 8b1c891ae9c0f50e1794afc69d8c2f5c1527c257 Mon Sep 17 00:00:00 2001 From: Naftoli Gugenheim Date: Wed, 1 Aug 2012 02:28:29 -0400 Subject: [PATCH 0141/1949] lift-squery-record --- .../squerylrecord/SquerylRecordSpec.scala | 72 ++++++++++--------- 1 file changed, 38 insertions(+), 34 deletions(-) diff --git a/persistence/squeryl-record/src/test/scala/net/liftweb/squerylrecord/SquerylRecordSpec.scala b/persistence/squeryl-record/src/test/scala/net/liftweb/squerylrecord/SquerylRecordSpec.scala index 54164713de..c1fa9a5d06 100644 --- a/persistence/squeryl-record/src/test/scala/net/liftweb/squerylrecord/SquerylRecordSpec.scala +++ b/persistence/squeryl-record/src/test/scala/net/liftweb/squerylrecord/SquerylRecordSpec.scala @@ -19,7 +19,11 @@ import org.squeryl.dsl.ast.FunctionNode import org.squeryl.internals.OutMapper import org.squeryl.dsl.StringExpression import org.squeryl.dsl.DateExpression -import org.specs.Specification + +import org.specs2.mutable.Specification +import org.specs2.specification.AroundExample +import org.specs2.execute.Result + import record.{ BaseField, Record } import record.field._ import RecordTypeMode._ @@ -35,22 +39,23 @@ import util.Helpers /** * Systems under specification for SquerylRecord. */ -object SquerylRecordSpec extends Specification("SquerylRecord Specification") { +class SquerylRecordSpec extends Specification with AroundExample { + "SquerylRecord Specification".title + sequential - val session = new LiftSession("", Helpers.randomString(20), Empty) - doBeforeSpec { + lazy val session = new LiftSession("", Helpers.randomString(20), Empty) + protected def around[T <% Result](t: =>T) = { S.initIfUninitted(session) { DBHelper.initSquerylRecordWithInMemoryDB() DBHelper.createSchema() + t } } - // NOTE: Use explicit forExample() in the examples to avoid - // implicit ambiguity with Specs 1.6.6 in Scala 2.8.0 "SquerylRecord" should { - forExample("load record by ID") in { + "load record by ID" in { transaction { - S.initIfUninitted(session){ + S.initIfUninitted(session) { val company = companies.lookup(td.c2.id) checkCompaniesEqual(company.get, td.c2) @@ -60,7 +65,7 @@ object SquerylRecordSpec extends Specification("SquerylRecord Specification") { } } - forExample("load record by string field value") in { + "load record by string field value" in { transaction { S.initIfUninitted(session){ val company = from(companies)(c => @@ -70,7 +75,7 @@ object SquerylRecordSpec extends Specification("SquerylRecord Specification") { } } - forExample("support order by") in { + "support order by" in { transaction { val orderedCompanies = from(companies)(c => select(c) orderBy (c.name)) @@ -82,17 +87,17 @@ object SquerylRecordSpec extends Specification("SquerylRecord Specification") { } } - forExample("support normal joins") in { + "support normal joins" in { transaction { val companiesWithEmployees = from(companies, employees)((c, e) => where(c.id === e.companyId.get) select ((c.id, e.id))).toList companiesWithEmployees must haveSize(td.allEmployees.size) - companiesWithEmployees must containAll(td.allEmployees map { e => (e.companyId.get, e.id) }) + companiesWithEmployees must containAllOf(td.allEmployees map { e => (e.companyId.get, e.id) }) } } - forExample("support left outer joins") in { + "support left outer joins" in { transaction { S.initIfUninitted(session){ val companiesWithEmployees = join(companies, employees.leftOuter)((c, e) => @@ -145,7 +150,7 @@ object SquerylRecordSpec extends Specification("SquerylRecord Specification") { } } - forExample("support one to many relations") in { + "support one to many relations" in { transaction { val company = companies.lookup(td.c1.id) company must beSome[Company] @@ -157,7 +162,7 @@ object SquerylRecordSpec extends Specification("SquerylRecord Specification") { } } - forExample("support many to many relations") in { + "support many to many relations" in { transactionWithRollback { td.e1.rooms must haveSize(2) @@ -171,7 +176,7 @@ object SquerylRecordSpec extends Specification("SquerylRecord Specification") { } } - forExample("support updates") in { + "support updates" in { val id = td.c1.id transactionWithRollback { @@ -200,14 +205,14 @@ object SquerylRecordSpec extends Specification("SquerylRecord Specification") { } } - forExample("support delete") in { + "support delete" in { transactionWithRollback { employees.delete(td.e2.id) employees.lookup(td.e2.id) must beNone } } - forExample("support select with properties of formerly fetched objects") in { + "support select with properties of formerly fetched objects" in { transaction { S.initIfUninitted(session) { val company = companies.lookup(td.c2.id).head @@ -222,13 +227,13 @@ object SquerylRecordSpec extends Specification("SquerylRecord Specification") { } } - forExample("support many to many relations") >> { + "support many to many relations" >> { transactionWithRollback { td.e1.rooms must haveSize(2) } } - forExample("support date/time queries") >> { + "support date/time queries" >> { transaction { val c1 = from(companies)(c => where(c.created <= Calendar.getInstance) @@ -242,7 +247,7 @@ object SquerylRecordSpec extends Specification("SquerylRecord Specification") { } } - forExample("support inner queries") >> { + "support inner queries" >> { import record.field._ transaction { @@ -298,7 +303,7 @@ object SquerylRecordSpec extends Specification("SquerylRecord Specification") { } - forExample("support the CRUDify trait") >> { + "support the CRUDify trait" >> { transaction { val company = Company.create.name("CRUDify Company"). created(Calendar.getInstance()). @@ -323,7 +328,7 @@ object SquerylRecordSpec extends Specification("SquerylRecord Specification") { } } - forExample("Support Optimistic Locking") >> { + "Support Optimistic Locking" >> { val company = Company.create.name("Optimistic Company"). created(Calendar.getInstance()). country(Countries.USA). @@ -352,7 +357,7 @@ object SquerylRecordSpec extends Specification("SquerylRecord Specification") { } } - forExample("Allow custom functions") in { + "Allow custom functions" in { inTransaction { val created = from(companies)(c => @@ -363,14 +368,14 @@ object SquerylRecordSpec extends Specification("SquerylRecord Specification") { } } - forExample("Support precision and scale taken from DecimalTypedField") >> { + "Support precision and scale taken from DecimalTypedField" >> { val posoMetaData = companies.posoMetaData val fieldMetaData = posoMetaData.findFieldMetaDataForProperty("employeeSatisfaction").get val columnDefinition = new PostgreSqlAdapter().writeColumnDeclaration(fieldMetaData, false, MySchema) columnDefinition.endsWith("numeric(" + Company.employeeSatisfaction.context.getPrecision() +"," + Company.employeeSatisfaction.scale + ")") must_== true } - forExample("Properly reset the dirty_? flag after loading entities") >> inTransaction { + "Properly reset the dirty_? flag after loading entities" >> inTransaction { val company = from(companies)(company => select(company)).page(0, 1).single company.allFields map { f => f.dirty_? must_== false } @@ -388,9 +393,9 @@ object SquerylRecordSpec extends Specification("SquerylRecord Specification") { * back the transaction afterwards. */ private def transactionWithRollback[T](code: => T): T = { - + def rollback: Unit = throw new TransactionRollbackException() - + var result: T = null.asInstanceOf[T] try { transaction { @@ -404,7 +409,7 @@ object SquerylRecordSpec extends Specification("SquerylRecord Specification") { result } - private def checkCompaniesEqual(c1: Company, c2: Company) { + private def checkCompaniesEqual(c1: Company, c2: Company): Result = { val cmp = new RecordComparer[Company](c1, c2) cmp.check(_.idField) cmp.check(_.description) @@ -415,7 +420,7 @@ object SquerylRecordSpec extends Specification("SquerylRecord Specification") { cmp.checkXHtml() } - private def checkEmployeesEqual(e1: Employee, e2: Employee) { + private def checkEmployeesEqual(e1: Employee, e2: Employee): Result = { val cmp = new RecordComparer[Employee](e1, e2) cmp.check(_.name) cmp.check(_.companyId) @@ -435,7 +440,7 @@ object SquerylRecordSpec extends Specification("SquerylRecord Specification") { p2 must beSome[Array[Byte]] p2.get.size must_== p.size - (0 until p.size).foreach { i => + (0 until p.size) map { i => p2.get(i) must_== p(i) } } @@ -444,15 +449,14 @@ object SquerylRecordSpec extends Specification("SquerylRecord Specification") { } class RecordComparer[T <: Record[T]](val r1: T, val r2: T) { - def check(fieldExtractor: (T) => BaseField) { + def check(fieldExtractor: (T) => BaseField): Result = { val f1 = fieldExtractor(r1) val f2 = fieldExtractor(r2) f1.get must_== f2.get f1.name must_== f2.name } - def checkXHtml() { + def checkXHtml(): Result = r1.toXHtml must_== r2.toXHtml - } } } From 8923c699dfdb92ee73aa37ef062d97776bb6831c Mon Sep 17 00:00:00 2001 From: David Pollak Date: Wed, 1 Aug 2012 14:28:23 -0700 Subject: [PATCH 0142/1949] Added some extra error logging. Introduced the ignore snippet failure mode --- .../net/liftweb/builtin/snippet/Embed.scala | 12 ++-- .../scala/net/liftweb/http/LiftSession.scala | 67 ++++++++++++------- .../src/main/scala/net/liftweb/http/S.scala | 46 +++++++++++++ 3 files changed, 98 insertions(+), 27 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Embed.scala b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Embed.scala index 2143529812..55b313d91e 100644 --- a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Embed.scala +++ b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Embed.scala @@ -41,8 +41,8 @@ object Embed extends DispatchSnippet { def render(kids: NodeSeq) : NodeSeq = (for { ctx <- S.session ?~ ("FIX"+"ME: session is invalid") - what <- S.attr ~ ("what") ?~ ("FIX" + "ME The 'what' attribute not defined") - templateOpt <- ctx.findTemplate(what.text) ?~ ("FIX"+"ME the "+what+" template not found") + what <- S.attr ~ ("what") ?~ ("FIX" + "ME The 'what' attribute not defined. In order to embed a template, the 'what' attribute must be specified") + templateOpt <- ctx.findTemplate(what.text) ?~ ("FIX"+"ME trying to embed a template named '"+what+"', but the template not found. ") } yield (what, Templates.checkForContentId(templateOpt))) match { case Full((what,template)) => { val bindingMap : Map[String,NodeSeq] = Map(kids.flatMap({ @@ -62,9 +62,13 @@ object Embed extends DispatchSnippet { BindHelpers.bind(bindingMap, template) } - case Failure(msg, _, _) => throw new SnippetExecutionException(msg) + case Failure(msg, _, _) => + logger.error("'embed' snippet failed with message: "+msg) + throw new SnippetExecutionException("Embed Snippet failed: "+msg) - case _ => throw new SnippetExecutionException("session is invalid") + case _ => + logger.error("'embed' snippet failed because it was invoked outside session context") + throw new SnippetExecutionException("session is invalid") } } diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index a63672baaf..78bff90dc6 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -1461,6 +1461,15 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, why: LiftRules.SnippetFailures.Value, addlMsg: NodeSeq, whole: NodeSeq): NodeSeq = { + + (for { + nodeSeq <- S.currentSnippetNodeSeq if S.ignoreFailedSnippets + } yield { + // don't keep nailing the same snippet name if we just failed it + S.currentSnippet.foreach(s => _lastFoundSnippet.set(s)) + nodeSeq + }) openOr { + for { f <- LiftRules.snippetFailedFunc.toList } { @@ -1486,6 +1495,7 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, {whole.toString}
  • hU>jF8`UCnXVSf5L*fvd~rwV2=Zs+zmPx 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�SSfHkL=V(FvZ+pnL5)E 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

    */ def children: List[JValue] = this match { - case JObject(l) => l map (_._2) + case JObject(l) => l map (_.value) case JArray(l) => l case _ => Nil } @@ -160,7 +160,7 @@ object JsonAST { v match { case JObject(l) => l.foldLeft(newAcc) { - case (a, (name, value)) => value.fold(a)(f) + case (a, JField(name, value)) => value.fold(a)(f) } case JArray(l) => l.foldLeft(newAcc) { (a, e) => @@ -179,7 +179,7 @@ object JsonAST { def rec(acc: A, v: JValue) = { v match { case JObject(l) => l.foldLeft(acc) { - case (a, field@(name, value)) => value.foldField(f(a, field))(f) + case (a, field@JField(name, value)) => value.foldField(f(a, field))(f) } case JArray(l) => l.foldLeft(acc)((a, e) => e.foldField(a)(f)) case _ => acc @@ -200,7 +200,7 @@ object JsonAST { */ def map(f: JValue => JValue): JValue = { def rec(v: JValue): JValue = v match { - case JObject(l) => f(JObject(l.map { case (n, v) => (n, rec(v)) })) + case JObject(l) => f(JObject(l.map { field => field.copy(value = rec(field.value)) })) case JArray(l) => f(JArray(l.map(rec))) case x => f(x) } @@ -219,7 +219,7 @@ object JsonAST { */ def mapField(f: JField => JField): JValue = { def rec(v: JValue): JValue = v match { - case JObject(l) => JObject(l.map { case (n, v) => f(n, rec(v)) }) + case JObject(l) => JObject(l.map { field => f(field.copy(value = rec(field.value))) }) case JArray(l) => JArray(l.map(rec)) case x => x } @@ -288,7 +288,7 @@ object JsonAST { def findField(p: JField => Boolean): Option[JField] = { def find(json: JValue): Option[JField] = json match { case JObject(fs) if (fs find p).isDefined => return fs find p - case JObject(fs) => fs.flatMap { case (n, v) => find(v) }.headOption + case JObject(fs) => fs.flatMap { case JField(n, v) => find(v) }.headOption case JArray(l) => l.flatMap(find _).headOption case _ => None } @@ -305,7 +305,7 @@ object JsonAST { def find(json: JValue): Option[JValue] = { if (p(json)) return Some(json) json match { - case JObject(fs) => fs.flatMap { case (n, v) => find(v) }.headOption + case JObject(fs) => fs.flatMap { case JField(n, v) => find(v) }.headOption case JArray(l) => l.flatMap(find _).headOption case _ => None } @@ -376,7 +376,7 @@ object JsonAST { *
    */ def removeField(p: JField => Boolean): JValue = this mapField { - case x if p(x) => (x._1, JNothing) + case x if p(x) => JField(x.name, JNothing) case x => x } @@ -478,11 +478,12 @@ object JsonAST { case class JObject(obj: List[JField]) extends JValue { type Values = Map[String, Any] - def values = + def values = { obj.map { - case (n, v) => - (n, v.values) + case JField(name, value) => + (name, value.values): (String, Any) }.toMap + } override def equals(that: Any): Boolean = that match { case o: JObject => obj.toSet == o.obj.toSet @@ -501,11 +502,7 @@ object JsonAST { override def apply(i: Int): JValue = arr(i) } - type JField = (String, JValue) - object JField { - def apply(name: String, value: JValue) = (name, value) - def unapply(f: JField): Option[(String, JValue)] = Some(f) - } + case class JField(name: String, value: JValue) /** Renders JSON. * @see Printer#compact @@ -522,13 +519,13 @@ object JsonAST { case JString(s) => text("\"" + quote(s) + "\"") case JArray(arr) => text("[") :: series(trimArr(arr).map(render)) :: text("]") case JObject(obj) => - val nested = break :: fields(trimObj(obj).map { case (name, value) => text("\"" + quote(name) + "\":") :: render(value) }) + val nested = break :: fields(trimObj(obj).map { case JField(name, value) => text("\"" + quote(name) + "\":") :: render(value) }) text("{") :: nest(2, nested) :: break :: text("}") case JNothing => sys.error("can't render 'nothing'") //TODO: this should not throw an exception } private def trimArr(xs: List[JValue]) = xs.filter(_ != JNothing) - private def trimObj(xs: List[JField]) = xs.filter(_._2 != JNothing) + private def trimObj(xs: List[JField]) = xs.filter(_.value != JNothing) private def series(docs: List[Document]) = punctuate(text(","), docs) private def fields(docs: List[Document]) = punctuate(text(",") :: break, docs) @@ -608,7 +605,7 @@ object JsonAST { buf.append("{") //open bracket if (!xs.isEmpty) { xs.foreach { - case (name, value) if value != JNothing => + case JField(name, value) if value != JNothing => bufQuote(name, buf) buf.append(":") bufRender(value, buf) diff --git a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala index b4f1d717a2..953f32de68 100644 --- a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala +++ b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala @@ -145,7 +145,7 @@ object JsonParser { // This is a slightly faster way to correct order of fields and arrays than using 'map'. def reverse(v: JValue): JValue = v match { - case JObject(l) => JObject((l.map { case (n, v) => (n, reverse(v)) }).reverse) + case JObject(l) => JObject((l.map { field => field.copy(value = reverse(field.value)) }).reverse) case JArray(l) => JArray(l.map(reverse).reverse) case x => x } @@ -157,10 +157,10 @@ object JsonParser { } vals.peekOption match { - case Some((name: String, value)) => + case Some(JField(name: String, value)) => vals.pop(classOf[JField]) val obj = vals.peek(classOf[JObject]) - vals.replace(JObject((name, toJValue(v)) :: obj.obj)) + vals.replace(JObject(JField(name, toJValue(v)) :: obj.obj)) case Some(o: JObject) => vals.replace(JObject(vals.peek(classOf[JField]) :: o.obj)) case Some(a: JArray) => vals.replace(JArray(toJValue(v) :: a.arr)) @@ -172,12 +172,12 @@ object JsonParser { def newValue(v: JValue) { if (!vals.isEmpty) vals.peekAny match { - case (name: String, value) => + case JField(name, value) => vals.pop(classOf[JField]) val obj = vals.peek(classOf[JObject]) - vals.replace(JObject((name, v) :: obj.obj)) + vals.replace(JObject(JField(name, v) :: obj.obj)) case a: JArray => vals.replace(JArray(v :: a.arr)) - case _ => p.fail("expected field or array") + case other => p.fail("expected field or array but got " + other) } else { vals.push(v) root = Some(v) diff --git a/core/json/src/main/scala/net/liftweb/json/Merge.scala b/core/json/src/main/scala/net/liftweb/json/Merge.scala index 6cca0d759a..f1fb6edc65 100644 --- a/core/json/src/main/scala/net/liftweb/json/Merge.scala +++ b/core/json/src/main/scala/net/liftweb/json/Merge.scala @@ -68,9 +68,9 @@ object Merge { private[json] def mergeFields(vs1: List[JField], vs2: List[JField]): List[JField] = { def mergeRec(xleft: List[JField], yleft: List[JField]): List[JField] = xleft match { case Nil => yleft - case (xn, xv) :: xs => yleft find (_._1 == xn) match { - case Some(y @ (yn, yv)) => - (xn, merge(xv, yv)) :: mergeRec(xs, yleft filterNot (_ == y)) + case JField(xn, xv) :: xs => yleft find (_.name == xn) match { + case Some(y @ JField(yn, yv)) => + JField(xn, merge(xv, yv)) :: mergeRec(xs, yleft filterNot (_ == y)) case None => JField(xn, xv) :: mergeRec(xs, yleft) } } diff --git a/core/json/src/main/scala/net/liftweb/json/Xml.scala b/core/json/src/main/scala/net/liftweb/json/Xml.scala index d3bba05c17..209a752f4b 100644 --- a/core/json/src/main/scala/net/liftweb/json/Xml.scala +++ b/core/json/src/main/scala/net/liftweb/json/Xml.scala @@ -107,7 +107,7 @@ object Xml { case XLeaf((name, value), attrs) => (value, attrs) match { case (_, Nil) => toJValue(value) case (XValue(""), xs) => JObject(mkFields(xs)) - case (_, xs) => JObject((name, toJValue(value)) :: mkFields(xs)) + case (_, xs) => JObject(JField(name, toJValue(value)) :: mkFields(xs)) } case XNode(xs) => JObject(mkFields(xs)) case XArray(elems) => JArray(elems.map(toJValue)) @@ -169,7 +169,7 @@ object Xml { */ def toXml(json: JValue): NodeSeq = { def toXml(name: String, json: JValue): NodeSeq = json match { - case JObject(fields) => new XmlNode(name, fields flatMap { case (n, v) => toXml(n, v) }) + case JObject(fields) => new XmlNode(name, fields flatMap { case JField(n, v) => toXml(n, v) }) case JArray(xs) => xs flatMap { v => toXml(name, v) } case JInt(x) => new XmlElem(name, x.toString) case JDouble(x) => new XmlElem(name, x.toString) @@ -180,7 +180,7 @@ object Xml { } json match { - case JObject(fields) => fields flatMap { case (name, value) => toXml(name, value) } + case JObject(fields) => fields flatMap { case JField(name, value) => toXml(name, value) } case x => toXml("root", x) } } diff --git a/core/json/src/test/scala/net/liftweb/json/Examples.scala b/core/json/src/test/scala/net/liftweb/json/Examples.scala index ce9726ec85..b1191175a1 100644 --- a/core/json/src/test/scala/net/liftweb/json/Examples.scala +++ b/core/json/src/test/scala/net/liftweb/json/Examples.scala @@ -120,13 +120,13 @@ trait AbstractExamples extends Specification { } "JSON building example" in { - val json = JObject(("name", JString("joe")), ("age", JInt(34))) ++ JObject(("name", ("mazy")), ("age", JInt(31))) + val json = JObject(JField("name", JString("joe")), JField("age", JInt(34))) ++ JObject(JField("name", ("mazy")), JField("age", JInt(31))) print(json) mustEqual """[{"name":"joe","age":34},{"name":"mazy","age":31}]""" } "JSON building with implicit primitive conversions example" in { import Implicits._ - val json = JObject(("name", "joe"), ("age", 34)) ++ JObject(("name", "mazy"), ("age", 31)) + val json = JObject(JField("name", "joe"), JField("age", 34)) ++ JObject(JField("name", "mazy"), JField("age", 31)) print(json) mustEqual """[{"name":"joe","age":34},{"name":"mazy","age":31}]""" } diff --git a/core/json/src/test/scala/net/liftweb/json/JsonAstSpec.scala b/core/json/src/test/scala/net/liftweb/json/JsonAstSpec.scala index 22dca7f6e9..f2bf144bf7 100644 --- a/core/json/src/test/scala/net/liftweb/json/JsonAstSpec.scala +++ b/core/json/src/test/scala/net/liftweb/json/JsonAstSpec.scala @@ -107,7 +107,7 @@ object JsonAstSpec extends Specification with JValueGen with ScalaCheck { val anyReplacement = (x: JValue, replacement: JObject) => { def findOnePath(jv: JValue, l: List[String]): List[String] = jv match { case JObject(fl) => fl match { - case field :: xs => findOnePath(field._2, l) + case field :: xs => findOnePath(field.value, l) case Nil => l } case _ => l diff --git a/core/json/src/test/scala/net/liftweb/json/XmlExamples.scala b/core/json/src/test/scala/net/liftweb/json/XmlExamples.scala index 818044f66b..09e026fb28 100644 --- a/core/json/src/test/scala/net/liftweb/json/XmlExamples.scala +++ b/core/json/src/test/scala/net/liftweb/json/XmlExamples.scala @@ -150,10 +150,10 @@ object XmlExamples extends Specification { // { ..., "fieldName": "", "attrName":"someValue", ...} -> // { ..., "fieldName": { "attrName": f("someValue") }, ... } def attrToObject(fieldName: String, attrName: String, f: JString => JValue)(json: JValue) = json.transformField { - case (n, v: JString) if n == attrName => JField(fieldName, JObject(JField(n, f(v)) :: Nil)) - case (n, JString("")) if n == fieldName => JField(n, JNothing) + case JField(n, v: JString) if n == attrName => JField(fieldName, JObject(JField(n, f(v)) :: Nil)) + case JField(n, JString("")) if n == fieldName => JField(n, JNothing) } transformField { - case (n, x: JObject) if n == attrName => JField(fieldName, x) + case JField(n, x: JObject) if n == attrName => JField(fieldName, x) } "Example with multiple attributes, multiple nested elements " in { diff --git a/persistence/mapper/src/main/scala/net/liftweb/mapper/MetaMapper.scala b/persistence/mapper/src/main/scala/net/liftweb/mapper/MetaMapper.scala index 38262493f8..b2e75ecffb 100644 --- a/persistence/mapper/src/main/scala/net/liftweb/mapper/MetaMapper.scala +++ b/persistence/mapper/src/main/scala/net/liftweb/mapper/MetaMapper.scala @@ -674,10 +674,11 @@ trait MetaMapper[A<:Mapper[A]] extends BaseMetaMapper with Mapper[A] { import JsonAST._ ret.runSafe { - for { - field <- json.obj - JField("$persisted", JBool(per)) <- field - } ret.persisted_? = per + json.findField { + case JField("$persisted", JBool(per)) => + ret.persisted_? = per + true + } for { field <- json.obj diff --git a/persistence/record/src/main/scala/net/liftweb/record/RecordHelpers.scala b/persistence/record/src/main/scala/net/liftweb/record/RecordHelpers.scala index f565f058bb..fe566947f9 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/RecordHelpers.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/RecordHelpers.scala @@ -29,7 +29,6 @@ object RecordHelpers { case JArray(vs) => JsArray(vs.map(jvalueToJsExp): _*) case JBool(b) => if (b) JsTrue else JsFalse case JDouble(d) => Num(d) - case JField(n,v) => sys.error("no parallel") case JInt(i) => Num(i) case JNothing => JsNull case JNull => JsNull diff --git a/web/webkit/src/main/scala/net/liftweb/http/rest/RestHelper.scala b/web/webkit/src/main/scala/net/liftweb/http/rest/RestHelper.scala index d9683f1a1a..559d847297 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/rest/RestHelper.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/rest/RestHelper.scala @@ -705,19 +705,11 @@ trait RestHelper extends LiftRules.DispatchPF { original match { case JObject(fields) => toMerge match { - case jf: JField => JObject(replace(fields, jf)) case JObject(otherFields) => JObject(otherFields.foldLeft(fields)(replace(_, _))) case _ => original } - case jf: JField => - toMerge match { - case jfMerge: JField => JObject(replace(List(jf), jfMerge)) - case JObject(otherFields) => - JObject(otherFields.foldLeft(List(jf))(replace(_, _))) - case _ => original - } case _ => original // can't merge } } From 17a17cb813411291cb1e5c284cd96f350c4dea9c Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 8 Jun 2014 23:48:18 -0400 Subject: [PATCH 0949/1949] Fix docs for case class JField. --- core/json/README.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/core/json/README.md b/core/json/README.md index cad918fd81..aeca5c7041 100644 --- a/core/json/README.md +++ b/core/json/README.md @@ -13,7 +13,7 @@ a JSON document as a syntax tree. case class JObject(obj: List[JField]) extends JValue case class JArray(arr: List[JValue]) extends JValue - type JField = (String, JValue) + case class JField(String, JValue) All features are implemented in terms of above AST. Functions are used to transform the AST itself, or to transform the AST between different formats. Common transformations @@ -118,7 +118,7 @@ Any valid json can be parsed into internal AST format. scala> import net.liftweb.json._ scala> parse(""" { "numbers" : [1, 2, 3, 4] } """) res0: net.liftweb.json.JsonAST.JValue = - JObject(List((numbers,JArray(List(JInt(1), JInt(2), JInt(3), JInt(4)))))) + JObject(List(JField(numbers,JArray(List(JInt(1), JInt(2), JInt(3), JInt(4)))))) Producing JSON ============== @@ -262,7 +262,7 @@ Please see more examples in src/test/scala/net/liftweb/json/MergeExamples.scala changed: net.liftweb.json.JsonAST.JValue = JNothing added: net.liftweb.json.JsonAST.JValue = JNothing deleted: net.liftweb.json.JsonAST.JValue = JObject(List((lotto,JObject(List((winners, - JArray(List(JObject(List((winner-id,JInt(54)), (numbers,JArray( + JArray(List(JObject(List(JField(winner-id,JInt(54)), JField(numbers,JArray( List(JInt(52), JInt(3), JInt(12), JInt(11), JInt(18), JInt(22)))))))))))))) @@ -342,7 +342,7 @@ Json AST can be queried using XPath like functions. Following REPL session shows scala> json \\ "spouse" res0: net.liftweb.json.JsonAST.JValue = JObject(List( - (person,JObject(List((name,JString(Marilyn)), (age,JInt(33))))))) + JField(person,JObject(List((name,JString(Marilyn)), (age,JInt(33))))))) scala> compact(render(res0)) res1: String = {"person":{"name":"Marilyn","age":33}} @@ -363,20 +363,20 @@ Json AST can be queried using XPath like functions. Following REPL session shows case JField("name", _) => true case _ => false } - res6: Option[net.liftweb.json.JsonAST.JValue] = Some((name,JString(Joe))) + res6: Option[net.liftweb.json.JsonAST.JField] = Some(JField(name,JString(Joe))) scala> json filterField { case JField("name", _) => true case _ => false } - res7: List[net.liftweb.json.JsonAST.JValue] = List(JField(name,JString(Joe)), JField(name,JString(Marilyn))) + res7: List[net.liftweb.json.JsonAST.JField] = List(JField(name,JString(Joe)), JField(name,JString(Marilyn))) scala> json transformField { case ("name", JString(s)) => ("NAME", JString(s.toUpperCase)) } - res8: net.liftweb.json.JsonAST.JValue = JObject(List((person,JObject(List( - (NAME,JString(JOE)), (age,JInt(35)), (spouse,JObject(List( - (person,JObject(List((NAME,JString(MARILYN)), (age,JInt(33))))))))))))) + res8: net.liftweb.json.JsonAST.JValue = JObject(List(JField(person,JObject(List( + JField(NAME,JString(JOE)), JField(age,JInt(35)), JField(spouse,JObject(List( + JField(person,JObject(List(JField(NAME,JString(MARILYN)), JField(age,JInt(33))))))))))))) scala> json.values res8: scala.collection.immutable.Map[String,Any] = Map(person -> Map(name -> Joe, age -> 35, spouse -> Map(person -> Map(name -> Marilyn, age -> 33)))) @@ -399,7 +399,7 @@ Indexed path expressions work too and values can be unboxed using type expressio """) scala> (json \ "children")(0) - res0: net.liftweb.json.JsonAST.JValue = JObject(List((name,JString(Mary)), (age,JInt(5)))) + res0: net.liftweb.json.JsonAST.JValue = JObject(List(JField(name,JString(Mary)), JField(age,JInt(5)))) scala> (json \ "children")(1) \ "name" res1: net.liftweb.json.JsonAST.JValue = JString(Mazy) @@ -459,7 +459,7 @@ Use transform function to postprocess AST. scala> case class Person(firstname: String) scala> json transformField { - case ("first-name", x) => ("firstname", x) + case JField("first-name", x) => ("firstname", x) } Extraction function tries to find the best matching constructor when case class has auxiliary @@ -664,8 +664,8 @@ XML document which happens to have just one user-element will generate a JSON do is rarely a desired outcome. These both problems can be fixed by following transformation function. scala> json transformField { - case ("id", JString(s)) => ("id", JInt(s.toInt)) - case ("user", x: JObject) => ("user", JArray(x :: Nil)) + case JField("id", JString(s)) => ("id", JInt(s.toInt)) + case JField("user", x: JObject) => ("user", JArray(x :: Nil)) } Other direction is supported too. Converting JSON to XML: From 34de88d3375766ecd10696c83eebb2234194dfb8 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 9 Jun 2014 22:36:57 -0400 Subject: [PATCH 0950/1949] updateListeners(Any)->sendListenersMessage. We do this because there is an updateListeners overload that takes a list of actors to update, which is optional. Having an updateListeners overload that takes an Any makes it really easy to accidentally call the wrong updateListeners method by having the wrong type of List. Additionally, it's particularly confusing to have the two methods named the same thing when they behave differently. We name the method that sends all listeners a message that is passed in `sendListenersMessage` and leave `updateListeners` for sending all listeners the message generated by `createUpdate`. --- .../src/main/scala/net/liftweb/http/CometActor.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala b/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala index aad020e6aa..f76c87a754 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala @@ -162,7 +162,7 @@ object ListenerManager { * * override def mediumPriority = { * case Tick => { - * updateListeners(Tick) + * sendListenersMessage(Tick) * ActorPing.schedule(this, Tick, 1000L) * } * } @@ -230,10 +230,10 @@ trait ListenerManager { } /** - * Update the listeners with a message that we create. Note that - * with this invocation the createUpdate method is not used. + * Send a message we create to all of the listeners. Note that with this + * invocation the createUpdate method is not used. */ - protected def updateListeners(msg: Any) { + protected def sendListenersMessage(msg: Any) { listeners foreach (_._1 ! msg) } From 4bf7fba9c334d6f3bb56f231daeb7584ac86e797 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 9 Jun 2014 23:53:43 -0400 Subject: [PATCH 0951/1949] updateListeners(Any)->sendListenersMessage. We do this because it's particularly confusing to have the two methods named the same thing when they behave differently. We name the method that sends all listeners a message that is passed in `sendListenersMessage` and leave `updateListeners` for sending all listeners the message generated by `createUpdate`. The old updateListeners(Any) method is deprecated for the 2.6 cycle. --- .../src/main/scala/net/liftweb/http/CometActor.scala | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala b/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala index 97424b7be4..a122e3f3f8 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala @@ -225,13 +225,16 @@ trait ListenerManager { } /** - * Update the listeners with a message that we create. Note that - * with this invocation the createUpdate method is not used. + * Send a message we create to all of the listeners. Note that with this + * invocation the createUpdate method is not used. */ - protected def updateListeners(msg: Any) { + protected def sendListenersMessage(msg: Any) { listeners foreach (_._1 ! msg) } + @deprecated("Use sendListenersMessage instead.", "2.6") + protected def updateListeners(msg: Any) { sendListenersMessage(msg) } + /** * This method provides legacy functionality for filtering messages * before sending to each registered actor. It is deprecated in From 02f3a84cdb17241fa7836a3197a2328ed5e45a99 Mon Sep 17 00:00:00 2001 From: izmailoff Date: Tue, 10 Jun 2014 21:22:23 -0400 Subject: [PATCH 0952/1949] Added documentation to warn clients about thread-local behavior of methods. --- contributors.md | 8 +++++++- core/util/src/main/scala/net/liftweb/util/Maker.scala | 9 +++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/contributors.md b/contributors.md index 1b530b3b7f..0760d36338 100644 --- a/contributors.md +++ b/contributors.md @@ -214,4 +214,10 @@ robertfreytag+lift at gmail .. com Mikhail Limansky ### Email: ### -mike.limansky at gmail dot com \ No newline at end of file +mike.limansky at gmail dot com + +### Name: ### +Aleksey Izmailov + +### Email: ### +izmailoff at gmail dot com diff --git a/core/util/src/main/scala/net/liftweb/util/Maker.scala b/core/util/src/main/scala/net/liftweb/util/Maker.scala index 6c386c4465..c5d0db843c 100644 --- a/core/util/src/main/scala/net/liftweb/util/Maker.scala +++ b/core/util/src/main/scala/net/liftweb/util/Maker.scala @@ -128,12 +128,21 @@ trait StackableMaker[T] extends Maker[T] { case x => x } + /** + * Changes to the stack of Makers made by this method are thread-local! + */ def doWith[F](value: T)(f: => F): F = doWith(PValueHolder(Maker(value)))(f) + /** + * Changes to the stack of Makers made by this method are thread-local! + */ def doWith[F](vFunc: () => T)(f: => F): F = doWith(PValueHolder(Maker(vFunc)))(f) + /** + * Changes to the stack of Makers made by this method are thread-local! + */ def doWith[F](addl: PValueHolder[Maker[T]])(f: => F): F = { val old = _stack.get() _stack.set(addl :: stack) From 6a4fb84b16f15ae9653203a4f5756005c5f6910d Mon Sep 17 00:00:00 2001 From: izmailoff Date: Tue, 10 Jun 2014 21:22:23 -0400 Subject: [PATCH 0953/1949] Added documentation to warn clients about thread-local behavior of 'doWith' methods. --- contributors.md | 8 +++++++- core/util/src/main/scala/net/liftweb/util/Maker.scala | 9 +++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/contributors.md b/contributors.md index 1b530b3b7f..0760d36338 100644 --- a/contributors.md +++ b/contributors.md @@ -214,4 +214,10 @@ robertfreytag+lift at gmail .. com Mikhail Limansky ### Email: ### -mike.limansky at gmail dot com \ No newline at end of file +mike.limansky at gmail dot com + +### Name: ### +Aleksey Izmailov + +### Email: ### +izmailoff at gmail dot com diff --git a/core/util/src/main/scala/net/liftweb/util/Maker.scala b/core/util/src/main/scala/net/liftweb/util/Maker.scala index 6c386c4465..c5d0db843c 100644 --- a/core/util/src/main/scala/net/liftweb/util/Maker.scala +++ b/core/util/src/main/scala/net/liftweb/util/Maker.scala @@ -128,12 +128,21 @@ trait StackableMaker[T] extends Maker[T] { case x => x } + /** + * Changes to the stack of Makers made by this method are thread-local! + */ def doWith[F](value: T)(f: => F): F = doWith(PValueHolder(Maker(value)))(f) + /** + * Changes to the stack of Makers made by this method are thread-local! + */ def doWith[F](vFunc: () => T)(f: => F): F = doWith(PValueHolder(Maker(vFunc)))(f) + /** + * Changes to the stack of Makers made by this method are thread-local! + */ def doWith[F](addl: PValueHolder[Maker[T]])(f: => F): F = { val old = _stack.get() _stack.set(addl :: stack) From 325b095b29a861dddf0538f9816e055fa801fa71 Mon Sep 17 00:00:00 2001 From: Diego Medina Date: Tue, 10 Jun 2014 22:48:45 -0400 Subject: [PATCH 0954/1949] When tests run on cloudbees (or any other slow server), the time specs tend to take longer that 500 ms, so give these two tests up to a second to still be considered correct (this is how master is right now) --- .../src/test/scala/net/liftweb/util/TimeHelpersSpec.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/util/src/test/scala/net/liftweb/util/TimeHelpersSpec.scala b/core/util/src/test/scala/net/liftweb/util/TimeHelpersSpec.scala index 12fd6db64a..96fa10dda0 100644 --- a/core/util/src/test/scala/net/liftweb/util/TimeHelpersSpec.scala +++ b/core/util/src/test/scala/net/liftweb/util/TimeHelpersSpec.scala @@ -91,12 +91,12 @@ object TimeHelpersSpec extends Specification with ScalaCheck with TimeAmountsGen "have a later method returning a date relative to now plus the time span" in { val expectedTime = new Date().getTime + 3.seconds.millis - 3.seconds.later.getTime must beCloseTo(expectedTime, 700L) + 3.seconds.later.getTime must beCloseTo(expectedTime, 1000L) } "have an ago method returning a date relative to now minus the time span" in { val expectedTime = new Date().getTime - 3.seconds.millis - 3.seconds.ago.getTime must beCloseTo(expectedTime, 500L) + 3.seconds.ago.getTime must beCloseTo(expectedTime, 1000L) } "have a toString method returning the relevant number of weeks, days, hours, minutes, seconds, millis" in { val conversionIsOk = forAll(timeAmounts)((t: TimeAmounts) => { val (timeSpanToString, timeSpanAmounts) = t From e8ae670840bb53cb3f925a57bdfcb21f41173cd2 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 1 Jun 2014 23:09:04 -0400 Subject: [PATCH 0955/1949] Add early draft of CSS selector reference docs. --- docs/css-selectors.adoc | 83 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 docs/css-selectors.adoc diff --git a/docs/css-selectors.adoc b/docs/css-selectors.adoc new file mode 100644 index 0000000000..bdc4645cfb --- /dev/null +++ b/docs/css-selectors.adoc @@ -0,0 +1,83 @@ +:idprefix: +:idseparator: - +:toc: right +:toclevels: 2 + += CSS Selector Transforms + +Lift's templating strategy is much simpler than most systems, and is aimed at +cleanly and completely separating business logic from markup. Many a framework +has made this claim, but Lift is one of the few to have achieved this break +completely, using only HTML annotations with `data-` attributes. You can find +an overview of the full Lift CSS templating strategy in the [Lift templating +guide](). + +This document is a reference on the underpinnings of Lift templating, the CSS +Selector Transforms. These are used in Lift code to transform a block of HTML +by enriching it with data from the system and filtering it based on business +rules. + +== Selectors and Replacement Rules + +CSS Selector Transforms consist of three main components: + - The selector + - The subnode modification rule + - The transformation function + +/// + Nice to have: graphic that shows a selector transform pointing to each +/// + +The details of each are provided below, but first here ere are some simple +examples of transforms that you can write with links: + +.Replace the contents of all `a` elements with the text "Mozilla" +==== +```scala +"a *" #> "Mozilla" +``` +==== + +.Make all links point to Mozilla +==== +```scala +"a [href]" #> "http://mozilla.org" +``` +==== + +.Replace all elements with class `name` with a user's name +==== +```scala +".name" #> user.name +``` +==== + +.Do all three of the previous things at once +==== +"a *" #> "Mozilla" & +"a [href]" #> "http://mozilla.org" & +".name" #> user.name +==== + +These examples show a few options: + - You can select by element name or by class name. More available selectors are + in the section below on <>. + - You can set the body of an element, an attribute of an element, or even + replace the element altogether. More subnode modification rules are in the + section below on <>. + - You can combine multiple CSS selector transforms using the `&` operator. This + is subject to some limitations detailed in the section below on <>. + +== Available Selectors + +== Available Modification Rules + +== Transformation Functions + +== Combining Transforms + +== Macros and Strings + +Lift's CSS Selector Transforms can be used in two modes. Most basically, you +can create a `String` with the appropriate selector and then specify the +transformation you want to apply to matching blocks From 8f9b0ec9dbf6d51ce3575ae44afb92db2dec8ed2 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 2 Jun 2014 21:48:48 -0400 Subject: [PATCH 0956/1949] CSS selector transforms doc now lists available selectors. --- docs/css-selectors.adoc | 66 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 2 deletions(-) diff --git a/docs/css-selectors.adoc b/docs/css-selectors.adoc index bdc4645cfb..2c8a05d5b5 100644 --- a/docs/css-selectors.adoc +++ b/docs/css-selectors.adoc @@ -66,15 +66,77 @@ These examples show a few options: replace the element altogether. More subnode modification rules are in the section below on <>. - You can combine multiple CSS selector transforms using the `&` operator. This - is subject to some limitations detailed in the section below on <>. + is subject to some limitations detailed in the section below on <>. == Available Selectors +NOTE: You cannot chain these in the standard CSS way (e.g., `input.class-name` is not +valid). Instead, you must always put spaces between the selectors. More on this +in the section below on <>. + +Class selector: `.class-name`:: + The class selector matches any element that has `class-name` as one of its + classes. For example, you can use `.item` to match an element `
  • ...
  • `. + +Id selector: `#element-id`:: + The id selector matches any element that has `element-id` as the value of its + `id` attribute. For example, you can use `#page-header` to match an element + ``. + +Name selector: `@field-name`:: + The name selector matches any element that has `field-name` as the value of + its `name` attribute. For example, you can use `@username` to match an element + ``. + +Element selector: `element-name`:: + The element selector matches any element of type `element-name`. For example, + you can use `input` to match an element ``. + +Attribute selector: `an-attribute=a-value`:: + The attribute selector matches any element whose attribute named + `an-attribute` has the value `a-value`. For example, you can use + `ng-model=user` to match an element `
      ...
    `. + +Universal selector: `*`:: + The universal selector matches any element. + +Root selector: `^`:: + The root selector matches elements at the root level of the NodeSeq being + transformed. For example, you can use `^` to match both the `header` + and `ul` elements in the HTML `
      ...
    `. + +=== Shortened Attribute Selectors + +In addition to the above base selectors, a few selectors are provided that are +useful shortcuts for special attributes: + +Data name attribute selector: `;name-data`:: + The data name attribute selector matches any element that has `name-data` as + the value of its `data-name` attribute. For example, you can use `;user-info` + to match an element `
      ...
    `. + +Field type selectors: `:button`, `:checkbox`, `:file`, `:password`, `:radio`, `:reset`, `:submit`, `:text`:: + The field type selectors match elements whose `type` attribute is set to a + particular type. For example, `:button` will match an element ``. `:checkbox` will match an element ``. Note that this is _not_ generalized. So, for example, + `:custom-field` will _not_ match ``. Only the above + values are supported. + == Available Modification Rules == Transformation Functions -== Combining Transforms +== Combining Selectors and Transforms + +Lift's selectors are not identical to CSS selectors. They're designed for speed +rather than for being featureful, and designed in the context of a full-featured +language rather than a limited language like CSS. One key difference is in how +you combine them. In CSS, you can use `>` to select direct children, `+` for +direct siblings, etc. Lift only provides one combinator, the space: ` `. It +works just like in CSS, applying to all descendants. Com == Macros and Strings From 89cb5bb231f6b76dc2f7bede1a08956976988a77 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 4 Jun 2014 01:12:30 -0400 Subject: [PATCH 0957/1949] Added almost all CSS modification rules to docs. --- docs/css-selectors.adoc | 56 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/docs/css-selectors.adoc b/docs/css-selectors.adoc index 2c8a05d5b5..f739e38b96 100644 --- a/docs/css-selectors.adoc +++ b/docs/css-selectors.adoc @@ -127,6 +127,62 @@ Field type selectors: `:button`, `:checkbox`, `:file`, `:password`, `:radio`, `: == Available Modification Rules +Subnode modification rules indicate what the result of the transformation +function will do to the element matched by the selector. + +Set children rule: `*`:: + The transformation result will set the children of the matched element(s). For + example, `^ *` will set the children of all root elements to the results of + the transformation. + +Append to children rule: `*<` or `*+`:: + The transformation result will be appended to the children of the matched + element(s). For example, `^ *+` will append the results of the transformation + to the end of the content of all root elements. + +Prepend to children rule: `>*` or `-*`:: + The transformation result will be prepended to the children of the matched + element(s). For example, `^ *+` will prepend the results of the transformation + to the beginning of the content of all root elements. + +Surround children rule: `<*>`:: + The transformation result will produce a single element, whose children will + be set to the children of the matched element(s). For example, `^ <*>` will + take the element produced by the transformation function and copy it once for + every root element, wrapping the new element around the children of the root + elements. + +Set attribute rule: `[attribute-name]`:: + The attribute with name `attribute-name` on the matched element will have its + value set to the transformation result. For example, `^ [data-user-id]` will + set the `data-user-id` attribute of all root elements to the transformation + result. + +Append to attribute rule: `[attribute-name+]`:: + The transformation result will be appended to the end of the value of the + attribute with name `attribute-name` on the matched element with a prepended + space. For example, `^ [class+]` will append a space and then the + transformation result to the `class` attribute of all root elements. + +Remove from attribute rule: `[attribute-name!]`:: + The transformation result will be filtered from the value of the attribute + with name `attribute-name` on the matched element, provided it can be found on + its own separated by a space. For example, `^ [class!]` will remove the + class named by the transformation result from all root elements. + +Don't merge attributes rule: `!!`:: + By default, if the transformation yields a single element and the element + matched by the selector is being replaced by that result, the attributes from + the matched element are merged into the attributes of the transformation's + element. This modifier prevents that from happening. For example, by default + doing `"input" #>
    ` and applying it to `` would + yield `
    `. Doing `"input !!" #>
    ` would instead yield + `
    `. + +Lift node rule:: `^^`:: + +Lift node's children rule: `^*`:: + == Transformation Functions == Combining Selectors and Transforms From 27619e8fa0c492e78a4de0d17126116d4ee79719 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Thu, 5 Jun 2014 23:26:46 -0400 Subject: [PATCH 0958/1949] Ignore generated docs. --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 569cd5f51b..c591e59f20 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,5 @@ project/plugins/project # Pax Runner (for easy OSGi launching) runner +# Generated docs +docs/*.html From 22fefb80e53039978263eb377beec0ce339fa748 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Thu, 5 Jun 2014 23:41:50 -0400 Subject: [PATCH 0959/1949] Detailed ^^ and ^* operators in docs. --- docs/css-selectors.adoc | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/docs/css-selectors.adoc b/docs/css-selectors.adoc index f739e38b96..3d1efe059f 100644 --- a/docs/css-selectors.adoc +++ b/docs/css-selectors.adoc @@ -179,9 +179,30 @@ Don't merge attributes rule: `!!`:: yield `
    `. Doing `"input !!" #>
    ` would instead yield `
    `. -Lift node rule:: `^^`:: +Lift node rule: `^^`:: + This rule will lift the first selected element all the way to the root of the + `NodeSeq` it's being applied to. Note that the transformation result is + irrelevant in this case. Additionally, note that this only applies to the + _first_ element that matches the selector, and that it lifts it all the way to + the root of the `NodeSeq` being transformed. For example, + `".admin-user ^^" #> "ignored"`, when applied to the + markup `
    ...
    +
    ...
    `, will + produce `
    ...
    `. This is useful for + selecting among a set of template elements based on some external condition + (e.g., one template for one type of user, another template for another type of + user, etc). Lift node's children rule: `^*`:: + This rule will lift the _children_ of the first selected element all the way + to the root of the `NodeSeq` it's being applied to. As above, the + transformation result is irrelevant, only the _first_ matched element's + children are lifted, and the children are lifted all the way to the root of + the `NodeSeq` being transformed. For example, `"#power-user ^*" #> "ignored"`, + when applied to the markup + `

    Admin

    +

    Power User

    `, + will produce `

    Power User

    `. == Transformation Functions From d5805403cf74fe5a13a6b82bf296e2a088a5ecc0 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Fri, 6 Jun 2014 00:18:59 -0400 Subject: [PATCH 0960/1949] Add some more general details on CSS transforms. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This includes the fact that they’re just (NodeSeq)=>NodeSeq. --- docs/css-selectors.adoc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/css-selectors.adoc b/docs/css-selectors.adoc index 3d1efe059f..ed820738c0 100644 --- a/docs/css-selectors.adoc +++ b/docs/css-selectors.adoc @@ -19,7 +19,11 @@ rules. == Selectors and Replacement Rules -CSS Selector Transforms consist of three main components: +CSS Selector Transforms generate a function that takes in a `NodeSeq` and +transforms it according to a set of rules, producing a final `NodeSeq` with all +of the transformations applied. This means a CSS Selector Transform is +ultimately simply a function with signature `(NodeSeq)=>NodeSeq`. CSS Selector +Transforms consist of three main components: - The selector - The subnode modification rule - The transformation function From 8945612712d7abb6e976547032e3b61645d0129f Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Fri, 6 Jun 2014 00:19:05 -0400 Subject: [PATCH 0961/1949] Add information about transformation functions. --- docs/css-selectors.adoc | 71 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/docs/css-selectors.adoc b/docs/css-selectors.adoc index ed820738c0..2ce566fa98 100644 --- a/docs/css-selectors.adoc +++ b/docs/css-selectors.adoc @@ -210,6 +210,77 @@ Lift node's children rule: `^*`:: == Transformation Functions +Transformation functions specify the contents used by the modification rules to +update the `NodeSeq` that is being transformed. Note that these are always +lazily computed, so if a selector doesn't match, then its transformation +function will not be run. Strictly speaking, a transformation function need +not be a _function_---sometimes it will just be a static value. More details +below. + +NOTE: Two of the modification rules, `^^` and `^*`, ignore the result of the +transformation function; usually `"ignored"` is passed as the transformation +function in these cases. + +The transformation function can be any type `T` that has an implicit +`CanBind[T]` available. `CanBind` requires a single `apply` method with two +parameter lists, one for the `T` value and one that is the `NodeSeq` that was +matched by the selector. For example, if you invoke `"input" #> "Hello"` with +the HTML `
    `, +an instance of `CanBind[String]` is used, and is called twice; first as +`stringBind("Hello")()` and then as +`stringBind("Hello")()`. Note that a `CanBind[String]` is +already provided by default. + +Here are a few of the more interesting `CanBind` s that are supported out of the +box by Lift: + +`CanBind[Bindable]`:: + This allows you to directly use a `Mapper` or `Record` instance on the right + hand side of the transform to put its HTML representation somewhere (as + returned by `asHtml`). + +`CanBind[StringPromotable]`:: + Lift has a `StringPromotable` trait that can be used to mark objects that can + be straightforwardly promoted to a `String`. Amongst other things, by default + this includes `JsCmd` s. This allows those types of objects to be put on the + right hand side of a transform. + +`CanBind[Box[T]]` and `CanBind[Option[T]]`:: + Defined for a few types, the most important characteristic of these is that + they will return a `NodeSeq.Empty` if the `Option` or `Box` is `Empty`/`None` + or `Failure`. + +`CanBind[NodeSeq=>NodeSeq]`:: + This lets you use a full-blown transformation function. This function will + take in the element that matched the selector and provide the modification + rule with the results of the function. For example, you could clear an + element by saying `".user" #> { ns: NodeSeq => NodeSeq.Empty }`footnote:[In + fact, there is a `ClearNodes` function defined in `net.liftweb.util` that does + exactly this.]. Because CSS Selector Transforms are themselves + `NodeSeq=>NodeSeq` functions, you can nest them this way. For example, you + can say `".user" #> { ".name *" #> user.name }`. With the markup + `
  • Person

  • `, this will first select + the `li`, then pass it to the second transform which will select the `p` + and set its value to the user's name. Then the second transform will return + the `li` with the user's name set up, and the top-level transform will replace + the original, unbound `li` with the new one. + +`CanBind[Iterable[T]]`:: + This is defined for most `T` values that `CanBind` is also defined for, and + in fact it's recommended that if you provide a `CanBind` for a type `T`, you + also provide it for `Iterable[T]`. This will repeatedly run the transform + function that you specify for each `T` in the `Iterable`, concatenate the + resulting `NodeSeq` s, and return that. This makes it trivial to deal with + lists, so you can simply do something like + `".user" #> users.map { user => ".name" #> user.name }` + to map the names for all users. This will create a copy of the `.user` element + for each user, and bind their name correctly. It will also ensure that if + the matched `.user` instance has an id, only the first copy of the elements + will have that id after the transform is finished. + +There are a lot more `CanBind` s, and you can find them at the +link:docs/for/net.liftweb.util.CanBind[docs for `CanBind`]. + == Combining Selectors and Transforms Lift's selectors are not identical to CSS selectors. They're designed for speed From 09650555288b3823380aaced8c9240f1b9c63953 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Fri, 6 Jun 2014 00:29:38 -0400 Subject: [PATCH 0962/1949] Fixed a couple of markup errors in docs. --- docs/css-selectors.adoc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/css-selectors.adoc b/docs/css-selectors.adoc index 2ce566fa98..e45bbc5d5e 100644 --- a/docs/css-selectors.adoc +++ b/docs/css-selectors.adoc @@ -9,8 +9,8 @@ Lift's templating strategy is much simpler than most systems, and is aimed at cleanly and completely separating business logic from markup. Many a framework has made this claim, but Lift is one of the few to have achieved this break completely, using only HTML annotations with `data-` attributes. You can find -an overview of the full Lift CSS templating strategy in the [Lift templating -guide](). +an overview of the full Lift CSS templating strategy in the +link:templating-guide[Lift templating guide]. This document is a reference on the underpinnings of Lift templating, the CSS Selector Transforms. These are used in Lift code to transform a block of HTML @@ -254,7 +254,7 @@ box by Lift: This lets you use a full-blown transformation function. This function will take in the element that matched the selector and provide the modification rule with the results of the function. For example, you could clear an - element by saying `".user" #> { ns: NodeSeq => NodeSeq.Empty }`footnote:[In + element by saying `".user" #> { ns: NodeSeq => NodeSeq.Empty }` footnote:[In fact, there is a `ClearNodes` function defined in `net.liftweb.util` that does exactly this.]. Because CSS Selector Transforms are themselves `NodeSeq=>NodeSeq` functions, you can nest them this way. For example, you From 6b01d37d743797d73eaea2cf23687307c8de211d Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 11 Jun 2014 23:09:14 -0400 Subject: [PATCH 0963/1949] Add dexy config to output stuff into target/docs. --- .gitignore | 4 ++-- dexy.conf | 1 + dexy.yaml | 1 + dexyplugin.yaml | 3 +++ 4 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 dexy.conf create mode 100644 dexy.yaml create mode 100644 dexyplugin.yaml diff --git a/.gitignore b/.gitignore index c591e59f20..848d817ced 100644 --- a/.gitignore +++ b/.gitignore @@ -56,5 +56,5 @@ project/plugins/project # Pax Runner (for easy OSGi launching) runner -# Generated docs -docs/*.html +# Doc generation +.dexy diff --git a/dexy.conf b/dexy.conf new file mode 100644 index 0000000000..4fde382cc2 --- /dev/null +++ b/dexy.conf @@ -0,0 +1 @@ +outputroot: docs diff --git a/dexy.yaml b/dexy.yaml new file mode 100644 index 0000000000..119321851d --- /dev/null +++ b/dexy.yaml @@ -0,0 +1 @@ +docs/*.adoc|asciidoctor diff --git a/dexyplugin.yaml b/dexyplugin.yaml new file mode 100644 index 0000000000..c0a2b50b8f --- /dev/null +++ b/dexyplugin.yaml @@ -0,0 +1,3 @@ +reporter:output: + dir: target/docs + readme-filename: None From d19c5f6d742be8a743d76563bf69a96a58e1d439 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Fri, 13 Jun 2014 00:22:24 -0400 Subject: [PATCH 0964/1949] Add and annotate sample CSS transform input/output. --- docs/css-selectors.adoc | 79 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/docs/css-selectors.adoc b/docs/css-selectors.adoc index e45bbc5d5e..8b560f9730 100644 --- a/docs/css-selectors.adoc +++ b/docs/css-selectors.adoc @@ -35,32 +35,111 @@ Transforms consist of three main components: The details of each are provided below, but first here ere are some simple examples of transforms that you can write with links: +[.interactive.selectors] .Replace the contents of all `a` elements with the text "Mozilla" ==== +[.input] +```html +
    John Doe
    + +``` +[.selector] ```scala "a *" #> "Mozilla" ``` +[.output] +```html +
    John Doe
    + +``` ==== +[.interactive.selectors] .Make all links point to Mozilla ==== +[.input] +```html +
    John Doe
    + +``` ```scala "a [href]" #> "http://mozilla.org" ``` +[.output] +```html +
    John Doe
    + +``` ==== +[.interactive.selectors] .Replace all elements with class `name` with a user's name ==== +[.input] +```html +
    John Doe
    + +``` ```scala ".name" #> user.name ``` +[.output] +```html +
    Antonio Salazar Cardozo
    + +``` ==== +[.interactive.selectors] .Do all three of the previous things at once ==== +[.input] +```html +
    John Doe
    + +``` +```scala "a *" #> "Mozilla" & "a [href]" #> "http://mozilla.org" & ".name" #> user.name +``` +[.output] +```html +
    Antonio Salazar Cardozo
    + +``` ==== These examples show a few options: From c255fb83eccc89fe4591d9355dbc33a535288f75 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Fri, 13 Jun 2014 00:26:36 -0400 Subject: [PATCH 0965/1949] Do basic example extraction from dexy using Ruby script. Not doing anything with the extracted examples yet, but this gives us the starting point from which we can look at how we can read in a file, find the examples in it, and run them. Ultimately, we're going to want to do this all in Scala with CSS selector transforms, because hey, why not. --- dexy.yaml | 9 ++++++++- docs/scripts/extract-contents.rb | 21 +++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 docs/scripts/extract-contents.rb diff --git a/dexy.yaml b/dexy.yaml index 119321851d..eb25e41280 100644 --- a/dexy.yaml +++ b/dexy.yaml @@ -1 +1,8 @@ -docs/*.adoc|asciidoctor + - html-docs: + - docs/*.adoc|asciidoctor + + - example-verification: + - docs/scripts/extract-contents.rb|rb: + - rb: + timeout: 500 + - html-docs diff --git a/docs/scripts/extract-contents.rb b/docs/scripts/extract-contents.rb new file mode 100644 index 0000000000..94ab9ad4e6 --- /dev/null +++ b/docs/scripts/extract-contents.rb @@ -0,0 +1,21 @@ +require 'rubygems' +require 'nokogiri' + +Dir.chdir '..' do + Dir.glob('*.html').each do |html_file| + contents = File.read(html_file) + + html = Nokogiri::HTML(contents) + + examples = + html.css('.exampleblock').collect do |example| + { + input: example.css('.input code.html').text, + selector: example.css('.selector code.scala').text, + output: example.css('.output code.html').text + } + end + + puts examples + end +end From e571055c800d420a35475edd0515c5cf63e08ac8 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 14 Jun 2014 00:33:48 -0400 Subject: [PATCH 0966/1949] Add some info on combinint CSS selector transform. --- docs/css-selectors.adoc | 58 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/docs/css-selectors.adoc b/docs/css-selectors.adoc index 8b560f9730..4d7448f09a 100644 --- a/docs/css-selectors.adoc +++ b/docs/css-selectors.adoc @@ -367,7 +367,63 @@ rather than for being featureful, and designed in the context of a full-featured language rather than a limited language like CSS. One key difference is in how you combine them. In CSS, you can use `>` to select direct children, `+` for direct siblings, etc. Lift only provides one combinator, the space: ` `. It -works just like in CSS, applying to all descendants. Com +works just like in CSS, applying to all descendants. So you can set up a selector +`.user-form input [value]` and it will for setting the `href` attribute of all +`input` elements that have some ancestorwith class `user-form` . + +Notably, you cannot select `form.user input [href]`, because you cannot check +multiple selectors on a single element. In practice, this is rarely needed for +snippets because the snippet itself will typically be attached to the element +that you would usually use a more complex selector to identify. + +=== Combining Transforms + +You may want to apply more than one transform to a single `NodeSeq`. Indeed, +this is a fairly common thing to do in snippets. The simplest way of doing this +is to pass the result of each transformation in turn through the next +transform. For example, if you wanted to do both `"a *" #> "Mozilla"` and +`"a [href]" #> "https://mozilla.org"`, you could do: + +```scala +val textReplaced = ("a *" #> "Mozilla") apply nodes +val final result = ("a [href]" #> "https://mozilla.org") apply textReplaced +``` + +Scala itself provides a function composition helper that lets us chain a set +of functions into a single function that runs through all of them: `andThen`. +With this, we can do: + +```scala +("a *" #> "Mozilla" andThen + "a [href]" #> "https://mozilla.org") apply nodes +``` + +And get the same result. + +However, Lift provides one more little trick, the `&` operator. When CSS +Selector Transforms are combined via `andThen`, each transform that runs +potentially has to go through the entire set of input nodes to see where its +transformations should apply. `&` does something a little different: instead of +chaining the functions, it creates one big function that goes through the input +nodes a single time, checking at each point which of the combined transforms +should be applied and then applying them. So, you can do: + +```scala +("a *" #> "Mozilla" & + "a [href]" #> "https://mozilla.org") apply nodes +``` + +Beware, however, as `&` is not the same as `andThen`. To do this trickery, Lift +will only transform a part of a node once, and it won't revisit it. Two +transformations that apply to the same attribute for the same element, for +example, will not both be applied. Additionally, if your transformation applies +to the body of an element, like `a *`, the new children of the element will +_not_ be transformed. Additionally, if you replace the element itself, e.g. +with the selector `a`, none of the other transforms for that element will run. + +Thus, you will occasionally find yourself using `&` together with `andThen`; in +general you should default to `&` and switch to `andThen` when you need to in +order to apply a transform to the results of the previous one. == Macros and Strings From ff755b5ce375b41c31dab3c31b309b8c560c2636 Mon Sep 17 00:00:00 2001 From: Tim Nelson Date: Sat, 14 Jun 2014 08:44:26 -0500 Subject: [PATCH 0967/1949] Remove Mongo.defineDbAuth function that takes a MongoClient as an argument and updated deprecation message for the other defineDbAuth functions. When the new code for using MongoClient was added the functions that take a Mongo instance as argument were deprecated, including defineDbAuth. However, a subsequent update in the mongo-java-driver also deprecated DB.authenticate. Replaced by passing credentials in via MongoClient. So, I removed the new defineDbAuth function. --- .../src/main/scala/net/liftweb/mongodb/Mongo.scala | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/Mongo.scala b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/Mongo.scala index 086d634292..5c0ff85ef6 100644 --- a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/Mongo.scala +++ b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/Mongo.scala @@ -97,7 +97,7 @@ object MongoDB { /* * Define and authenticate a Mongo db */ - @deprecated("Use defineDbAuth that takes a MongoClient instance.", "2.6") + @deprecated("use MongoClient to supply credentials.", "2.6") def defineDbAuth(name: ConnectionIdentifier, address: MongoAddress, username: String, password: String) { if (!address.db.authenticate(username, password.toCharArray)) throw new MongoException("Authorization failed: "+address.toString) @@ -108,7 +108,7 @@ object MongoDB { /* * Define and authenticate a Mongo db using a Mongo instance. */ - @deprecated("Use defineDbAuth that takes a MongoClient instance.", "2.6") + @deprecated("use MongoClient to supply credentials.", "2.6") def defineDbAuth(name: ConnectionIdentifier, mngo: Mongo, dbName: String, username: String, password: String) { if (!mngo.getDB(dbName).authenticate(username, password.toCharArray)) throw new MongoException("Authorization failed: "+mngo.toString) @@ -116,16 +116,6 @@ object MongoDB { dbs.put(name, (mngo, dbName)) } - /** - * Define and authenticate a Mongo db using a MongoClient instance. - */ - def defineDbAuth(name: ConnectionIdentifier, mngo: MongoClient, dbName: String, username: String, password: String) { - if (!mngo.getDB(dbName).authenticate(username, password.toCharArray)) - throw new MongoException("Authorization failed: "+mngo.toString) - - dbs.put(name, (mngo, dbName)) - } - /* * Get a DB reference */ From 1243bf6c7c8bbb76c34df257c29162560ba1499e Mon Sep 17 00:00:00 2001 From: Tim Nelson Date: Sat, 14 Jun 2014 09:40:40 -0500 Subject: [PATCH 0968/1949] Replace MongoMeta.ensureIndex with createIndex. --- .../scala/net/liftweb/mongodb/MongoMeta.scala | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/MongoMeta.scala b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/MongoMeta.scala index 4a4df5ecdf..b5e0c559fb 100644 --- a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/MongoMeta.scala +++ b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/MongoMeta.scala @@ -124,12 +124,14 @@ trait MongoMeta[BaseDocument] extends JsonFormats { /* * Ensure an index exists */ + @deprecated("use createIndex(JObject) instead.", "2.6") def ensureIndex(keys: JObject): Unit = useColl { coll => coll.ensureIndex(JObjectParser.parse(keys)) } /* * Ensure an index exists and make unique */ + @deprecated("use createIndex(JObject, Boolean) instead.", "2.6") def ensureIndex(keys: JObject, unique: Boolean): Unit = { val options = new BasicDBObject if (unique) options.put("unique", true) @@ -138,14 +140,28 @@ trait MongoMeta[BaseDocument] extends JsonFormats { } } + def createIndex(keys: JObject, unique: Boolean = false): Unit = { + val options = new BasicDBObject + if (unique) options.put("unique", true) + useColl { coll => + coll.createIndex(JObjectParser.parse(keys), options) + } + } + /* * Ensure an index exists with options */ + @deprecated("use createIndex(JObject, JObject) instead.", "2.6") def ensureIndex(keys: JObject, opts: JObject): Unit = useColl { coll => coll.ensureIndex(JObjectParser.parse(keys), JObjectParser.parse(opts)) } + def createIndex(keys: JObject, opts: JObject): Unit = + useColl { coll => + coll.createIndex(JObjectParser.parse(keys), JObjectParser.parse(opts)) + } + /* * Update document with a DBObject query using the given Mongo instance. */ From e6bc89d0cd71b603a5791e25ff811e68ff6f5ef9 Mon Sep 17 00:00:00 2001 From: Tim Nelson Date: Sat, 14 Jun 2014 10:19:14 -0500 Subject: [PATCH 0969/1949] Fixes #1573 - Failing Mongo Specs due to error message changes. --- .../src/test/scala/net/liftweb/mongodb/MongoDirectSpec.scala | 2 +- .../scala/net/liftweb/mongodb/MongoDocumentExamplesSpec.scala | 2 +- .../test/scala/net/liftweb/mongodb/QueryExamplesSpec.scala | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDirectSpec.scala b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDirectSpec.scala index 87dec19362..b3e10ac340 100644 --- a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDirectSpec.scala +++ b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDirectSpec.scala @@ -229,7 +229,7 @@ class MongoDirectSpec extends Specification with MongoTestKit { coll.save(doc) db.getLastError.get("err") must beNull coll.save(doc2) - db.getLastError.get("err").toString must startWith("E11000 duplicate key error index") + db.getLastError.get("err").toString must contain("E11000 duplicate key error index") coll.save(doc3) db.getLastError.get("err") must beNull diff --git a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDocumentExamplesSpec.scala b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDocumentExamplesSpec.scala index 4566ff7484..01cc55f882 100644 --- a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDocumentExamplesSpec.scala +++ b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDocumentExamplesSpec.scala @@ -492,7 +492,7 @@ class MongoDocumentExamplesSpec extends Specification with MongoTestKit { SessCollection.save(tc, db) db.getLastError.get("err") must beNull SessCollection.save(tc2, db) - db.getLastError.get("err").toString must startWith("E11000 duplicate key error index") + db.getLastError.get("err").toString must contain("E11000 duplicate key error index") SessCollection.save(tc3, db) db.getLastError.get("err") must beNull diff --git a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/QueryExamplesSpec.scala b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/QueryExamplesSpec.scala index 0ddfb90f9e..0a640b23b4 100644 --- a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/QueryExamplesSpec.scala +++ b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/QueryExamplesSpec.scala @@ -1,5 +1,5 @@ /* - * Copyright 2011 WorldWide Conferencing, LLC + * Copyright 2011-2014 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. @@ -33,7 +33,7 @@ package queryexamplesfixtures { object Person extends MongoDocumentMeta[Person] { override def formats = allFormats // index name - ensureIndex(("name" -> 1)) + createIndex(("name" -> 1)) // implicit formats already exists def findAllBornAfter(dt: Date) = findAll(("birthDate" -> ("$gt" -> dt))) From 6d0d742b322a4fa312f625714c4b0653b47f8a80 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 15 Jun 2014 01:04:02 -0400 Subject: [PATCH 0970/1949] Add documentation helpers project. --- .../liftweb/documentation/ExtractCssSelectorExamples.scala | 6 ++++++ project/Build.scala | 5 +++++ 2 files changed, 11 insertions(+) create mode 100644 core/documentation-helpers/src/main/scala/net/liftweb/documentation/ExtractCssSelectorExamples.scala diff --git a/core/documentation-helpers/src/main/scala/net/liftweb/documentation/ExtractCssSelectorExamples.scala b/core/documentation-helpers/src/main/scala/net/liftweb/documentation/ExtractCssSelectorExamples.scala new file mode 100644 index 0000000000..377541358d --- /dev/null +++ b/core/documentation-helpers/src/main/scala/net/liftweb/documentation/ExtractCssSelectorExamples.scala @@ -0,0 +1,6 @@ +package net.liftweb +package documentation + +object ExtractCssSelectorExamples extends App { + println("OHAI") +} diff --git a/project/Build.scala b/project/Build.scala index 6d23f498b4..bddaf72264 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -62,6 +62,11 @@ object BuildDef extends Build { parallelExecution in Test := false, libraryDependencies <++= scalaVersion { sv => Seq(scalap(sv), paranamer) }) + lazy val documentationHelpers = + coreProject("documentation-helpers") + .settings(description := "Documentation Helpers") + .dependsOn(util) + lazy val json_scalaz = coreProject("json-scalaz") .dependsOn(json) From 8b8d3b6f407443b2ed4ce7243c9618e5c3688adf Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 15 Jun 2014 01:10:08 -0400 Subject: [PATCH 0971/1949] Add extract-css-selector-examples bash runner. We use it to fire off the documentation helpers' ExtractCssSelectorExamples application. Currently it's just a stub, more to come on that front. --- dexy.yaml | 4 ++-- docs/scripts/extract-css-selector-examples.sh | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 docs/scripts/extract-css-selector-examples.sh diff --git a/dexy.yaml b/dexy.yaml index eb25e41280..053f4f2008 100644 --- a/dexy.yaml +++ b/dexy.yaml @@ -2,7 +2,7 @@ - docs/*.adoc|asciidoctor - example-verification: - - docs/scripts/extract-contents.rb|rb: - - rb: + - docs/scripts/extract-css-selector-examples.sh|bash: + - sh: timeout: 500 - html-docs diff --git a/docs/scripts/extract-css-selector-examples.sh b/docs/scripts/extract-css-selector-examples.sh new file mode 100644 index 0000000000..cfb02400a8 --- /dev/null +++ b/docs/scripts/extract-css-selector-examples.sh @@ -0,0 +1,15 @@ +WORK_DIR=`pwd` + +# If we're running in dexy, take us to the project top-level. +if [[ "$WORK_DIR" == *.dexy* ]] +then + until [ "$(basename $WORK_DIR)" == ".dexy" ] + do + WORK_DIR=$(dirname $WORK_DIR) + done +fi + +cd $(dirname $WORK_DIR) + +sbt "project lift-documentation-helpers" \ + "run-main net.liftweb.documentation.ExtractCssSelectorExamples" From 9db144bdba5799aba3042fa95ef88eca965ec347 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 15 Jun 2014 22:13:07 -0400 Subject: [PATCH 0972/1949] Add selector class to selector examples. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We’re going to be using this to extract the selectors. --- docs/css-selectors.adoc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/css-selectors.adoc b/docs/css-selectors.adoc index 4d7448f09a..d4c4392618 100644 --- a/docs/css-selectors.adoc +++ b/docs/css-selectors.adoc @@ -74,6 +74,7 @@ examples of transforms that you can write with links:
  • Apple
  • ``` +[.selector] ```scala "a [href]" #> "http://mozilla.org" ``` @@ -100,6 +101,7 @@ examples of transforms that you can write with links:
  • Apple
  • ``` +[.selector] ```scala ".name" #> user.name ``` @@ -126,6 +128,7 @@ examples of transforms that you can write with links:
  • Apple
  • ``` +[.selector] ```scala "a *" #> "Mozilla" & "a [href]" #> "http://mozilla.org" & From 808fb976540a897bf73b2230112d37ca4038f15b Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 15 Jun 2014 22:13:42 -0400 Subject: [PATCH 0973/1949] Extract CSS selector examples. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We’re not doing anything with them yet, but we’re extracting the input, output, and selector function. --- .../ExtractCssSelectorExamples.scala | 133 +++++++++++++++++- 1 file changed, 132 insertions(+), 1 deletion(-) diff --git a/core/documentation-helpers/src/main/scala/net/liftweb/documentation/ExtractCssSelectorExamples.scala b/core/documentation-helpers/src/main/scala/net/liftweb/documentation/ExtractCssSelectorExamples.scala index 377541358d..46aecff4d0 100644 --- a/core/documentation-helpers/src/main/scala/net/liftweb/documentation/ExtractCssSelectorExamples.scala +++ b/core/documentation-helpers/src/main/scala/net/liftweb/documentation/ExtractCssSelectorExamples.scala @@ -1,6 +1,137 @@ package net.liftweb package documentation +import java.io.File + +import scala.io.Source +import scala.xml.{Elem,NodeSeq} + +import common._ +import util.Html5 +import util.Helpers._ + +sealed trait ExamplePart +case class ExampleInput(input: String) extends ExamplePart +case class ExampleFunction(function: String) extends ExamplePart +case class ExampleOutput(output: String) extends ExamplePart + +case class FileContents(filename: String, contents: String) +case class ExampleContents(filename: String, exampleLabel: String, exampleParts: List[ExamplePart]) + object ExtractCssSelectorExamples extends App { - println("OHAI") + private def contentsToProcess(basePath: String): Box[List[FileContents]] = { + val docsFile = new File(s"$basePath") + + for { + docsDir <- + ((Full(docsFile) + .filter(_.exists) ?~ s"'$docsFile' should be a directory, but does not exist.") + .filter(_.isDirectory) ?~ s"'$docsFile' should be a directory, not a file.") + } yield { + for { + file <- docsDir.listFiles.toList + if file.getName.endsWith(".html") + fileContents <- tryo(Source.fromFile(file).mkString) + } yield { + FileContents(file.getName, fileContents) + } + } + } + + private def extractPart(partBuilder: (String)=>ExamplePart)(ns: NodeSeq): Option[ExamplePart] = { + var part: Option[ExamplePart] = None + + val partExtractor = + "code" #> { ns: NodeSeq => ns match { + case codeElement: Elem if codeElement.label == "code" => + part = Some(partBuilder(codeElement.text)) + codeElement + case other => other + } } + + partExtractor(ns) + + part + } + + private def hasClass_?(element: Elem, className: String) = { + element.attribute("class") match { + case Some(thing) => + charSplit(thing.text, ' ').exists(_ == className) + case _ => + false + } + } + + private def extractExamplesFromContents(fileContents: FileContents): List[ExampleContents] = { + Html5.parse(fileContents.contents).toList.flatMap { html => + var exampleContents = List[ExampleContents]() + + val contentExtractor = + ".selectors" #> { exampleNodes: NodeSeq => + var parts = List[ExamplePart]() + var exampleLabel = "No label" + + var labelExtractor = + ".title" #> { title: NodeSeq => title match { + case titleElement: Elem if titleElement.label == "div" => + exampleLabel = titleElement.text + + titleElement + case other => other + }} + val partExtractor = + ".listingblock" #> { part: NodeSeq => + var specializedPartExtractor = + part match { + case inputBlock: Elem if hasClass_?(inputBlock, "input") => + Some(extractPart(ExampleInput(_)) _) + case selectorBlock: Elem if hasClass_?(selectorBlock, "selector") => + Some(extractPart(ExampleFunction(_)) _) + case outputBlock: Elem if hasClass_?(outputBlock, "output") => + Some(extractPart(ExampleOutput(_)) _) + case _ => None + } + + for { + extractor <- specializedPartExtractor + extractedPart <- extractor(part) + } { + parts ::= extractedPart + } + + part + } + + (labelExtractor & partExtractor)(exampleNodes) + + exampleContents ::= ExampleContents(fileContents.filename, exampleLabel, parts.reverse) + + exampleNodes + } + + contentExtractor(html) + + exampleContents.reverse + } + } + + if (args.length < 1) { + Console.err.println( + "Expected one argument: the base directory of the Lift project." + ) + } else { + val thingies = + for { + extractedContents <- contentsToProcess(args(0)) + } yield { + extractedContents.map(extractExamplesFromContents _) + } + + thingies match { + case Failure(message, _, _) => Console.err.println(message) + case Full(thingie) => println(thingie) + case _ => Console.err.println("Unknown error.") + } + } } From cc1f64af2a224172c6d66716971a70415e680e39 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 15 Jun 2014 22:14:47 -0400 Subject: [PATCH 0974/1949] Fix dexy invocation of selector example extractor. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We now want to be passed the directory that the built HTML is in, and we also have to compute it. Additionally, we make use of the DEXY_ROOT environment variable that dexy sets for us to know the project’s root directory for proper invocation of sbt (we were computing this in a somewhat brittle way before). --- docs/scripts/extract-css-selector-examples.sh | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) mode change 100644 => 100755 docs/scripts/extract-css-selector-examples.sh diff --git a/docs/scripts/extract-css-selector-examples.sh b/docs/scripts/extract-css-selector-examples.sh old mode 100644 new mode 100755 index cfb02400a8..b109985231 --- a/docs/scripts/extract-css-selector-examples.sh +++ b/docs/scripts/extract-css-selector-examples.sh @@ -1,15 +1,15 @@ WORK_DIR=`pwd` +HTML_DIR="$WORK_DIR" -# If we're running in dexy, take us to the project top-level. -if [[ "$WORK_DIR" == *.dexy* ]] +if [[ ! -z "$DEXY_ROOT" ]] then - until [ "$(basename $WORK_DIR)" == ".dexy" ] + cd $DEXY_ROOT + + until [ "$(basename $HTML_DIR)" == "docs" ] do - WORK_DIR=$(dirname $WORK_DIR) + HTML_DIR=$(dirname $HTML_DIR) done fi -cd $(dirname $WORK_DIR) - -sbt "project lift-documentation-helpers" \ - "run-main net.liftweb.documentation.ExtractCssSelectorExamples" +sbt -Dsbt.log.noformat=true "project lift-documentation-helpers" \ + "run-main net.liftweb.documentation.ExtractCssSelectorExamples \"$HTML_DIR\"" From b9f2c42e9fc7465628386e858a08968751bc13dc Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 15 Jun 2014 22:15:30 -0400 Subject: [PATCH 0975/1949] Drop extract-contents Ruby prototype. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It was just getting started, but we’ll be doing things with Scala and Lift thanks ;) --- docs/scripts/extract-contents.rb | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 docs/scripts/extract-contents.rb diff --git a/docs/scripts/extract-contents.rb b/docs/scripts/extract-contents.rb deleted file mode 100644 index 94ab9ad4e6..0000000000 --- a/docs/scripts/extract-contents.rb +++ /dev/null @@ -1,21 +0,0 @@ -require 'rubygems' -require 'nokogiri' - -Dir.chdir '..' do - Dir.glob('*.html').each do |html_file| - contents = File.read(html_file) - - html = Nokogiri::HTML(contents) - - examples = - html.css('.exampleblock').collect do |example| - { - input: example.css('.input code.html').text, - selector: example.css('.selector code.scala').text, - output: example.css('.output code.html').text - } - end - - puts examples - end -end From 919d9d1303c7ddf4e49f75cd8d187ceef3a49618 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 16 Jun 2014 23:24:38 -0400 Subject: [PATCH 0976/1949] Generate tests from documentation examples. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We generate them as files in the documentation-helpers sbt sub-project, which can then be used to run them. Currently the examples don’t compile yet, more to come on that front. --- .../ExtractCssSelectorExamples.scala | 69 ++++++++++++++++--- docs/scripts/extract-css-selector-examples.sh | 2 +- 2 files changed, 62 insertions(+), 9 deletions(-) diff --git a/core/documentation-helpers/src/main/scala/net/liftweb/documentation/ExtractCssSelectorExamples.scala b/core/documentation-helpers/src/main/scala/net/liftweb/documentation/ExtractCssSelectorExamples.scala index 46aecff4d0..acfcc8610f 100644 --- a/core/documentation-helpers/src/main/scala/net/liftweb/documentation/ExtractCssSelectorExamples.scala +++ b/core/documentation-helpers/src/main/scala/net/liftweb/documentation/ExtractCssSelectorExamples.scala @@ -1,7 +1,7 @@ package net.liftweb package documentation -import java.io.File +import java.io.{File,PrintStream} import scala.io.Source import scala.xml.{Elem,NodeSeq} @@ -33,7 +33,7 @@ object ExtractCssSelectorExamples extends App { if file.getName.endsWith(".html") fileContents <- tryo(Source.fromFile(file).mkString) } yield { - FileContents(file.getName, fileContents) + FileContents(file.getName.replace('.', '-'), fileContents) } } } @@ -116,21 +116,74 @@ object ExtractCssSelectorExamples extends App { } } - if (args.length < 1) { + if (args.length < 2) { Console.err.println( - "Expected one argument: the base directory of the Lift project." + "Expected two arguments: the base directory of generated HTML and the base directory of the Lift project." ) } else { - val thingies = + val examples = for { extractedContents <- contentsToProcess(args(0)) } yield { - extractedContents.map(extractExamplesFromContents _) + extractedContents.flatMap(extractExamplesFromContents _) } - thingies match { + examples match { + case Full(exampleContents) => + val testPath = s"${args(1)}/core/documentation-helpers/src/test/scala/net/liftweb/documentation" + (new File(testPath)).mkdirs + + for { + (normalizedFilename, contents) <- exampleContents.groupBy(_.filename) + } { + val filename = camelify(normalizedFilename.replace('-','_')) + + val examples = + for { + ExampleContents(_, exampleLabel, exampleParts) <- contents + i <- (0 to (exampleParts.length / 3)) + ExampleInput(input) <- exampleParts.lift(i) + ExampleFunction(function) <- exampleParts.lift(i + 1) + ExampleOutput(output) <- exampleParts.lift(i + 2) + } yield { + s""" + | ""\"$exampleLabel""\" in { + | val input = Html5.parse(""\"$input""\") + | val function = $function + | val output = Html5.parse(""\"$output""\") + | + | input.map(function) must_== output + | }""".stripMargin('|') + } + + val file = new File(s"$testPath/${filename}Test.scala") + + var stream: PrintStream = null + try { + stream = new PrintStream(file) + stream.println(s""" + |package net.liftweb + |package documentation + | + |import scala.xml._ + | + |import org.specs2.mutable.Specification + | + |import common._ + |import util.Html5 + |import util.Helpers._ + | + |object $filename extends Specification { + | ""\"${filename} examples""\" should { + |${examples.mkString("\n")} + | } + |}""".stripMargin('|')) + } finally { + Option(stream).map(_.close) + } + } + case Failure(message, _, _) => Console.err.println(message) - case Full(thingie) => println(thingie) case _ => Console.err.println("Unknown error.") } } diff --git a/docs/scripts/extract-css-selector-examples.sh b/docs/scripts/extract-css-selector-examples.sh index b109985231..60c569f704 100755 --- a/docs/scripts/extract-css-selector-examples.sh +++ b/docs/scripts/extract-css-selector-examples.sh @@ -12,4 +12,4 @@ then fi sbt -Dsbt.log.noformat=true "project lift-documentation-helpers" \ - "run-main net.liftweb.documentation.ExtractCssSelectorExamples \"$HTML_DIR\"" + "run-main net.liftweb.documentation.ExtractCssSelectorExamples \"$HTML_DIR\" \"$DEXY_ROOT\"" From 296ddffdf2af33787d37bfbe74d0134ef23fef5e Mon Sep 17 00:00:00 2001 From: Diego Medina Date: Mon, 16 Jun 2014 23:58:31 -0400 Subject: [PATCH 0977/1949] Added squeryl back to the lift build Updated to latest squeryl version Minor specs2 change so it compiles undder latest specs2 version --- build.sbt | 6 +++--- .../liftweb/squerylrecord/SquerylRecordSpec.scala | 12 +++++++++++- project/Build.scala | 4 ++-- project/Dependencies.scala | 2 +- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/build.sbt b/build.sbt index ddc4822737..c37a3c68be 100644 --- a/build.sbt +++ b/build.sbt @@ -14,7 +14,7 @@ organizationName in ThisBuild := "WorldWide Conferencing, LLC" scalaVersion in ThisBuild := "2.10.4" -crossScalaVersions in ThisBuild := Seq("2.11.0", "2.10.4", "2.9.2", "2.9.1-1", "2.9.1") +crossScalaVersions in ThisBuild := Seq("2.11.1", "2.10.4", "2.9.2", "2.9.1-1", "2.9.1") libraryDependencies in ThisBuild <++= scalaVersion {sv => Seq(specs2(sv), scalacheck, scalatest(sv)) } @@ -32,6 +32,6 @@ credentials in ThisBuild <+= state map { s => Credentials(BuildPaths.getGlobalSe initialize <<= (name, version, scalaVersion) apply printLogo resolvers in ThisBuild ++= Seq( - "snapshots" at "http://oss.sonatype.org/content/repositories/snapshots", - "releases" at "http://oss.sonatype.org/content/repositories/releases" + "snapshots" at "https://oss.sonatype.org/content/repositories/snapshots", + "releases" at "https://oss.sonatype.org/content/repositories/releases" ) diff --git a/persistence/squeryl-record/src/test/scala/net/liftweb/squerylrecord/SquerylRecordSpec.scala b/persistence/squeryl-record/src/test/scala/net/liftweb/squerylrecord/SquerylRecordSpec.scala index a5ffb46966..2aba6c79f0 100644 --- a/persistence/squeryl-record/src/test/scala/net/liftweb/squerylrecord/SquerylRecordSpec.scala +++ b/persistence/squeryl-record/src/test/scala/net/liftweb/squerylrecord/SquerylRecordSpec.scala @@ -22,7 +22,7 @@ import org.squeryl.dsl.DateExpression import org.specs2.mutable.Specification import org.specs2.specification.AroundExample -import org.specs2.execute.Result +import org.specs2.execute.{ AsResult , Result } import record.{ BaseField, Record } import record.field._ @@ -44,6 +44,7 @@ class SquerylRecordSpec extends Specification with AroundExample { sequential lazy val session = new LiftSession("", Helpers.randomString(20), Empty) + // One of these is for specs2 2.x, the other for specs2 1.x protected def around[T <% Result](t: =>T) = { S.initIfUninitted(session) { DBHelper.initSquerylRecordWithInMemoryDB() @@ -52,6 +53,14 @@ class SquerylRecordSpec extends Specification with AroundExample { } } + protected def around[T : AsResult](t: =>T) = { + S.initIfUninitted(session) { + DBHelper.initSquerylRecordWithInMemoryDB() + DBHelper.createSchema() + AsResult(t) + } + } + "SquerylRecord" should { "load record by ID" in { transaction { @@ -379,6 +388,7 @@ class SquerylRecordSpec extends Specification with AroundExample { val company = from(companies)(company => select(company)).page(0, 1).single company.allFields map { f => f.dirty_? must_== false } + success } } class ToChar(d: DateExpression[Timestamp], e: StringExpression[String], m: OutMapper[String]) diff --git a/project/Build.scala b/project/Build.scala index 1cf51d6eeb..754c6d3f00 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -44,7 +44,7 @@ object BuildDef extends Build { lazy val core: Seq[ProjectReference] = Seq(common, actor, markdown, json, json_scalaz7, json_ext, util) - lazy val pre_211_project: Seq[ProjectReference] = Seq(json_scalaz, squeryl_record) + lazy val pre_211_project: Seq[ProjectReference] = Seq(json_scalaz) lazy val common = coreProject("common") @@ -139,7 +139,7 @@ object BuildDef extends Build { // Persistence Projects // -------------------- lazy val persistence: Seq[ProjectReference] = - Seq(db, proto, jpa, mapper, record, mongodb, mongodb_record, ldap) + Seq(db, proto, jpa, mapper, record, mongodb, mongodb_record, ldap, squeryl_record) lazy val db = persistenceProject("db") diff --git a/project/Dependencies.scala b/project/Dependencies.scala index f712a916cf..3b262d1142 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -53,7 +53,7 @@ object Dependencies { lazy val scalaz_core: ModuleMap = sv => scalazGroup(sv) % "scalaz-core" % scalazVersion(sv) cross CVMappingAll lazy val scalaz7_core: ModuleMap = sv => scalazGroup(sv) % "scalaz-core" % scalaz7Version(sv) cross CVMappingScalaz lazy val slf4j_api = "org.slf4j" % "slf4j-api" % slf4jVersion - lazy val squeryl = "org.squeryl" % "squeryl" % "0.9.5-6" cross CVMappingAll + lazy val squeryl = "org.squeryl" % "squeryl" % "0.9.5-7" cross CVMappingAll lazy val scala_xml = "org.scala-lang.modules" %% "scala-xml" % "1.0.1" lazy val scala_parser = "org.scala-lang.modules" %% "scala-parser-combinators" % "1.0.1" From f0f02cd67bf7a05f790fcd8999e77c5ea74db3e2 Mon Sep 17 00:00:00 2001 From: Diego Medina Date: Tue, 17 Jun 2014 00:03:41 -0400 Subject: [PATCH 0978/1949] updated unsafePublish to using sbt 0.13.x --- unsafePublishLift.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/unsafePublishLift.sh b/unsafePublishLift.sh index 74949f632e..4e2468cba2 100755 --- a/unsafePublishLift.sh +++ b/unsafePublishLift.sh @@ -157,7 +157,7 @@ for MODULE in framework ; do # Do a separate build for each configured Scala version so we don't blow the heap for SCALA_VERSION in $(grep crossScalaVersions build.sbt | cut -d '(' -f 2 | sed s/[,\)\"]//g ); do echo -n " Building against Scala ${SCALA_VERSION}..." - if ! ./liftsh ++${SCALA_VERSION} clean update test publish-signed >> ${BUILDLOG} ; then + if ! ./liftsh ++${SCALA_VERSION} clean update test publishSigned >> ${BUILDLOG} ; then echo "failed! See build log for details" exit fi @@ -169,7 +169,7 @@ for MODULE in framework ; do done echo -e "\n\nRelease complete!" -echo -e "\n\nPlease update the lift_sbt_2.5 templates!" +echo -e "\n\nPlease update the lift_sbt_2.6 templates!" echo -e "\n\nand write something about this release on the liftweb.net site." From 2d32be05e5706a8010b6f8d43b1af5301056c673 Mon Sep 17 00:00:00 2001 From: Diego Medina Date: Wed, 18 Jun 2014 07:37:45 -0400 Subject: [PATCH 0979/1949] avoid double tag in pom, which sonatype does not like --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index c37a3c68be..cea41bdc97 100644 --- a/build.sbt +++ b/build.sbt @@ -25,7 +25,7 @@ publishTo in ThisBuild <<= isSnapshot(if (_) Some(Opts.resolver.sonat scmInfo in ThisBuild := Some(ScmInfo(url("https://github.com/lift/framework"), "scm:git:https://github.com/lift/framework.git")) -pomExtra in ThisBuild ~= (_ ++ {Developers.toXml}) +pomExtra in ThisBuild := Developers.toXml credentials in ThisBuild <+= state map { s => Credentials(BuildPaths.getGlobalSettingsDirectory(s, BuildPaths.getGlobalBase(s)) / ".credentials") } From 17cc0bbf1706ea2f5bc85f573d8cc7dae6501555 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Tue, 17 Jun 2014 23:51:03 -0700 Subject: [PATCH 0980/1949] Dexy runs generated tests for selector examples. We rename the example-verification target to example-extraction, and make example-verification a target that runs its own shell script. --- dexy.yaml | 8 +++++++- docs/scripts/run-css-selector-examples.sh | 8 ++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 docs/scripts/run-css-selector-examples.sh diff --git a/dexy.yaml b/dexy.yaml index 053f4f2008..88a79420ea 100644 --- a/dexy.yaml +++ b/dexy.yaml @@ -1,8 +1,14 @@ - html-docs: - docs/*.adoc|asciidoctor - - example-verification: + - example-extraction: - docs/scripts/extract-css-selector-examples.sh|bash: - sh: timeout: 500 - html-docs + + - example-verification: + - docs/scripts/run-css-selector-examples.sh|bash: + - sh: + timeout: 500 + - example-extraction diff --git a/docs/scripts/run-css-selector-examples.sh b/docs/scripts/run-css-selector-examples.sh new file mode 100644 index 0000000000..3d991dca13 --- /dev/null +++ b/docs/scripts/run-css-selector-examples.sh @@ -0,0 +1,8 @@ +WORK_DIR=`pwd` + +if [[ ! -z "$DEXY_ROOT" ]] +then + cd $DEXY_ROOT +fi + +sbt -Dsbt.log.noformat=true "project lift-documentation-helpers" "test" From 33aa7841a8dd5c4fbf2490a4c49f120093ac6616 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 18 Jun 2014 18:24:53 -0400 Subject: [PATCH 0981/1949] Add support for setup code for example extractor. This setup code is a code block marked with a .setup class. --- .../ExtractCssSelectorExamples.scala | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/core/documentation-helpers/src/main/scala/net/liftweb/documentation/ExtractCssSelectorExamples.scala b/core/documentation-helpers/src/main/scala/net/liftweb/documentation/ExtractCssSelectorExamples.scala index acfcc8610f..fc8f64b681 100644 --- a/core/documentation-helpers/src/main/scala/net/liftweb/documentation/ExtractCssSelectorExamples.scala +++ b/core/documentation-helpers/src/main/scala/net/liftweb/documentation/ExtractCssSelectorExamples.scala @@ -16,7 +16,7 @@ case class ExampleFunction(function: String) extends ExamplePart case class ExampleOutput(output: String) extends ExamplePart case class FileContents(filename: String, contents: String) -case class ExampleContents(filename: String, exampleLabel: String, exampleParts: List[ExamplePart]) +case class ExampleContents(filename: String, exampleLabel: String, setupCode: String, exampleParts: List[ExamplePart]) object ExtractCssSelectorExamples extends App { private def contentsToProcess(basePath: String): Box[List[FileContents]] = { @@ -65,6 +65,17 @@ object ExtractCssSelectorExamples extends App { private def extractExamplesFromContents(fileContents: FileContents): List[ExampleContents] = { Html5.parse(fileContents.contents).toList.flatMap { html => + var setupCode: String = "" + + val setupExtractor = + ".setup" #> { + "code *" #> { codeContents: NodeSeq => + setupCode = codeContents.text + + codeContents + } + } + var exampleContents = List[ExampleContents]() val contentExtractor = @@ -105,11 +116,12 @@ object ExtractCssSelectorExamples extends App { (labelExtractor & partExtractor)(exampleNodes) - exampleContents ::= ExampleContents(fileContents.filename, exampleLabel, parts.reverse) + exampleContents ::= ExampleContents(fileContents.filename, exampleLabel, setupCode, parts.reverse) exampleNodes } + setupExtractor(html) contentExtractor(html) exampleContents.reverse @@ -140,7 +152,7 @@ object ExtractCssSelectorExamples extends App { val examples = for { - ExampleContents(_, exampleLabel, exampleParts) <- contents + ExampleContents(_, exampleLabel, setupCode, exampleParts) <- contents i <- (0 to (exampleParts.length / 3)) ExampleInput(input) <- exampleParts.lift(i) ExampleFunction(function) <- exampleParts.lift(i + 1) @@ -148,6 +160,8 @@ object ExtractCssSelectorExamples extends App { } yield { s""" | ""\"$exampleLabel""\" in { + | $setupCode + | | val input = Html5.parse(""\"$input""\") | val function = $function | val output = Html5.parse(""\"$output""\") From 5d2cbb266c2b3c3f3e0b229d787db9588131b562 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 18 Jun 2014 18:31:07 -0400 Subject: [PATCH 0982/1949] Fix structure and matching in extracted tests. Because of how HTML parsing works, we need to guarantee a root node, so we wrap the input and output in a div. Then, we include the div in the comparison we do after the fact. We also compare rendered input to output ignoring whitespace. --- .../documentation/ExtractCssSelectorExamples.scala | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/core/documentation-helpers/src/main/scala/net/liftweb/documentation/ExtractCssSelectorExamples.scala b/core/documentation-helpers/src/main/scala/net/liftweb/documentation/ExtractCssSelectorExamples.scala index fc8f64b681..7c81ade8ee 100644 --- a/core/documentation-helpers/src/main/scala/net/liftweb/documentation/ExtractCssSelectorExamples.scala +++ b/core/documentation-helpers/src/main/scala/net/liftweb/documentation/ExtractCssSelectorExamples.scala @@ -162,11 +162,16 @@ object ExtractCssSelectorExamples extends App { | ""\"$exampleLabel""\" in { | $setupCode | - | val input = Html5.parse(""\"$input""\") - | val function = $function - | val output = Html5.parse(""\"$output""\") + | val input = Html5.parse(""\"
    $input
    ""\") + | val function: (NodeSeq)=>NodeSeq = $function + | val output = Html5.parse(""\"
    $output
    ""\") | - | input.map(function) must_== output + | // The function returns a NodeSeq; we assume it will + | // contain a single element and unwrap it. + | input.map { html => function(html) } must beLike { + | case Full(rendered) => + | rendered must ==/(output.toOption.get) + | } | }""".stripMargin('|') } From 82902b2ff7fd398ff82071cd5b24cfa58edb49ce Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 18 Jun 2014 18:31:16 -0400 Subject: [PATCH 0983/1949] Run examples with color turned off. --- docs/scripts/run-css-selector-examples.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/scripts/run-css-selector-examples.sh b/docs/scripts/run-css-selector-examples.sh index 3d991dca13..c37be8058b 100644 --- a/docs/scripts/run-css-selector-examples.sh +++ b/docs/scripts/run-css-selector-examples.sh @@ -5,4 +5,4 @@ then cd $DEXY_ROOT fi -sbt -Dsbt.log.noformat=true "project lift-documentation-helpers" "test" +sbt -Dsbt.log.noformat=true -Dspecs2.color=false "project lift-documentation-helpers" "test" From b6e686be850f15d98597a1cbd62fa198b1c94715 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 18 Jun 2014 18:31:36 -0400 Subject: [PATCH 0984/1949] Add setup code to CSS selector example docs. --- docs/css-selectors.adoc | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/css-selectors.adoc b/docs/css-selectors.adoc index d4c4392618..ef99f04569 100644 --- a/docs/css-selectors.adoc +++ b/docs/css-selectors.adoc @@ -32,8 +32,16 @@ Transforms consist of three main components: Nice to have: graphic that shows a selector transform pointing to each /// -The details of each are provided below, but first here ere are some simple -examples of transforms that you can write with links: +The details of each are provided below, but first let's look at some simple +examples of transforms that you can write with links. For all of these examples, +we'll be using this sample data: + +[.setup] +```scala +case class User(name: String) + +val user = User("Benedict Cumberbatch") +``` [.interactive.selectors] .Replace the contents of all `a` elements with the text "Mozilla" From 9a65a7685471e8d7e5d255fd4e41bd49723aed36 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 18 Jun 2014 18:31:55 -0400 Subject: [PATCH 0985/1949] Fix CSS selector example docs based on tests. First errors caught by test failures during documentation generation! :) --- docs/css-selectors.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/css-selectors.adoc b/docs/css-selectors.adoc index ef99f04569..8a8ce994ba 100644 --- a/docs/css-selectors.adoc +++ b/docs/css-selectors.adoc @@ -115,7 +115,7 @@ val user = User("Benedict Cumberbatch") ``` [.output] ```html -
    Antonio Salazar Cardozo
    +Benedict Cumberbatch
    • Mozilla
    • Google
    • @@ -144,7 +144,7 @@ val user = User("Benedict Cumberbatch") ``` [.output] ```html -
      Antonio Salazar Cardozo
      +Benedict Cumberbatch
      • Mozilla
      • Mozilla
      • From bb6ee147988bc276f7c23992282b8b492c57f14f Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 18 Jun 2014 18:33:52 -0400 Subject: [PATCH 0986/1949] Ignore vim swap files. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 848d817ced..2c52eb3a42 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ syntax: glob *.old .DS_Store .cache +*.swp # logs derby.log From d7ad6cfc2cac15e343f07e9176a54371572672c5 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 18 Jun 2014 18:34:00 -0400 Subject: [PATCH 0987/1949] Ignore tests generated by example extractor. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 2c52eb3a42..cbe4ae4634 100644 --- a/.gitignore +++ b/.gitignore @@ -59,3 +59,4 @@ runner # Doc generation .dexy +core/documentation-helpers/src/test/**Test.scala From 62f69bcab33612dec52398da1c95a56e5f282732 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 21 Jun 2014 01:28:15 -0400 Subject: [PATCH 0988/1949] Bump Lift 3 to build only on Scala 2.11.1. --- build.sbt | 4 ++-- .../src/test/scala/net/liftweb/util/HtmlHelpersSpec.scala | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/build.sbt b/build.sbt index a91f67ed23..700c65a566 100644 --- a/build.sbt +++ b/build.sbt @@ -12,9 +12,9 @@ startYear in ThisBuild := Some(2006) organizationName in ThisBuild := "WorldWide Conferencing, LLC" -scalaVersion in ThisBuild := "2.10.4" +scalaVersion in ThisBuild := "2.11.1" -crossScalaVersions in ThisBuild := Seq("2.10.4") +crossScalaVersions in ThisBuild := Seq("2.11.1") libraryDependencies in ThisBuild <++= scalaVersion {sv => Seq(specs2(sv), scalacheck, scalatest(sv)) } diff --git a/core/util/src/test/scala/net/liftweb/util/HtmlHelpersSpec.scala b/core/util/src/test/scala/net/liftweb/util/HtmlHelpersSpec.scala index bd070971c2..1e30a62402 100644 --- a/core/util/src/test/scala/net/liftweb/util/HtmlHelpersSpec.scala +++ b/core/util/src/test/scala/net/liftweb/util/HtmlHelpersSpec.scala @@ -19,6 +19,7 @@ package util import xml._ +import org.specs2.matcher.XmlMatchers import org.specs2.mutable.Specification import common._ @@ -26,7 +27,7 @@ import common._ /** * Systems under specification for HtmlHelpers. */ -object HtmlHelpersSpec extends Specification with HtmlHelpers { +object HtmlHelpersSpec extends Specification with HtmlHelpers with XmlMatchers { "HtmlHelpers Specification".title "findBox" should { From 912956890660b57c361d10d729acca774daad160 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 21 Jun 2014 01:28:38 -0400 Subject: [PATCH 0989/1949] Drop lift-json-scalaz. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We’re only building on 2.11, so we’re only building lift-json-scalaz7. --- project/Build.scala | 6 ------ 1 file changed, 6 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index 751064bf47..1bd64bf445 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -69,12 +69,6 @@ object BuildDef extends Build { parallelExecution in Test := false, libraryDependencies <++= scalaVersion { sv => Seq(scalap(sv), paranamer) }) - lazy val json_scalaz = - coreProject("json-scalaz") - .dependsOn(json) - .settings(description := "JSON Library based on Scalaz 6", - libraryDependencies <+= scalaVersion(scalaz)) - lazy val json_scalaz7 = coreProject("json-scalaz7") .dependsOn(json) From d88d93858a68d177088359787b92d8fd05882059 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 21 Jun 2014 01:28:55 -0400 Subject: [PATCH 0990/1949] Fix 2.11 warning in CombParserHelpersSpec. --- .../test/scala/net/liftweb/util/CombParserHelpersSpec.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/util/src/test/scala/net/liftweb/util/CombParserHelpersSpec.scala b/core/util/src/test/scala/net/liftweb/util/CombParserHelpersSpec.scala index bf52c5f48b..25ebdb10b4 100644 --- a/core/util/src/test/scala/net/liftweb/util/CombParserHelpersSpec.scala +++ b/core/util/src/test/scala/net/liftweb/util/CombParserHelpersSpec.scala @@ -34,10 +34,10 @@ object CombParserHelpersSpec extends Specification with ScalaCheck { "The parser helpers" should { "provide an isEof function returning true iff a char is end of file" in { - isEof('\032') must beTrue + isEof('\u001a') must beTrue } "provide an notEof function returning true iff a char is not end of file" in { - notEof('\032') must beFalse + notEof('\u001a') must beFalse } "provide an isNum function returning true iff a char is a digit" in { isNum('0') must beTrue From d55b11db196efb2ba81afd0f13f7c7543f9652be Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 21 Jun 2014 18:44:11 -0400 Subject: [PATCH 0991/1949] Produce CSS selector example specs for 2.11. We need to mix in XmlMatchers to the Specification for the appropriate version of specs2 to take. --- .../net/liftweb/documentation/ExtractCssSelectorExamples.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/documentation-helpers/src/main/scala/net/liftweb/documentation/ExtractCssSelectorExamples.scala b/core/documentation-helpers/src/main/scala/net/liftweb/documentation/ExtractCssSelectorExamples.scala index 7c81ade8ee..c7e2be17df 100644 --- a/core/documentation-helpers/src/main/scala/net/liftweb/documentation/ExtractCssSelectorExamples.scala +++ b/core/documentation-helpers/src/main/scala/net/liftweb/documentation/ExtractCssSelectorExamples.scala @@ -186,13 +186,14 @@ object ExtractCssSelectorExamples extends App { | |import scala.xml._ | + |import org.specs2.matcher.XmlMatchers |import org.specs2.mutable.Specification | |import common._ |import util.Html5 |import util.Helpers._ | - |object $filename extends Specification { + |object $filename extends Specification with XmlMatchers { | ""\"${filename} examples""\" should { |${examples.mkString("\n")} | } From 4167969c06b1361a1ae2415a7c85178ec60b0e11 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Tue, 24 Jun 2014 23:34:53 -0400 Subject: [PATCH 0992/1949] Conditionally exclude the Position.scala file in Scala <2.11. Position is something we imported from scala.io because it was removed in Scala 2.11; in 2.10, we can lean on the one in the scala library. For people who used sbt assembly to build fat JARs, having both in two different JARs resulted in nasty-sauce. We now exclude it when building for Scala versions below 2.11 so this issue doesn't come up. --- project/Build.scala | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index 754c6d3f00..fb25b70a2d 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -103,8 +103,14 @@ object BuildDef extends Build { .settings(description := "Utilities Library", parallelExecution in Test := false, libraryDependencies <++= scalaVersion {sv => Seq(scala_compiler(sv), joda_time, - joda_convert, commons_codec, javamail, log4j, htmlparser)}) - + joda_convert, commons_codec, javamail, log4j, htmlparser)}, + excludeFilter <<= scalaVersion { scalaVersion => + if (scalaVersion.startsWith("2.11")) { + HiddenFileFilter + } else { + HiddenFileFilter || "Position.scala" + } + }) // Web Projects // ------------ From 4e4850efed5ec41d057f1afd18a81a7aa1dcac55 Mon Sep 17 00:00:00 2001 From: Aleksey Izmailov Date: Mon, 30 Jun 2014 11:10:31 -0400 Subject: [PATCH 0993/1949] Make BsonRecordListField validate all its elements by default when validate is called on it. This makes Record validation more complete. --- .../record/field/BsonRecordField.scala | 2 + .../field/BsonRecordListFieldSpec.scala | 69 +++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/field/BsonRecordListFieldSpec.scala diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala index bd6cab443c..c6825ccfee 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala @@ -81,6 +81,8 @@ class BsonRecordListField[OwnerType <: BsonRecord[OwnerType], SubRecordType <: B import scala.collection.JavaConversions._ + override def validations = ((elems: ValueType) => elems.flatMap(_.validate)) :: super.validations + override def asDBObject: DBObject = { val dbl = new BasicDBList value.foreach { v => dbl.add(v.asDBObject) } diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/field/BsonRecordListFieldSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/field/BsonRecordListFieldSpec.scala new file mode 100644 index 0000000000..97fc3f0bc4 --- /dev/null +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/field/BsonRecordListFieldSpec.scala @@ -0,0 +1,69 @@ +/* + * Copyright 2010-2014 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 mongodb +package record +package field + +import org.specs2.mutable.Specification +import net.liftweb.common._ +import net.liftweb.record.field.StringField + +package bsonlistfieldspecs { + class BookShelf extends MongoRecord[BookShelf] with ObjectIdPk[BookShelf] { + def meta = BookShelf + + object books extends BsonRecordListField(this, Book) + } + object BookShelf extends BookShelf with MongoMetaRecord[BookShelf] { + override def collectionName = "bookshelf" + } + + class Book extends BsonRecord[Book] { + override def meta = Book + + object title extends StringField(this, 512) + } + object Book extends Book with BsonMetaRecord[Book] +} + +class BsonRecordListFieldSpec extends Specification { + "BsonRecordListField Specification".title + + import bsonlistfieldspecs._ + + "BsonRecordListFieldSpec" should { + + "fail validation if at least one of its elements fails validation" in { + val scalaBook = Book.createRecord.title("Programming in Scala") + val liftBook = Book.createRecord + liftBook.title.setBox(Failure("Bad format")) + val shelf = BookShelf.createRecord.books(scalaBook :: liftBook :: Nil) + + shelf.validate must have size(1) + } + + "pass validation if all of its elements pass validation" in { + val scalaBook = Book.createRecord.title("Programming in Scala") + val liftBook = Book.createRecord.title("Simply Lift") + val shelf = BookShelf.createRecord.books(scalaBook :: liftBook :: Nil) + + shelf.validate must be empty + } + + } +} From 52f385018e46d08ddba9d61cdeb680dddaa65a67 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 6 Jul 2014 19:55:09 -0400 Subject: [PATCH 0994/1949] Tweak comet snippet API documentation. We now use div data-lift instead of lift:comet, and fix some small issues in the remaining docs. --- .../main/scala/net/liftweb/builtin/snippet/Comet.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Comet.scala b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Comet.scala index 8a81b09d06..4b5c1c38c0 100644 --- a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Comet.scala +++ b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Comet.scala @@ -6,7 +6,7 @@ * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 - * + *m * 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. @@ -70,13 +70,13 @@ object Comet extends DispatchSnippet with LazyLoggable { * A typical comet tag could look like: * * {{{ - * name="Optional, the name of this comet instance">{xhtml} + *
        {xhtml}
        * }}} * * For the name, you have three options - * - You can set a fixed name using `name="MyComet"` + * - You can set a fixed name using `name=MyComet` * - You can use a query parameter using `metaname`; e.g., for a url - * like foo?=id=122, your comet could take the name "122" if you use: + * like `foo?id=122`, your comet could take the name "122" if you use: * `metaname=id` * - You could assign a random name by using `randomname=true` * From 21338f456519beada09cb7624a85b9027141033f Mon Sep 17 00:00:00 2001 From: Peter Brant Date: Wed, 30 Jan 2013 18:45:56 +0100 Subject: [PATCH 0995/1949] Provide explicit parameter type Provide explicit parameter type when looking for snippet methods --- web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index e9797d62c9..35c1d4b50f 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -1815,7 +1815,7 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, (gotIt or nodeSeqFunc) openOr { val ar: Array[AnyRef] = List(Group(kids)).toArray - ((Helpers.invokeMethod(inst.getClass, inst, method, ar)) or + ((Helpers.invokeMethod(inst.getClass, inst, method, ar, Array(classOf[NodeSeq]))) or Helpers.invokeMethod(inst.getClass, inst, method)) match { case CheckNodeSeq(md) => md case it => From 1230d06afa98cbfbb98f5d7826a5b68cfbb5b607 Mon Sep 17 00:00:00 2001 From: Peter Brant Date: Thu, 22 Aug 2013 20:59:21 +0200 Subject: [PATCH 0996/1949] Set Date header when sending mail (apparently some spam filters want to see it to stay happy) --- core/util/src/main/scala/net/liftweb/util/Mailer.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/core/util/src/main/scala/net/liftweb/util/Mailer.scala b/core/util/src/main/scala/net/liftweb/util/Mailer.scala index b019e1de7b..aaa50bb8ec 100644 --- a/core/util/src/main/scala/net/liftweb/util/Mailer.scala +++ b/core/util/src/main/scala/net/liftweb/util/Mailer.scala @@ -233,6 +233,7 @@ trait Mailer extends SimpleInjector { message.setRecipients(Message.RecipientType.TO, info.flatMap {case x: To => Some[To](x) case _ => None}) message.setRecipients(Message.RecipientType.CC, info.flatMap {case x: CC => Some[CC](x) case _ => None}) message.setRecipients(Message.RecipientType.BCC, info.flatMap {case x: BCC => Some[BCC](x) case _ => None}) + message.setSentDate(new java.util.Date()) // message.setReplyTo(filter[MailTypes, ReplyTo](info, {case x @ ReplyTo(_) => Some(x); case _ => None})) message.setReplyTo(info.flatMap {case x: ReplyTo => Some[ReplyTo](x) case _ => None}) message.setSubject(subj) From 182594dae48c4d228a929a11f542f1d70c031995 Mon Sep 17 00:00:00 2001 From: Peter Brant Date: Wed, 23 Apr 2014 21:50:11 +0200 Subject: [PATCH 0997/1949] Share init key value Previously an effectively independent value was used for each *Var backing store. Depending on the situation this can result in large amounts of wasted memory (particularly with ScreenVars). --- .../main/scala/net/liftweb/util/AnyVar.scala | 9 +++---- .../scala/net/liftweb/http/LiftScreen.scala | 6 ++--- .../main/scala/net/liftweb/http/Vars.scala | 24 +++++++------------ .../scala/net/liftweb/wizard/Wizard.scala | 6 ++--- 4 files changed, 17 insertions(+), 28 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/AnyVar.scala b/core/util/src/main/scala/net/liftweb/util/AnyVar.scala index 2b1269bea7..16970d0b9f 100644 --- a/core/util/src/main/scala/net/liftweb/util/AnyVar.scala +++ b/core/util/src/main/scala/net/liftweb/util/AnyVar.scala @@ -100,10 +100,11 @@ abstract class AnyVar[T, MyType <: AnyVar[T, MyType]](dflt: => T) extends AnyVar trait AnyVarTrait[T, MyType <: AnyVarTrait[T, MyType]] extends PSettableValueHolder[T] with HasCalcDefaultValue[T] { self: MyType => protected lazy val name = VarConstants.varPrefix+getClass.getName+"_"+__nameSalt + private lazy val initedKey = name + VarConstants.initedSuffix protected def findFunc(name: String): Box[T] protected def setFunc(name: String, value: T): Unit protected def clearFunc(name: String): Unit - protected def wasInitialized(name: String): Boolean + protected def wasInitialized(name: String, initedKey: String): Boolean protected def calcDefaultValue: T @@ -111,7 +112,7 @@ trait AnyVarTrait[T, MyType <: AnyVarTrait[T, MyType]] extends PSettableValueHol /** * A non-side-effecting test if the value was initialized */ - protected def testWasSet(name: String): Boolean + protected def testWasSet(name: String, initedKey: String): Boolean protected def __nameSalt = "" @@ -150,7 +151,7 @@ trait AnyVarTrait[T, MyType <: AnyVarTrait[T, MyType]] extends PSettableValueHol } private def testInitialized: Unit = doSync { - if (!wasInitialized(name)) { + if (!wasInitialized(name, initedKey)) { registerCleanupFunc(_onShutdown _) } } @@ -168,7 +169,7 @@ trait AnyVarTrait[T, MyType <: AnyVarTrait[T, MyType]] extends PSettableValueHol /** * Has this Var been set or accessed and had its default value calculated */ - def set_? : Boolean = testWasSet(name) + def set_? : Boolean = testWasSet(name, initedKey) /** * Set the Var if it has not been calculated diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala index 1157dc8e57..3138ed6ef5 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala @@ -1438,15 +1438,13 @@ trait LiftScreen extends AbstractScreen with StatefulSnippet with ScreenWizardRe override protected def clearFunc(name: String): Unit = ScreenVarHandler.clear(name) - override protected def wasInitialized(name: String): Boolean = { - val bn = name + "_inited_?" + override protected def wasInitialized(name: String, bn: String): Boolean = { val old: Boolean = ScreenVarHandler.get(bn) openOr false ScreenVarHandler.set(bn, this, true) old } - override protected def testWasSet(name: String): Boolean = { - val bn = name + "_inited_?" + override protected def testWasSet(name: String, bn: String): Boolean = { ScreenVarHandler.get(name).isDefined || (ScreenVarHandler.get(bn) openOr false) } diff --git a/web/webkit/src/main/scala/net/liftweb/http/Vars.scala b/web/webkit/src/main/scala/net/liftweb/http/Vars.scala index ea0623e0b3..50dc194b13 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Vars.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Vars.scala @@ -131,15 +131,13 @@ abstract class SessionVar[T](dflt: => T) extends AnyVar[T, SessionVar[T]](dflt) override protected def clearFunc(name: String): Unit = S.session.foreach(_.unset(name)) - override protected def wasInitialized(name: String): Boolean = { - val bn = name + VarConstants.initedSuffix + override protected def wasInitialized(name: String, bn: String): Boolean = { val old: Boolean = S.session.flatMap(_.get(bn)) openOr false S.session.foreach(_.set(bn, true)) old } - override protected def testWasSet(name: String): Boolean = { - val bn = name + VarConstants.initedSuffix + override protected def testWasSet(name: String, bn: String): Boolean = { S.session.flatMap(_.get(name)).isDefined || (S.session.flatMap(_.get(bn)) openOr false) } @@ -245,8 +243,7 @@ abstract class ContainerVar[T](dflt: => T)(implicit containerSerializer: Contain httpSession <- session.httpSession } httpSession.removeAttribute(name) - override protected def wasInitialized(name: String): Boolean = { - val bn = name + VarConstants.initedSuffix + override protected def wasInitialized(name: String, bn: String): Boolean = { val old: Boolean = S.session.flatMap(s => localGet(s, bn) match { case Full(b: Boolean) => Full(b) case _ => Empty @@ -255,8 +252,7 @@ abstract class ContainerVar[T](dflt: => T)(implicit containerSerializer: Contain old } - override protected def testWasSet(name: String): Boolean = { - val bn = name + VarConstants.initedSuffix + override protected def testWasSet(name: String, bn: String): Boolean = { S.session.flatMap(s => localGet(s, name)).isDefined || (S.session.flatMap(s => localGet(s, bn) match { case Full(b: Boolean) => Full(b) @@ -415,8 +411,7 @@ abstract class RequestVar[T](dflt: => T) extends AnyVar[T, RequestVar[T]](dflt) override protected def clearFunc(name: String): Unit = RequestVarHandler.clear(name) - override protected def wasInitialized(name: String): Boolean = { - val bn = name + VarConstants.initedSuffix + override protected def wasInitialized(name: String, bn: String): Boolean = { val old: Boolean = RequestVarHandler.get(bn) openOr false RequestVarHandler.set(bn, this, true) old @@ -430,8 +425,7 @@ abstract class RequestVar[T](dflt: => T) extends AnyVar[T, RequestVar[T]](dflt) // no sync necessary for RequestVars... always on the same thread - override protected def testWasSet(name: String): Boolean = { - val bn = name + VarConstants.initedSuffix + override protected def testWasSet(name: String, bn: String): Boolean = { RequestVarHandler.get(name).isDefined || (RequestVarHandler.get(bn) openOr false) } @@ -475,15 +469,13 @@ abstract class TransientRequestVar[T](dflt: => T) extends AnyVar[T, TransientReq override protected def clearFunc(name: String): Unit = TransientRequestVarHandler.clear(name) - override protected def wasInitialized(name: String): Boolean = { - val bn = name + VarConstants.initedSuffix + override protected def wasInitialized(name: String, bn: String): Boolean = { val old: Boolean = TransientRequestVarHandler.get(bn) openOr false TransientRequestVarHandler.set(bn, this, true) old } - protected override def testWasSet(name: String): Boolean = { - val bn = name + VarConstants.initedSuffix + protected override def testWasSet(name: String, bn: String): Boolean = { TransientRequestVarHandler.get(name).isDefined || (TransientRequestVarHandler.get(bn) openOr false) } diff --git a/web/wizard/src/main/scala/net/liftweb/wizard/Wizard.scala b/web/wizard/src/main/scala/net/liftweb/wizard/Wizard.scala index 4d5cb4192f..542d691bef 100644 --- a/web/wizard/src/main/scala/net/liftweb/wizard/Wizard.scala +++ b/web/wizard/src/main/scala/net/liftweb/wizard/Wizard.scala @@ -577,15 +577,13 @@ trait Wizard extends StatefulSnippet with Factory with ScreenWizardRendered { override protected def clearFunc(name: String): Unit = WizardVarHandler.clear(name) - override protected def wasInitialized(name: String): Boolean = { - val bn = name + "_inited_?" + override protected def wasInitialized(name: String, bn: String): Boolean = { val old: Boolean = WizardVarHandler.get(bn) openOr false WizardVarHandler.set(bn, this, true) old } - override protected def testWasSet(name: String): Boolean = { - val bn = name + "_inited_?" + override protected def testWasSet(name: String, bn: String): Boolean = { WizardVarHandler.get(name).isDefined || (WizardVarHandler.get(bn) openOr false) } From b461a5642343642c77a018c0585ea83a1ba7cf3e Mon Sep 17 00:00:00 2001 From: Michael Elgar Date: Mon, 9 Jun 2014 14:53:52 -0500 Subject: [PATCH 0998/1949] Include option attributes in multiSelect_* --- .../main/scala/net/liftweb/http/SHtml.scala | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala b/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala index c645c20c5e..647909cdc5 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala @@ -1020,8 +1020,7 @@ trait SHtml { fmapFunc((testFunc)) { funcName => (attrs.foldLeft()(_ % _)) % ("onchange" -> (jsFunc match { case Full(f) => JsCrVar(key, JsRaw("this")) & deferCall(raw(funcName, key), f) @@ -1845,6 +1844,10 @@ trait SHtml { } } + private def optionToElem(option: SelectableOption[String]): Elem = + option.attrs.foldLeft()(_ % _) + + private final case class SelectableOptionWithNonce[+T](value: T, nonce: String, label: String, attrs: ElemAttr*) /** @@ -1929,8 +1932,7 @@ trait SHtml { attrs.foldLeft(fmapFunc(testFunc) { fn => })(_ % _) } @@ -1962,8 +1964,7 @@ trait SHtml { fmapFunc(func) { funcName => attrs.foldLeft()(_ % _) } @@ -1997,8 +1998,7 @@ trait SHtml { List( attrs.foldLeft()(_ % _), ) @@ -2068,8 +2068,7 @@ trait SHtml { funcName => (attrs.foldLeft()(_ % _)) % ("onchange" -> (jsFunc match { case Full(f) => JsCrVar(key, JsRaw("this")) & deferCall(raw(funcName, key), f) @@ -2143,7 +2142,7 @@ trait SHtml { deflt: Seq[String], func: AFuncHolder, attrs: ElemAttr*): Elem = fmapFunc(func)(funcName => - attrs.foldLeft()(_ % _)) + attrs.foldLeft()(_ % _)) def textarea(value: String, func: String => Any, attrs: ElemAttr*): Elem = From 34ec2d8401444abd4383b6cbc46f5ccb360ba3d9 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Wed, 23 Jul 2014 22:12:09 -0400 Subject: [PATCH 0999/1949] Loosen up some of the error message requirements in mongodb specs. MongoDB changed how they report errors a bit in one of the latest releases. Error messages no longer start with the string we were checking, but they do contain it - so this commit will get our specs passing again and should keep them passing on older mongos. --- .../src/test/scala/net/liftweb/mongodb/MongoDirectSpec.scala | 2 +- .../scala/net/liftweb/mongodb/MongoDocumentExamplesSpec.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDirectSpec.scala b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDirectSpec.scala index f91eb5d7ef..a2a94c5136 100644 --- a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDirectSpec.scala +++ b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDirectSpec.scala @@ -231,7 +231,7 @@ class MongoDirectSpec extends Specification with MongoTestKit { coll.save(doc) db.getLastError.get("err") must beNull coll.save(doc2) must throwA[MongoException] - db.getLastError.get("err").toString must startWith("E11000 duplicate key error index") + db.getLastError.get("err").toString must contain("E11000 duplicate key error index") coll.save(doc3) db.getLastError.get("err") must beNull diff --git a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDocumentExamplesSpec.scala b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDocumentExamplesSpec.scala index 1659c928bb..08061bd85f 100644 --- a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDocumentExamplesSpec.scala +++ b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDocumentExamplesSpec.scala @@ -493,7 +493,7 @@ class MongoDocumentExamplesSpec extends Specification with MongoTestKit { SessCollection.save(tc, db) db.getLastError.get("err") must beNull SessCollection.save(tc2, db) must throwA[MongoException] - db.getLastError.get("err").toString must startWith("E11000 duplicate key error index") + db.getLastError.get("err").toString must contain("E11000 duplicate key error index") SessCollection.save(tc3, db) db.getLastError.get("err") must beNull From 34c80421149bf505f8e35f03997f45a511888da3 Mon Sep 17 00:00:00 2001 From: Peter Brant Date: Thu, 24 Jul 2014 13:33:24 +0200 Subject: [PATCH 1000/1949] Fix a couple of Ajax related path issues - Include context path when generating page specific JS - Include context path when making Ajax requests - Fix Ajax path matching (previously renderVersion was being set to "ajax") --- web/webkit/src/main/resources/toserve/lift.js | 6 ++++-- .../src/main/scala/net/liftweb/http/LiftMerge.scala | 2 +- .../src/main/scala/net/liftweb/http/LiftRules.scala | 7 +++++++ .../main/scala/net/liftweb/http/LiftServlet.scala | 5 +++-- .../scala/net/liftweb/http/js/LiftJavaScript.scala | 1 + .../net/liftweb/http/js/LiftJavaScriptSpec.scala | 12 ++++++------ 6 files changed, 22 insertions(+), 11 deletions(-) diff --git a/web/webkit/src/main/resources/toserve/lift.js b/web/webkit/src/main/resources/toserve/lift.js index 332297262f..2f1b173cd7 100644 --- a/web/webkit/src/main/resources/toserve/lift.js +++ b/web/webkit/src/main/resources/toserve/lift.js @@ -73,6 +73,7 @@ window.location.href = "/"; }, cometServer: "", + ajaxServer: "", cometOnError: function(e) { if (window.console && typeof window.console.error === 'function') { window.console.error(e.stack || e); @@ -164,15 +165,16 @@ }*/ function calcAjaxUrl(url, version) { + var serverUrl = settings.ajaxServer + url; if (settings.enableGc) { var replacement = ajaxPath()+'/'+pageId; if (version !== null) { replacement += ('-'+version.toString(36)) + (ajaxQueue.length > 35 ? 35 : ajaxQueue.length).toString(36); } - return url.replace(ajaxPath(), replacement); + return serverUrl.replace(ajaxPath(), replacement); } else { - return url; + return serverUrl; } } diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala index c58feade2e..92f3d101ad 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala @@ -28,7 +28,7 @@ private[http] trait LiftMerge { self: LiftSession => private def scriptUrl(scriptFile: String) = { - s"/${LiftRules.liftPath}/$scriptFile" + S.encodeURL(s"${S.contextPath}/${LiftRules.liftPath}/$scriptFile") } // Gather all page-specific JS into one JsCmd. diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 1044699669..4fb5d1a0e5 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -444,6 +444,13 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { */ @volatile var cometServer: () => String = () => S.contextPath + /** + * Calculate the Ajax Server (by default, the server that + * the request was made on, but can do the multi-server thing + * as well) + */ + @volatile var ajaxServer: () => String = () => S.contextPath + /** * The maximum concurrent requests. If this number of * requests are being serviced for a given session, messages diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala index 3df69ac736..60fb16e6a4 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala @@ -567,10 +567,11 @@ class LiftServlet extends Loggable { * The requestVersion is passed to the function that is passed in. */ private def extractVersions[T](path: List[String])(f: (Box[AjaxVersionInfo]) => T): T = { + val liftPath = LiftRules.liftPath path match { - case ajaxPath :: AjaxVersions(versionInfo @ AjaxVersionInfo(renderVersion, _, _)) :: _ => + case liftPath :: "ajax" :: AjaxVersions(versionInfo @ AjaxVersionInfo(renderVersion, _, _)) :: _ => RenderVersion.doWith(renderVersion)(f(Full(versionInfo))) - case ajaxPath :: renderVersion :: _ => + case liftPath :: "ajax" :: renderVersion :: _ => RenderVersion.doWith(renderVersion)(f(Empty)) case _ => f(Empty) } diff --git a/web/webkit/src/main/scala/net/liftweb/http/js/LiftJavaScript.scala b/web/webkit/src/main/scala/net/liftweb/http/js/LiftJavaScript.scala index 27cc263a96..d131f77958 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/js/LiftJavaScript.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/js/LiftJavaScript.scala @@ -65,6 +65,7 @@ object LiftJavaScript { "cometGetTimeout" -> LiftRules.cometGetTimeout, "cometFailureRetryTimeout" -> LiftRules.cometFailureRetryTimeout, "cometServer" -> LiftRules.cometServer(), + "ajaxServer" -> LiftRules.ajaxServer(), "logError" -> LiftRules.jsLogFunc.map(fnc => AnonFunc("msg", fnc(JsVar("msg")))).openOr(AnonFunc("msg", Noop)), "ajaxOnFailure" -> LiftRules.ajaxDefaultFailure.map(fnc => AnonFunc(fnc())).openOr(AnonFunc(Noop)), "ajaxOnStart" -> LiftRules.ajaxStart.map(fnc => AnonFunc(fnc())).openOr(AnonFunc(Noop)), diff --git a/web/webkit/src/test/scala/net/liftweb/http/js/LiftJavaScriptSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/js/LiftJavaScriptSpec.scala index b615a8b122..d261e56f85 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/js/LiftJavaScriptSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/js/LiftJavaScriptSpec.scala @@ -40,35 +40,35 @@ object LiftJavaScriptSpec extends Specification { "create default settings" in { S.initIfUninitted(session) { val settings = LiftJavaScript.settings - settings.toJsCmd must_== """{"liftPath": "lift", "ajaxRetryCount": 3, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "", "logError": function(msg) {}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}""" + settings.toJsCmd must_== """{"liftPath": "lift", "ajaxRetryCount": 3, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "", "ajaxServer": "", "logError": function(msg) {}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}""" } } "create custom static settings" in { S.initIfUninitted(session) { LiftRules.ajaxRetryCount = Full(4) val settings = LiftJavaScript.settings - settings.toJsCmd must_== """{"liftPath": "lift", "ajaxRetryCount": 4, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "", "logError": function(msg) {}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}""" + settings.toJsCmd must_== """{"liftPath": "lift", "ajaxRetryCount": 4, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "", "ajaxServer": "", "logError": function(msg) {}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}""" } } "create custom dynamic settings" in { S.initIfUninitted(session) { LiftRules.cometServer = () => "srvr1" val settings = LiftJavaScript.settings - settings.toJsCmd must_== """{"liftPath": "lift", "ajaxRetryCount": 4, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "srvr1", "logError": function(msg) {}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}""" + settings.toJsCmd must_== """{"liftPath": "lift", "ajaxRetryCount": 4, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "srvr1", "ajaxServer": "", "logError": function(msg) {}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}""" } } "create custom function settings" in { S.initIfUninitted(session) { LiftRules.jsLogFunc = Full(v => JE.Call("lift.logError", v)) val settings = LiftJavaScript.settings - settings.toJsCmd must_== """{"liftPath": "lift", "ajaxRetryCount": 4, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "srvr1", "logError": function(msg) {lift.logError(msg);}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}""" + settings.toJsCmd must_== """{"liftPath": "lift", "ajaxRetryCount": 4, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "srvr1", "ajaxServer": "", "logError": function(msg) {lift.logError(msg);}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}""" } } "create init command" in { S.initIfUninitted(session) { val init = LiftRules.javaScriptSettings.vend().map(_.apply(session)).map(LiftJavaScript.initCmd(_).toJsCmd) init must_== - Full("""var lift_settings = {"liftPath": "lift", "ajaxRetryCount": 4, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "srvr1", "logError": function(msg) {lift.logError(msg);}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}; + Full("""var lift_settings = {"liftPath": "lift", "ajaxRetryCount": 4, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "srvr1", "ajaxServer": "", "logError": function(msg) {lift.logError(msg);}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}; |window.lift.extend(lift_settings,window.liftJQuery); |window.lift.init(lift_settings);""".stripMargin) } @@ -79,7 +79,7 @@ object LiftJavaScriptSpec extends Specification { val init = LiftJavaScript.initCmd(settings) println(init.toJsCmd) init.toJsCmd must_== - """var lift_settings = {"liftPath": "liftyStuff", "ajaxRetryCount": 4, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "srvr1", "logError": function(msg) {lift.logError(msg);}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}, "mysetting": 99}; + """var lift_settings = {"liftPath": "liftyStuff", "ajaxRetryCount": 4, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "srvr1", "ajaxServer": "", "logError": function(msg) {lift.logError(msg);}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}, "mysetting": 99}; |window.lift.extend(lift_settings,window.liftJQuery); |window.lift.init(lift_settings);""".stripMargin } From b4292c6e5bed3800017217e239d8f1a7d62c05d8 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Thu, 24 Jul 2014 18:30:23 -0400 Subject: [PATCH 1001/1949] Update the comet constructor not found error. --- web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index 460fd00528..02aa170abd 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -2697,7 +2697,7 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, attemptedComet match { case fail @ Failure(_, Full(e: java.lang.NoSuchMethodException), _) => - val message = s"Failed to find appropriate comet constructor for ${cometClass.getCanonicalName}. Tried no arguments and (LiftSession, Box[String], NodeSeq, Map[String,String])" + val message = s"Couldn't find valid comet constructor for ${cometClass.getCanonicalName}. Comets should have a no argument constructor or one that takes the following arguments: (LiftSession, Box[String], NodeSeq, Map[String,String])." logger.info(message, e) fail ?~! message From 5f09c86fdc72ae163d2fc4d4415b764e3d92be1f Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Thu, 24 Jul 2014 20:06:09 -0400 Subject: [PATCH 1002/1949] Fix an enum with a duplicate value in it. --- web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 802fd5b0ae..38eecfc635 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -192,7 +192,7 @@ object LiftRules extends LiftRulesMocker { val CometTimeout = Value(9, "Comet Component did not response to requests") val CometNotFound = Value(10, "Comet Component not found") val ExecutionFailure = Value(11, "Execution Failure") - val NoCometType = Value(10, "Comet Type not specified") + val NoCometType = Value(12, "Comet Type not specified") } def defaultFuncNameGenerator(runMode: Props.RunModes.Value): () => String = From c56e5618ddfd5730846bd700203400bad50e22fe Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Thu, 24 Jul 2014 20:06:16 -0400 Subject: [PATCH 1003/1949] Remove a case of two overloads providing default arguments. I'm not really sure what the intended case for this is - or how it worked to begin with, but @Shadowfiend may be interested in this commit. --- web/webkit/src/main/scala/net/liftweb/http/S.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/S.scala b/web/webkit/src/main/scala/net/liftweb/http/S.scala index c49ee896fc..4b8453f9a2 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/S.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/S.scala @@ -853,10 +853,10 @@ trait S extends HasParams with Loggable with UserAgentCalculator { * receive updates like this using {S.addComet}. */ def findOrCreateComet[T <: LiftCometActor]( - cometName: Box[String] = Empty, - cometHtml: NodeSeq = NodeSeq.Empty, - cometAttributes: Map[String, String] = Map.empty, - receiveUpdatesOnPage: Boolean = false + cometName: Box[String], + cometHtml: NodeSeq, + cometAttributes: Map[String, String], + receiveUpdatesOnPage: Boolean )(implicit cometManifest: Manifest[T]): Box[T] = { for { session <- session From cc613f84995e574c82608d59f14434b925cf32d5 Mon Sep 17 00:00:00 2001 From: Peter Brant Date: Tue, 17 Dec 2013 17:53:26 +0100 Subject: [PATCH 1004/1949] Minor LiftScreen tweaks - DisplayIf should take the target field as a parameter - Hack around erasure warnings (which strangely weren't reported before 2.11) - Add set_? and otherValueSet_? methods to field --- .../scala/net/liftweb/http/LiftScreen.scala | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala index c4d6dc55b3..0a7698aad2 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftScreen.scala @@ -178,6 +178,10 @@ trait AbstractScreen extends Factory with Loggable { def set(v: ValueType) = _currentValue.set(setFilter.foldLeft(v)((nv, f) => f(nv))) + def set_? : Boolean = _currentValue.set_? + + def otherValueSet_? : Boolean = _otherValue.set_? + implicit def manifest: Manifest[ValueType] protected def buildIt[T](implicit man: Manifest[T]): Manifest[T] = man @@ -282,7 +286,7 @@ trait AbstractScreen extends Factory with Loggable { case FieldTransform(func) => func }).toList - val newShow: Box[() => Boolean] = (stuff.collect { + val newShow: Box[BaseField => Boolean] = (stuff.collect { case DisplayIf(func) => func }).headOption @@ -321,7 +325,7 @@ trait AbstractScreen extends Factory with Loggable { override def transforms = newTransforms - override def show_? = newShow map (_()) openOr (super.show_?) + override def show_? = newShow map (_(this)) openOr (super.show_?) } } } @@ -351,7 +355,7 @@ trait AbstractScreen extends Factory with Loggable { protected def builder[T](name: => String, default: => T, stuff: FilterOrValidate[T]*)(implicit man: Manifest[T]): FieldBuilder[T] = { new FieldBuilder[T](name, default, man, Empty, stuff.toList.collect { - case AVal(v: (T => List[FieldError])) => v + case AVal(v: Function1[_, _]) => v.asInstanceOf[T => List[FieldError]] }, stuff.toList.collect { case AFilter(v) => v.asInstanceOf[T => T] @@ -400,7 +404,7 @@ trait AbstractScreen extends Factory with Loggable { protected final case class FieldTransform(func: BaseField => NodeSeq => NodeSeq) extends FilterOrValidate[Nothing] - protected final case class DisplayIf(func: () => Boolean) extends FilterOrValidate[Nothing] + protected final case class DisplayIf(func: BaseField => Boolean) extends FilterOrValidate[Nothing] protected def field[T](underlying: => BaseField {type ValueType = T}, stuff: FilterOrValidate[T]*)(implicit man: Manifest[T]): Field {type ValueType = T} = { @@ -427,7 +431,7 @@ trait AbstractScreen extends Factory with Loggable { case FieldTransform(func) => func }).toList - val newShow: Box[() => Boolean] = (stuff.collect { + val newShow: Box[BaseField => Boolean] = (stuff.collect { case DisplayIf(func) => func }).headOption @@ -445,7 +449,7 @@ trait AbstractScreen extends Factory with Loggable { /** * Given the current state of things, should this field be shown */ - override def show_? = newShow map (_()) openOr underlying.show_? + override def show_? = newShow map (_(this)) openOr underlying.show_? /** * What form elements are we going to add to this field? @@ -521,7 +525,7 @@ trait AbstractScreen extends Factory with Loggable { case FieldTransform(func) => func }).toList - val newShow: Box[() => Boolean] = (stuff.collect { + val newShow: Box[BaseField => Boolean] = (stuff.collect { case DisplayIf(func) => func }).headOption @@ -547,7 +551,7 @@ trait AbstractScreen extends Factory with Loggable { /** * Given the current state of things, should this field be shown */ - override def show_? = newShow map (_()) openOr (underlying.map(_.show_?) openOr false) + override def show_? = newShow map (_(this)) openOr (underlying.map(_.show_?) openOr false) /** * What form elements are we going to add to this field? @@ -608,7 +612,7 @@ trait AbstractScreen extends Factory with Loggable { */ protected def field[T](name: => String, default: => T, stuff: FilterOrValidate[T]*)(implicit man: Manifest[T]): Field {type ValueType = T} = new FieldBuilder[T](name, default, man, Empty, stuff.toList.flatMap { - case AVal(v: (T => List[FieldError])) => List(v) + case AVal(v: Function1[_, _]) => List(v.asInstanceOf[T => List[FieldError]]) case _ => Nil }, stuff.toList.flatMap { case AFilter(v) => List(v.asInstanceOf[T => T]) @@ -725,7 +729,7 @@ trait AbstractScreen extends Factory with Loggable { case FieldTransform(func) => func }).toList - val newShow: Box[() => Boolean] = (stuff.collect { + val newShow: Box[BaseField => Boolean] = (stuff.collect { case DisplayIf(func) => func }).headOption @@ -753,7 +757,7 @@ trait AbstractScreen extends Factory with Loggable { case _ => Nil }.toList override val validations = stuff.flatMap { - case AVal(v: (T => List[FieldError])) => List(v) + case AVal(v: Function1[_, _]) => List(v.asInstanceOf[T => List[FieldError]]) case _ => Nil }.toList @@ -765,7 +769,7 @@ trait AbstractScreen extends Factory with Loggable { override def transforms = newTransforms - override def show_? = newShow map (_()) openOr (super.show_?) + override def show_? = newShow map (_(this)) openOr (super.show_?) } } @@ -790,7 +794,7 @@ trait AbstractScreen extends Factory with Loggable { case _ => Nil }.toList override val validations = stuff.flatMap { - case AVal(v: (T => List[FieldError])) => List(v) + case AVal(v: Function1[_, _]) => List(v.asInstanceOf[T => List[FieldError]]) case _ => Nil }.toList @@ -802,7 +806,7 @@ trait AbstractScreen extends Factory with Loggable { override def transforms = newTransforms - override def show_? = newShow map (_()) openOr (super.show_?) + override def show_? = newShow map (_(this)) openOr (super.show_?) } } } From d07ffda1608893dee7796b384ce724dd487eafd4 Mon Sep 17 00:00:00 2001 From: Peter Brant Date: Mon, 28 Jul 2014 18:44:35 +0200 Subject: [PATCH 1005/1949] Add lift.getPageId function --- web/webkit/src/main/resources/toserve/lift.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/web/webkit/src/main/resources/toserve/lift.js b/web/webkit/src/main/resources/toserve/lift.js index 2f1b173cd7..1745c4587e 100644 --- a/web/webkit/src/main/resources/toserve/lift.js +++ b/web/webkit/src/main/resources/toserve/lift.js @@ -583,6 +583,9 @@ setPageId: function(pgId) { pageId = pgId; }, + getPageId: function() { + return pageId; + }, setUriSuffix: function(suffix) { uriSuffix = suffix; }, From c01a98bb63122550351a1cc2f7a282642961cb3e Mon Sep 17 00:00:00 2001 From: Diego Medina Date: Tue, 29 Jul 2014 20:48:50 -0400 Subject: [PATCH 1006/1949] prepare build script to build from lift_26 instead of master Since we are not adding or dropping any scala release for the 2.6 release and we have two main projects to build, depending on scala version, I hardcoded the list of scala versions for each project --- unsafePublishLift.sh | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/unsafePublishLift.sh b/unsafePublishLift.sh index 4e2468cba2..5bdedc111e 100755 --- a/unsafePublishLift.sh +++ b/unsafePublishLift.sh @@ -120,8 +120,8 @@ for MODULE in framework ; do CURRENT_BRANCH=`git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/\1/'` debug "Current branch for $MODULE is $CURRENT_BRANCH" - if [ "${CURRENT_BRANCH}" != "master" ]; then - echo "Currently releases can only be built from master. $MODULE is on branch $CURRENT_BRANCH. Aborting build." + if [ "${CURRENT_BRANCH}" != "lift_26" ]; then + echo "Currently releases can only be built from lift_26. $MODULE is on branch $CURRENT_BRANCH. Aborting build." exit fi @@ -155,7 +155,18 @@ for MODULE in framework ; do #git push origin ${RELEASE_VERSION}-release >> ${BUILDLOG} || die "Could not push release tag!" # Do a separate build for each configured Scala version so we don't blow the heap - for SCALA_VERSION in $(grep crossScalaVersions build.sbt | cut -d '(' -f 2 | sed s/[,\)\"]//g ); do + # We have one project for scala versions pre 2.11.x + for SCALA_VERSION in 2.9.1 2.9.1-1 2.9.2 2.10.4 ; do + echo -n " Building against Scala ${SCALA_VERSION}..." + if ! ./liftsh ++${SCALA_VERSION} "project lift-framework-pre-211" clean update test publishSigned >> ${BUILDLOG} ; then + echo "failed! See build log for details" + exit + fi + echo "complete" + done + + #and we have another for >= 2.11.x + for SCALA_VERSION in 2.11.1; do echo -n " Building against Scala ${SCALA_VERSION}..." if ! ./liftsh ++${SCALA_VERSION} clean update test publishSigned >> ${BUILDLOG} ; then echo "failed! See build log for details" @@ -164,6 +175,7 @@ for MODULE in framework ; do echo "complete" done + echo "Build complete for module ${MODULE}" done From 355b25c912be42a9ab40463c212f3f1fe40e14e2 Mon Sep 17 00:00:00 2001 From: Peter Brant Date: Thu, 31 Jul 2014 00:15:17 +0200 Subject: [PATCH 1007/1949] Rename ajaxServer to contextPath --- web/webkit/src/main/resources/toserve/lift.js | 4 ++-- .../src/main/scala/net/liftweb/http/LiftRules.scala | 7 ------- .../scala/net/liftweb/http/js/LiftJavaScript.scala | 2 +- .../net/liftweb/http/js/LiftJavaScriptSpec.scala | 12 ++++++------ 4 files changed, 9 insertions(+), 16 deletions(-) diff --git a/web/webkit/src/main/resources/toserve/lift.js b/web/webkit/src/main/resources/toserve/lift.js index 1745c4587e..f2e92770f0 100644 --- a/web/webkit/src/main/resources/toserve/lift.js +++ b/web/webkit/src/main/resources/toserve/lift.js @@ -73,7 +73,7 @@ window.location.href = "/"; }, cometServer: "", - ajaxServer: "", + contextPath: "", cometOnError: function(e) { if (window.console && typeof window.console.error === 'function') { window.console.error(e.stack || e); @@ -165,7 +165,7 @@ }*/ function calcAjaxUrl(url, version) { - var serverUrl = settings.ajaxServer + url; + var serverUrl = settings.contextPath + url; if (settings.enableGc) { var replacement = ajaxPath()+'/'+pageId; if (version !== null) { diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 4fb5d1a0e5..1044699669 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -444,13 +444,6 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { */ @volatile var cometServer: () => String = () => S.contextPath - /** - * Calculate the Ajax Server (by default, the server that - * the request was made on, but can do the multi-server thing - * as well) - */ - @volatile var ajaxServer: () => String = () => S.contextPath - /** * The maximum concurrent requests. If this number of * requests are being serviced for a given session, messages diff --git a/web/webkit/src/main/scala/net/liftweb/http/js/LiftJavaScript.scala b/web/webkit/src/main/scala/net/liftweb/http/js/LiftJavaScript.scala index d131f77958..f6b3d890a7 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/js/LiftJavaScript.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/js/LiftJavaScript.scala @@ -65,7 +65,7 @@ object LiftJavaScript { "cometGetTimeout" -> LiftRules.cometGetTimeout, "cometFailureRetryTimeout" -> LiftRules.cometFailureRetryTimeout, "cometServer" -> LiftRules.cometServer(), - "ajaxServer" -> LiftRules.ajaxServer(), + "contextPath" -> S.contextPath, "logError" -> LiftRules.jsLogFunc.map(fnc => AnonFunc("msg", fnc(JsVar("msg")))).openOr(AnonFunc("msg", Noop)), "ajaxOnFailure" -> LiftRules.ajaxDefaultFailure.map(fnc => AnonFunc(fnc())).openOr(AnonFunc(Noop)), "ajaxOnStart" -> LiftRules.ajaxStart.map(fnc => AnonFunc(fnc())).openOr(AnonFunc(Noop)), diff --git a/web/webkit/src/test/scala/net/liftweb/http/js/LiftJavaScriptSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/js/LiftJavaScriptSpec.scala index d261e56f85..5f5f3accc5 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/js/LiftJavaScriptSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/js/LiftJavaScriptSpec.scala @@ -40,35 +40,35 @@ object LiftJavaScriptSpec extends Specification { "create default settings" in { S.initIfUninitted(session) { val settings = LiftJavaScript.settings - settings.toJsCmd must_== """{"liftPath": "lift", "ajaxRetryCount": 3, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "", "ajaxServer": "", "logError": function(msg) {}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}""" + settings.toJsCmd must_== """{"liftPath": "lift", "ajaxRetryCount": 3, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "", "contextPath": "", "logError": function(msg) {}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}""" } } "create custom static settings" in { S.initIfUninitted(session) { LiftRules.ajaxRetryCount = Full(4) val settings = LiftJavaScript.settings - settings.toJsCmd must_== """{"liftPath": "lift", "ajaxRetryCount": 4, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "", "ajaxServer": "", "logError": function(msg) {}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}""" + settings.toJsCmd must_== """{"liftPath": "lift", "ajaxRetryCount": 4, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "", "contextPath": "", "logError": function(msg) {}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}""" } } "create custom dynamic settings" in { S.initIfUninitted(session) { LiftRules.cometServer = () => "srvr1" val settings = LiftJavaScript.settings - settings.toJsCmd must_== """{"liftPath": "lift", "ajaxRetryCount": 4, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "srvr1", "ajaxServer": "", "logError": function(msg) {}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}""" + settings.toJsCmd must_== """{"liftPath": "lift", "ajaxRetryCount": 4, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "srvr1", "contextPath": "", "logError": function(msg) {}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}""" } } "create custom function settings" in { S.initIfUninitted(session) { LiftRules.jsLogFunc = Full(v => JE.Call("lift.logError", v)) val settings = LiftJavaScript.settings - settings.toJsCmd must_== """{"liftPath": "lift", "ajaxRetryCount": 4, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "srvr1", "ajaxServer": "", "logError": function(msg) {lift.logError(msg);}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}""" + settings.toJsCmd must_== """{"liftPath": "lift", "ajaxRetryCount": 4, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "srvr1", "contextPath": "", "logError": function(msg) {lift.logError(msg);}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}""" } } "create init command" in { S.initIfUninitted(session) { val init = LiftRules.javaScriptSettings.vend().map(_.apply(session)).map(LiftJavaScript.initCmd(_).toJsCmd) init must_== - Full("""var lift_settings = {"liftPath": "lift", "ajaxRetryCount": 4, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "srvr1", "ajaxServer": "", "logError": function(msg) {lift.logError(msg);}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}; + Full("""var lift_settings = {"liftPath": "lift", "ajaxRetryCount": 4, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "srvr1", "contextPath": "", "logError": function(msg) {lift.logError(msg);}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}; |window.lift.extend(lift_settings,window.liftJQuery); |window.lift.init(lift_settings);""".stripMargin) } @@ -79,7 +79,7 @@ object LiftJavaScriptSpec extends Specification { val init = LiftJavaScript.initCmd(settings) println(init.toJsCmd) init.toJsCmd must_== - """var lift_settings = {"liftPath": "liftyStuff", "ajaxRetryCount": 4, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "srvr1", "ajaxServer": "", "logError": function(msg) {lift.logError(msg);}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}, "mysetting": 99}; + """var lift_settings = {"liftPath": "liftyStuff", "ajaxRetryCount": 4, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "srvr1", "contextPath": "", "logError": function(msg) {lift.logError(msg);}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}, "mysetting": 99}; |window.lift.extend(lift_settings,window.liftJQuery); |window.lift.init(lift_settings);""".stripMargin } From 6096b44f9694fbbcfd23a9000cd2121f236b58d7 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Fri, 18 Apr 2014 17:13:35 -0400 Subject: [PATCH 1008/1949] Clean up some naming in the data attribute processor code. --- .../scala/net/liftweb/http/LiftSession.scala | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index 1ede2d8280..4f6ff32a55 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -2185,20 +2185,20 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, def unapply(in: Node): Option[DataAttributeProcessorAnswer] = { in match { - case e: Elem if !rules.isEmpty => - - val attrs = e.attributes - - e.attributes.toStream.flatMap { + case element: Elem if ! rules.isEmpty => + element.attributes.toStream.flatMap { case UnprefixedAttribute(key, value, _) if key.toLowerCase().startsWith("data-") => - val nk = key.substring(5).toLowerCase() - val vs = value.text - val a2 = attrs.filter{ + val dataProcessorName = key.substring(5).toLowerCase() + val dataProcessorInputValue = value.text + val filteredAttributes = element.attributes.filter{ case UnprefixedAttribute(k2, _, _) => k2 != key case _ => true } - NamedPF.applyBox((nk, vs, new Elem(e.prefix, e.label, a2, e.scope, e.minimizeEmpty, e.child :_*), LiftSession.this), rules) + NamedPF.applyBox( + (dataProcessorName, dataProcessorInputValue, element.copy(attributes = filteredAttributes), LiftSession.this), + rules + ) case _ => Empty }.headOption From 8029fad9dd9eef2a0018f4b5a024a8c6278db9bc Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Fri, 18 Apr 2014 17:54:37 -0400 Subject: [PATCH 1009/1949] Clean up snippy, rip out its processing of data-lift. --- .../scala/net/liftweb/http/LiftSession.scala | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index 4f6ff32a55..46b1b563e7 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -3026,19 +3026,24 @@ private object SnippetNode { private def isLiftClass(s: String): Boolean = s.startsWith("lift:") || s.startsWith("l:") - private def snippy(in: Elem): Option[(String, MetaData)] = - ((for { - cls <- in.attribute("class") - snip <- cls.text.charSplit(' ').find(isLiftClass) - } yield snip) orElse in.attribute("lift").map(_.text) - orElse in.attribute("data-lift").map(_.text)).map { - snip => - snip.charSplit('?') match { - case Nil => "this should never happen" -> Null - case x :: Nil => urlDecode(removeLift(x)) -> Null - case x :: xs => urlDecode(removeLift(x)) -> pairsToMetaData(xs.flatMap(_.roboSplit("[;&]"))) - } + private def snippy(in: Elem): Option[(String, MetaData)] = { + val snippetContents = { + for { + cls <- in.attribute("class") + snip <- cls.text.charSplit(' ').find(isLiftClass) + } yield { + snip + } + } orElse in.attribute("lift").map(_.text) + + snippetContents.map { snip => + snip.charSplit('?') match { + case Nil => "this should never happen" -> Null + case x :: Nil => urlDecode(removeLift(x)) -> Null + case x :: xs => urlDecode(removeLift(x)) -> pairsToMetaData(xs.flatMap(_.roboSplit("[;&]"))) + } } + } private def liftAttrsAndParallel(in: MetaData): (Boolean, MetaData) = { var next = in From 638edababc2dc455f430bf867ad59a187d394490 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Fri, 18 Apr 2014 17:44:11 -0400 Subject: [PATCH 1010/1949] Initial implementation of data-lift as a DataAttributeProcessor. --- .../scala/net/liftweb/http/LiftRules.scala | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 38eecfc635..1ab6575858 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -897,6 +897,42 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { */ val dataAttributeProcessor: RulesSeq[DataAttributeProcessor] = new RulesSeq() + private def makeMetaData(key: String, value: String, rest: MetaData): MetaData = key.indexOf(":") match { + case x if x > 0 => new PrefixedAttribute(key.substring(0, x), + key.substring(x + 1), + value, rest) + + case _ => new UnprefixedAttribute(key, value, rest) + } + + private def pairsToMetaData(in: List[String]): MetaData = in match { + case Nil => Null + case x :: xs => { + val rest = pairsToMetaData(xs) + x.charSplit('=').map(Helpers.urlDecode) match { + case Nil => rest + case x :: Nil => makeMetaData(x, "", rest) + case x :: y :: _ => makeMetaData(x, y, rest) + } + } + } + + dataAttributeProcessor.append { + case ("lift", dataLiftContents, element, liftSession) => + dataLiftContents.charSplit('?') match { + case Nil => + // This shouldn't ever happen. + NodeSeq.Empty + + case snippetName :: Nil => + new Elem("lift", snippetName, Null, element.scope, false, element) + + case snippetName :: encodedArguments => + val decodedMetaData = pairsToMetaData(encodedArguments.flatMap(_.roboSplit("[;&]"))) + new Elem("lift", snippetName, decodedMetaData, element.scope, false, element) + } + } + /** * Ever wanted to match on *any* arbitrary tag in your HTML and process it * any way you wanted? Well, here's your chance, dude. You can capture any From 74f75e0b49d36f1b69226f6a57d8dce3f9602e81 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sun, 3 Aug 2014 15:36:44 -0400 Subject: [PATCH 1011/1949] Some spec format tweaks. --- .../net/liftweb/webapptest/snippet/DeferredSnippet.scala | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/web/webkit/src/test/scala/net/liftweb/webapptest/snippet/DeferredSnippet.scala b/web/webkit/src/test/scala/net/liftweb/webapptest/snippet/DeferredSnippet.scala index 48d4578cc1..27e01b2255 100644 --- a/web/webkit/src/test/scala/net/liftweb/webapptest/snippet/DeferredSnippet.scala +++ b/web/webkit/src/test/scala/net/liftweb/webapptest/snippet/DeferredSnippet.scala @@ -40,10 +40,9 @@ class DeferredSnippet { } def stackWhack: NodeSeq = { - val inActor: Boolean = Thread.currentThread.getStackTrace. - find(_.getClassName.contains("net.liftweb.actor.")).isDefined + val inActor: Boolean = Thread.currentThread.getStackTrace.find(_.getClassName.contains("net.liftweb.actor.")).isDefined - stackWhack + stackWhack } } From bd674b6a1f28c0de5421560b68a01193ba09dbf5 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sun, 3 Aug 2014 15:37:00 -0400 Subject: [PATCH 1012/1949] Explicitly return a future answer if parallel=true. --- .../src/main/scala/net/liftweb/http/LiftRules.scala | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 1ab6575858..b817c2963e 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -929,7 +929,14 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { case snippetName :: encodedArguments => val decodedMetaData = pairsToMetaData(encodedArguments.flatMap(_.roboSplit("[;&]"))) - new Elem("lift", snippetName, decodedMetaData, element.scope, false, element) + + if (decodedMetaData("parallel").headOption == Some(Text("true"))) { + DataAttributeProcessorAnswerFuture(LAFuture(() => + new Elem("lift", snippetName, decodedMetaData, element.scope, false, element) + )) + } else { + new Elem("lift", snippetName, decodedMetaData, element.scope, false, element) + } } } From 31497e51c50a86638bedd73a1897fad3a7d172a7 Mon Sep 17 00:00:00 2001 From: Peter Brant Date: Mon, 4 Aug 2014 11:35:48 +0200 Subject: [PATCH 1013/1949] Fix pattern match (again!) --- web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala index 60fb16e6a4..53b95706aa 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala @@ -569,9 +569,9 @@ class LiftServlet extends Loggable { private def extractVersions[T](path: List[String])(f: (Box[AjaxVersionInfo]) => T): T = { val liftPath = LiftRules.liftPath path match { - case liftPath :: "ajax" :: AjaxVersions(versionInfo @ AjaxVersionInfo(renderVersion, _, _)) :: _ => + case `liftPath` :: "ajax" :: AjaxVersions(versionInfo @ AjaxVersionInfo(renderVersion, _, _)) :: _ => RenderVersion.doWith(renderVersion)(f(Full(versionInfo))) - case liftPath :: "ajax" :: renderVersion :: _ => + case `liftPath` :: "ajax" :: renderVersion :: _ => RenderVersion.doWith(renderVersion)(f(Empty)) case _ => f(Empty) } From 7dae49c28fb62e01b16d0f037de3c3a7959d37f6 Mon Sep 17 00:00:00 2001 From: Peter Brant Date: Mon, 4 Aug 2014 11:50:42 +0200 Subject: [PATCH 1014/1949] Adjust pattern match for consistency --- .../src/main/scala/net/liftweb/http/LiftServlet.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala index 53b95706aa..cde2c22a3d 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala @@ -567,11 +567,11 @@ class LiftServlet extends Loggable { * The requestVersion is passed to the function that is passed in. */ private def extractVersions[T](path: List[String])(f: (Box[AjaxVersionInfo]) => T): T = { - val liftPath = LiftRules.liftPath + val LiftPath = LiftRules.liftPath path match { - case `liftPath` :: "ajax" :: AjaxVersions(versionInfo @ AjaxVersionInfo(renderVersion, _, _)) :: _ => + case LiftPath :: "ajax" :: AjaxVersions(versionInfo @ AjaxVersionInfo(renderVersion, _, _)) :: _ => RenderVersion.doWith(renderVersion)(f(Full(versionInfo))) - case `liftPath` :: "ajax" :: renderVersion :: _ => + case LiftPath :: "ajax" :: renderVersion :: _ => RenderVersion.doWith(renderVersion)(f(Empty)) case _ => f(Empty) } From 75b05c3e211ac902f1bdc763d00f83ac43aba798 Mon Sep 17 00:00:00 2001 From: Peter Brant Date: Mon, 4 Aug 2014 12:41:21 +0200 Subject: [PATCH 1015/1949] Include context path in liftPath --- web/webkit/src/main/resources/toserve/lift.js | 23 +++++++------ .../scala/net/liftweb/http/LiftMerge.scala | 2 +- .../scala/net/liftweb/http/LiftRules.scala | 8 +++-- .../scala/net/liftweb/http/LiftServlet.scala | 8 ++--- .../src/main/scala/net/liftweb/http/Req.scala | 2 +- .../net/liftweb/http/js/LiftJavaScript.scala | 34 ++++++++++--------- .../liftweb/http/js/LiftJavaScriptSpec.scala | 14 ++++---- 7 files changed, 49 insertions(+), 42 deletions(-) diff --git a/web/webkit/src/main/resources/toserve/lift.js b/web/webkit/src/main/resources/toserve/lift.js index f2e92770f0..29dcb6fbb5 100644 --- a/web/webkit/src/main/resources/toserve/lift.js +++ b/web/webkit/src/main/resources/toserve/lift.js @@ -21,7 +21,7 @@ /** * Contains the Ajax URI path used by Lift to process Ajax requests. */ - liftPath: "lift", + liftPath: "/lift", ajaxRetryCount: 3, ajaxPostTimeout: 5000, @@ -72,8 +72,7 @@ cometOnSessionLost: function() { window.location.href = "/"; }, - cometServer: "", - contextPath: "", + cometServer: null, cometOnError: function(e) { if (window.console && typeof window.console.error === 'function') { window.console.error(e.stack || e); @@ -165,16 +164,15 @@ }*/ function calcAjaxUrl(url, version) { - var serverUrl = settings.contextPath + url; if (settings.enableGc) { var replacement = ajaxPath()+'/'+pageId; if (version !== null) { replacement += ('-'+version.toString(36)) + (ajaxQueue.length > 35 ? 35 : ajaxQueue.length).toString(36); } - return serverUrl.replace(ajaxPath(), replacement); + return url.replace(ajaxPath(), replacement); } else { - return serverUrl; + return url; } } @@ -182,7 +180,7 @@ var data = "__lift__GC=_"; settings.ajaxPost( - calcAjaxUrl("/"+ajaxPath()+"/", null), + calcAjaxUrl(ajaxPath()+"/", null), data, "script", successRegisterGC, @@ -257,7 +255,7 @@ aboutToSend.responseType.toLowerCase() === "json") { settings.ajaxPost( - calcAjaxUrl("/"+ajaxPath()+"/", null), + calcAjaxUrl(ajaxPath()+"/", null), aboutToSend.data, "json", successFunc, @@ -267,7 +265,7 @@ } else { settings.ajaxPost( - calcAjaxUrl("/"+ajaxPath()+"/", aboutToSend.version), + calcAjaxUrl(ajaxPath()+"/", aboutToSend.version), aboutToSend.data, "script", successFunc, @@ -321,7 +319,12 @@ } function calcCometPath() { - return settings.cometServer+"/"+cometPath()+"/" + Math.floor(Math.random() * 100000000000) + "/" + sessionId + "/" + pageId; + var fullPath = cometPath()+ "/" + Math.floor(Math.random() * 100000000000) + "/" + sessionId + "/" + pageId; + if (settings.cometServer) { + return settings.cometServer + fullPath; + } else { + return fullPath; + } } function cometEntry() { diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala index 92f3d101ad..fe443b6b0c 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala @@ -28,7 +28,7 @@ private[http] trait LiftMerge { self: LiftSession => private def scriptUrl(scriptFile: String) = { - S.encodeURL(s"${S.contextPath}/${LiftRules.liftPath}/$scriptFile") + S.encodeURL(s"${LiftRules.liftPath}/$scriptFile") } // Gather all page-specific JS into one JsCmd. diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 1044699669..f47ea52b6c 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -325,7 +325,7 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { */ private def _getLiftSession(req: Req): LiftSession = { val wp = req.path.wholePath - val LiftPath = LiftRules.liftPath + val LiftPath = LiftRules.liftUriPath val cometSessionId = wp match { case LiftPath :: "comet" :: _ :: session :: _ => Full(session) case _ => Empty @@ -442,7 +442,7 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { * the request was made on, but can do the multi-server thing * as well) */ - @volatile var cometServer: () => String = () => S.contextPath + @volatile var cometServer: () => Option[String] = () => None /** * The maximum concurrent requests. If this number of @@ -1156,7 +1156,9 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { * Contains the URI path under which all built-in Lift-handled * requests are scoped. */ - @volatile var liftPath = "lift" + @volatile var liftUriPath = "lift" + + def liftPath: String = S.contextPath + "/" + liftUriPath /** * If there is an alternative way of calculating the context path diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala index cde2c22a3d..dae2ee36a3 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala @@ -306,8 +306,8 @@ class LiftServlet extends Loggable { } def cometOrAjax_?(req: Req): (Boolean, Boolean) = { - lazy val ajaxPath = LiftRules.liftPath :: "ajax" :: Nil - lazy val cometPath = LiftRules.liftPath :: "comet" :: Nil + lazy val ajaxPath = LiftRules.liftUriPath :: "ajax" :: Nil + lazy val cometPath = LiftRules.liftUriPath :: "comet" :: Nil val wp = req.path.wholePath val pathLen = wp.length @@ -567,7 +567,7 @@ class LiftServlet extends Loggable { * The requestVersion is passed to the function that is passed in. */ private def extractVersions[T](path: List[String])(f: (Box[AjaxVersionInfo]) => T): T = { - val LiftPath = LiftRules.liftPath + val LiftPath = LiftRules.liftUriPath path match { case LiftPath :: "ajax" :: AjaxVersions(versionInfo @ AjaxVersionInfo(renderVersion, _, _)) :: _ => RenderVersion.doWith(renderVersion)(f(Full(versionInfo))) @@ -1038,7 +1038,7 @@ class LiftServlet extends Loggable { import net.liftweb.http.provider.servlet._ private class SessionIdCalc(req: Req) { - private val LiftPath = LiftRules.liftPath + private val LiftPath = LiftRules.liftUriPath lazy val id: Box[String] = req.request.sessionId match { case Full(id) => Full(id) case _ => req.path.wholePath match { diff --git a/web/webkit/src/main/scala/net/liftweb/http/Req.scala b/web/webkit/src/main/scala/net/liftweb/http/Req.scala index 75a0ca75bc..b79e58aef5 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Req.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Req.scala @@ -868,7 +868,7 @@ class Req(val path: ParsePath, * A request that is neither Ajax or Comet */ lazy val standardRequest_? : Boolean = path.partPath match { - case x :: _ if x == LiftRules.liftPath => false + case x :: _ if x == LiftRules.liftUriPath => false case _ => true } diff --git a/web/webkit/src/main/scala/net/liftweb/http/js/LiftJavaScript.scala b/web/webkit/src/main/scala/net/liftweb/http/js/LiftJavaScript.scala index f6b3d890a7..1cf3b2fac8 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/js/LiftJavaScript.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/js/LiftJavaScript.scala @@ -37,7 +37,7 @@ object LiftJavaScript { object PageJs { def unapply(req: Req): Option[JavaScriptResponse] = { val suffixedPath = req.path.wholePath - val LiftPath = LiftRules.liftPath + val LiftPath = LiftRules.liftUriPath val renderVersion = "([^.]+)\\.js".r suffixedPath match { @@ -56,21 +56,23 @@ object LiftJavaScript { () => Full(response) } - def settings: JsObj = JsObj( - "liftPath" -> LiftRules.liftPath, - "ajaxRetryCount" -> Num(LiftRules.ajaxRetryCount.openOr(3)), - "ajaxPostTimeout" -> LiftRules.ajaxPostTimeout, - "gcPollingInterval" -> LiftRules.liftGCPollingInterval, - "gcFailureRetryTimeout" -> LiftRules.liftGCFailureRetryTimeout, - "cometGetTimeout" -> LiftRules.cometGetTimeout, - "cometFailureRetryTimeout" -> LiftRules.cometFailureRetryTimeout, - "cometServer" -> LiftRules.cometServer(), - "contextPath" -> S.contextPath, - "logError" -> LiftRules.jsLogFunc.map(fnc => AnonFunc("msg", fnc(JsVar("msg")))).openOr(AnonFunc("msg", Noop)), - "ajaxOnFailure" -> LiftRules.ajaxDefaultFailure.map(fnc => AnonFunc(fnc())).openOr(AnonFunc(Noop)), - "ajaxOnStart" -> LiftRules.ajaxStart.map(fnc => AnonFunc(fnc())).openOr(AnonFunc(Noop)), - "ajaxOnEnd" -> LiftRules.ajaxEnd.map(fnc => AnonFunc(fnc())).openOr(AnonFunc(Noop)) - ) + def settings: JsObj = { + val jsCometServer = LiftRules.cometServer().map(Str(_)).getOrElse(JsNull) + JsObj( + "liftPath" -> LiftRules.liftPath, + "ajaxRetryCount" -> Num(LiftRules.ajaxRetryCount.openOr(3)), + "ajaxPostTimeout" -> LiftRules.ajaxPostTimeout, + "gcPollingInterval" -> LiftRules.liftGCPollingInterval, + "gcFailureRetryTimeout" -> LiftRules.liftGCFailureRetryTimeout, + "cometGetTimeout" -> LiftRules.cometGetTimeout, + "cometFailureRetryTimeout" -> LiftRules.cometFailureRetryTimeout, + "cometServer" -> jsCometServer, + "logError" -> LiftRules.jsLogFunc.map(fnc => AnonFunc("msg", fnc(JsVar("msg")))).openOr(AnonFunc("msg", Noop)), + "ajaxOnFailure" -> LiftRules.ajaxDefaultFailure.map(fnc => AnonFunc(fnc())).openOr(AnonFunc(Noop)), + "ajaxOnStart" -> LiftRules.ajaxStart.map(fnc => AnonFunc(fnc())).openOr(AnonFunc(Noop)), + "ajaxOnEnd" -> LiftRules.ajaxEnd.map(fnc => AnonFunc(fnc())).openOr(AnonFunc(Noop)) + ) + } def initCmd(settings: JsObj): JsCmd = { val extendCmd = LiftRules.jsArtifacts match { diff --git a/web/webkit/src/test/scala/net/liftweb/http/js/LiftJavaScriptSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/js/LiftJavaScriptSpec.scala index 5f5f3accc5..6f8d5391cc 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/js/LiftJavaScriptSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/js/LiftJavaScriptSpec.scala @@ -40,35 +40,35 @@ object LiftJavaScriptSpec extends Specification { "create default settings" in { S.initIfUninitted(session) { val settings = LiftJavaScript.settings - settings.toJsCmd must_== """{"liftPath": "lift", "ajaxRetryCount": 3, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "", "contextPath": "", "logError": function(msg) {}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}""" + settings.toJsCmd must_== """{"liftPath": "/lift", "ajaxRetryCount": 3, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": null, "logError": function(msg) {}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}""" } } "create custom static settings" in { S.initIfUninitted(session) { LiftRules.ajaxRetryCount = Full(4) val settings = LiftJavaScript.settings - settings.toJsCmd must_== """{"liftPath": "lift", "ajaxRetryCount": 4, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "", "contextPath": "", "logError": function(msg) {}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}""" + settings.toJsCmd must_== """{"liftPath": "/lift", "ajaxRetryCount": 4, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": null, "logError": function(msg) {}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}""" } } "create custom dynamic settings" in { S.initIfUninitted(session) { - LiftRules.cometServer = () => "srvr1" + LiftRules.cometServer = () => Some("srvr1") val settings = LiftJavaScript.settings - settings.toJsCmd must_== """{"liftPath": "lift", "ajaxRetryCount": 4, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "srvr1", "contextPath": "", "logError": function(msg) {}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}""" + settings.toJsCmd must_== """{"liftPath": "/lift", "ajaxRetryCount": 4, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "srvr1", "logError": function(msg) {}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}""" } } "create custom function settings" in { S.initIfUninitted(session) { LiftRules.jsLogFunc = Full(v => JE.Call("lift.logError", v)) val settings = LiftJavaScript.settings - settings.toJsCmd must_== """{"liftPath": "lift", "ajaxRetryCount": 4, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "srvr1", "contextPath": "", "logError": function(msg) {lift.logError(msg);}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}""" + settings.toJsCmd must_== """{"liftPath": "/lift", "ajaxRetryCount": 4, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "srvr1", "logError": function(msg) {lift.logError(msg);}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}""" } } "create init command" in { S.initIfUninitted(session) { val init = LiftRules.javaScriptSettings.vend().map(_.apply(session)).map(LiftJavaScript.initCmd(_).toJsCmd) init must_== - Full("""var lift_settings = {"liftPath": "lift", "ajaxRetryCount": 4, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "srvr1", "contextPath": "", "logError": function(msg) {lift.logError(msg);}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}; + Full("""var lift_settings = {"liftPath": "/lift", "ajaxRetryCount": 4, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "srvr1", "logError": function(msg) {lift.logError(msg);}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}; |window.lift.extend(lift_settings,window.liftJQuery); |window.lift.init(lift_settings);""".stripMargin) } @@ -79,7 +79,7 @@ object LiftJavaScriptSpec extends Specification { val init = LiftJavaScript.initCmd(settings) println(init.toJsCmd) init.toJsCmd must_== - """var lift_settings = {"liftPath": "liftyStuff", "ajaxRetryCount": 4, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "srvr1", "contextPath": "", "logError": function(msg) {lift.logError(msg);}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}, "mysetting": 99}; + """var lift_settings = {"liftPath": "liftyStuff", "ajaxRetryCount": 4, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "srvr1", "logError": function(msg) {lift.logError(msg);}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}, "mysetting": 99}; |window.lift.extend(lift_settings,window.liftJQuery); |window.lift.init(lift_settings);""".stripMargin } From 26a0761aa82f2d46a89fd2a25a12afa77ba1370b Mon Sep 17 00:00:00 2001 From: Peter Brant Date: Tue, 5 Aug 2014 17:55:02 +0200 Subject: [PATCH 1016/1949] Add Scaladoc and rename liftUriPath --- .../main/scala/net/liftweb/http/LiftRules.scala | 15 ++++++++++----- .../main/scala/net/liftweb/http/LiftServlet.scala | 8 ++++---- .../src/main/scala/net/liftweb/http/Req.scala | 2 +- .../net/liftweb/http/js/LiftJavaScript.scala | 2 +- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index f47ea52b6c..1edce8a4dc 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -325,7 +325,7 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { */ private def _getLiftSession(req: Req): LiftSession = { val wp = req.path.wholePath - val LiftPath = LiftRules.liftUriPath + val LiftPath = LiftRules.liftContextRelativePath val cometSessionId = wp match { case LiftPath :: "comet" :: _ :: session :: _ => Full(session) case _ => Empty @@ -1153,12 +1153,17 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { } /** - * Contains the URI path under which all built-in Lift-handled - * requests are scoped. + * Contains the URI path under which all built-in Lift-handled requests are + * scoped. It does not include the context path and should not begin with a + * /. */ - @volatile var liftUriPath = "lift" + @volatile var liftContextRelativePath = "lift" - def liftPath: String = S.contextPath + "/" + liftUriPath + /** + * Returns a complete URI, including the context path, under which all + * built-in Lift-handled requests are scoped. + */ + def liftPath: String = S.contextPath + "/" + liftContextRelativePath /** * If there is an alternative way of calculating the context path diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala index dae2ee36a3..73d1779285 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala @@ -306,8 +306,8 @@ class LiftServlet extends Loggable { } def cometOrAjax_?(req: Req): (Boolean, Boolean) = { - lazy val ajaxPath = LiftRules.liftUriPath :: "ajax" :: Nil - lazy val cometPath = LiftRules.liftUriPath :: "comet" :: Nil + lazy val ajaxPath = LiftRules.liftContextRelativePath :: "ajax" :: Nil + lazy val cometPath = LiftRules.liftContextRelativePath :: "comet" :: Nil val wp = req.path.wholePath val pathLen = wp.length @@ -567,7 +567,7 @@ class LiftServlet extends Loggable { * The requestVersion is passed to the function that is passed in. */ private def extractVersions[T](path: List[String])(f: (Box[AjaxVersionInfo]) => T): T = { - val LiftPath = LiftRules.liftUriPath + val LiftPath = LiftRules.liftContextRelativePath path match { case LiftPath :: "ajax" :: AjaxVersions(versionInfo @ AjaxVersionInfo(renderVersion, _, _)) :: _ => RenderVersion.doWith(renderVersion)(f(Full(versionInfo))) @@ -1038,7 +1038,7 @@ class LiftServlet extends Loggable { import net.liftweb.http.provider.servlet._ private class SessionIdCalc(req: Req) { - private val LiftPath = LiftRules.liftUriPath + private val LiftPath = LiftRules.liftContextRelativePath lazy val id: Box[String] = req.request.sessionId match { case Full(id) => Full(id) case _ => req.path.wholePath match { diff --git a/web/webkit/src/main/scala/net/liftweb/http/Req.scala b/web/webkit/src/main/scala/net/liftweb/http/Req.scala index b79e58aef5..d5af23d6b2 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Req.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Req.scala @@ -868,7 +868,7 @@ class Req(val path: ParsePath, * A request that is neither Ajax or Comet */ lazy val standardRequest_? : Boolean = path.partPath match { - case x :: _ if x == LiftRules.liftUriPath => false + case x :: _ if x == LiftRules.liftContextRelativePath => false case _ => true } diff --git a/web/webkit/src/main/scala/net/liftweb/http/js/LiftJavaScript.scala b/web/webkit/src/main/scala/net/liftweb/http/js/LiftJavaScript.scala index 1cf3b2fac8..dd09deba34 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/js/LiftJavaScript.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/js/LiftJavaScript.scala @@ -37,7 +37,7 @@ object LiftJavaScript { object PageJs { def unapply(req: Req): Option[JavaScriptResponse] = { val suffixedPath = req.path.wholePath - val LiftPath = LiftRules.liftUriPath + val LiftPath = LiftRules.liftContextRelativePath val renderVersion = "([^.]+)\\.js".r suffixedPath match { From 3b5a0bf44297f3ea265dfa29f00a61d36e8097f2 Mon Sep 17 00:00:00 2001 From: Peter Brant Date: Tue, 29 Jul 2014 21:10:45 +0200 Subject: [PATCH 1017/1949] Remove automatic lift:surround with="default" insertion Remove automatic lift:surround with="default" insertion. Continue to wrap Markdown templates in if they contain multiple top level nodes (but only if is not already there). --- .../scala/net/liftweb/http/LiftSession.scala | 18 ++------------- .../scala/net/liftweb/http/Templates.scala | 23 +++++++++++++++---- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index 1ede2d8280..e5ac7a5d46 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -1516,22 +1516,8 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, case s@_ if !s.isEmpty => s case _ => List("index") } - Templates(splits, S.locale).map { - case e: Elem if e.label == "html" => e - case e: Elem if hasSurround(e) => e - case x => - {x} - - } - } - - private def hasSurround(e: Elem): Boolean = - (S.location.isDefined) && - (S.request.map(!_.ajax_?) openOr false) && - ((e.attribute("data-lift").map(_.text.startsWith("surround")) getOrElse false) || - (e.attribute("lift").map(_.text.startsWith("surround")) getOrElse false) || - (e.label == "surround") || - (e.attribute("class").map(_.text.contains("surround")) getOrElse false)) + Templates.findTopLevelTemplate(splits, S.locale) + } private[liftweb] def findTemplate(name: String): Box[NodeSeq] = { val splits = (if (name.startsWith("/")) name else "/" + name).split("/").toList.drop(1) match { diff --git a/web/webkit/src/main/scala/net/liftweb/http/Templates.scala b/web/webkit/src/main/scala/net/liftweb/http/Templates.scala index ff1a294544..8cf8711fcf 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Templates.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Templates.scala @@ -58,6 +58,10 @@ object Templates { } } + private [http] def findTopLevelTemplate(places: List[String], locale: Locale) = { + findRawTemplate0(places, locale, true).map(checkForContentId) + } + /** * Given a list of paths (e.g. List("foo", "index")), * find the template. This method runs checkForContentId @@ -130,11 +134,16 @@ object Templates { }.headOption getOrElse in } - private def parseMarkdown(is: InputStream): Box[NodeSeq] = + private def parseMarkdown(is: InputStream, topLevel: Boolean): Box[NodeSeq] = for { bytes <- Helpers.tryo(Helpers.readWholeStream(is)) elems <- MarkdownParser.parse(new String(bytes, "UTF-8")) - } yield elems + } yield { + if (topLevel && elems.length > 1 && ! elems.exists(_.label == "html")) + {elems} + else + elems + } /** * Given a list of paths (e.g. List("foo", "index")), @@ -145,6 +154,10 @@ object Templates { * @return the template if it can be found */ def findRawTemplate(places: List[String], locale: Locale): Box[NodeSeq] = { + findRawTemplate0(places, locale, false) + } + + private def findRawTemplate0(places: List[String], locale: Locale, topLevel: Boolean): Box[NodeSeq] = { /* From a Scala coding standpoint, this method is ugly. It's also a performance hotspot that needed some tuning. I've made the code very imperative and @@ -196,9 +209,9 @@ object Templates { val name = pls + p + (if (s.length > 0) "." + s else "") import scala.xml.dtd.ValidationException val xmlb = try { - LiftRules.doWithResource(name) { - if (s == "md") {parseMarkdown} else - parserFunction } match { + LiftRules.doWithResource(name) { is => + if (s == "md") {parseMarkdown(is, topLevel)} else + parserFunction(is) } match { case Full(seq) => seq case _ => Empty } From 6b22501d4008d276b03868fc6ab8f652d8bdffd3 Mon Sep 17 00:00:00 2001 From: Peter Brant Date: Thu, 7 Aug 2014 10:54:16 +0200 Subject: [PATCH 1018/1949] Auto-surround top level Markdown templates Auto-surround top level Markdown templates with default template and refine test for top level templates --- .../main/scala/net/liftweb/http/LiftSession.scala | 6 +++++- .../main/scala/net/liftweb/http/Templates.scala | 14 +++++++------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index e5ac7a5d46..6419035ca7 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -1516,7 +1516,11 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, case s@_ if !s.isEmpty => s case _ => List("index") } - Templates.findTopLevelTemplate(splits, S.locale) + Templates.findTopLevelTemplate( + splits, + S.locale, + S.location.isDefined && S.request.exists(!_.ajax_?) + ) } private[liftweb] def findTemplate(name: String): Box[NodeSeq] = { diff --git a/web/webkit/src/main/scala/net/liftweb/http/Templates.scala b/web/webkit/src/main/scala/net/liftweb/http/Templates.scala index 8cf8711fcf..d8e7a2e7fe 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Templates.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Templates.scala @@ -58,8 +58,8 @@ object Templates { } } - private [http] def findTopLevelTemplate(places: List[String], locale: Locale) = { - findRawTemplate0(places, locale, true).map(checkForContentId) + private [http] def findTopLevelTemplate(places: List[String], locale: Locale, needAutoSurround: Boolean) = { + findRawTemplate0(places, locale, needAutoSurround).map(checkForContentId) } /** @@ -134,13 +134,13 @@ object Templates { }.headOption getOrElse in } - private def parseMarkdown(is: InputStream, topLevel: Boolean): Box[NodeSeq] = + private def parseMarkdown(is: InputStream, needAutoSurround: Boolean): Box[NodeSeq] = for { bytes <- Helpers.tryo(Helpers.readWholeStream(is)) elems <- MarkdownParser.parse(new String(bytes, "UTF-8")) } yield { - if (topLevel && elems.length > 1 && ! elems.exists(_.label == "html")) - {elems} + if (needAutoSurround) + {elems} else elems } @@ -157,7 +157,7 @@ object Templates { findRawTemplate0(places, locale, false) } - private def findRawTemplate0(places: List[String], locale: Locale, topLevel: Boolean): Box[NodeSeq] = { + private def findRawTemplate0(places: List[String], locale: Locale, needAutoSurround: Boolean): Box[NodeSeq] = { /* From a Scala coding standpoint, this method is ugly. It's also a performance hotspot that needed some tuning. I've made the code very imperative and @@ -210,7 +210,7 @@ object Templates { import scala.xml.dtd.ValidationException val xmlb = try { LiftRules.doWithResource(name) { is => - if (s == "md") {parseMarkdown(is, topLevel)} else + if (s == "md") {parseMarkdown(is, needAutoSurround)} else parserFunction(is) } match { case Full(seq) => seq case _ => Empty From e94bd4cf2ef2b8b59fc9a7c4b073e7647c92f5fc Mon Sep 17 00:00:00 2001 From: Peter Brant Date: Thu, 7 Aug 2014 20:31:47 +0200 Subject: [PATCH 1019/1949] Move to Scala 2.11.2 --- build.sbt | 4 ++-- project/Build.scala | 4 ++-- project/Dependencies.scala | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/build.sbt b/build.sbt index 700c65a566..5b79517ff9 100644 --- a/build.sbt +++ b/build.sbt @@ -12,9 +12,9 @@ startYear in ThisBuild := Some(2006) organizationName in ThisBuild := "WorldWide Conferencing, LLC" -scalaVersion in ThisBuild := "2.11.1" +scalaVersion in ThisBuild := "2.11.2" -crossScalaVersions in ThisBuild := Seq("2.11.1") +crossScalaVersions in ThisBuild := Seq("2.11.2") libraryDependencies in ThisBuild <++= scalaVersion {sv => Seq(specs2(sv), scalacheck, scalatest(sv)) } diff --git a/project/Build.scala b/project/Build.scala index 2b4c8f50bb..9d02131cd1 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -41,7 +41,7 @@ object BuildDef extends Build { .settings(description := "Common Libraties and Utilities", libraryDependencies ++= Seq(slf4j_api, logback, slf4j_log4j12), libraryDependencies <++= scalaVersion { - case "2.11.0" | "2.11.1" => Seq(scala_xml, scala_parser) + case "2.11.2" => Seq(scala_xml, scala_parser) case _ => Seq() } ) @@ -58,7 +58,7 @@ object BuildDef extends Build { parallelExecution in Test := false, libraryDependencies <++= scalaVersion { sv => Seq(scalatest(sv), junit) }, libraryDependencies <++= scalaVersion { - case "2.11.0" | "2.11.1" => Seq(scala_xml, scala_parser) + case "2.11.2" => Seq(scala_xml, scala_parser) case _ => Seq() } ) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 823358c456..d4e31d31bd 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -23,15 +23,15 @@ object Dependencies { type ModuleMap = String => ModuleID - lazy val CVMappingAll = crossMapped("2.10.0" -> "2.10", "2.10.1" -> "2.10", "2.10.2" -> "2.10", "2.10.3" -> "2.10", "2.10.4" -> "2.10", "2.11.0" -> "2.11", "2.11.1" -> "2.11") + lazy val CVMappingAll = crossMapped("2.10.0" -> "2.10", "2.10.1" -> "2.10", "2.10.2" -> "2.10", "2.10.3" -> "2.10", "2.10.4" -> "2.10", "2.11.0" -> "2.11", "2.11.1" -> "2.11", "2.11.2" -> "2.11") lazy val slf4jVersion = "1.7.2" lazy val scalazGroup = defaultOrMapped("org.scalaz") lazy val scalazVersion = defaultOrMapped("6.0.4") - lazy val scalaz7Version = defaultOrMapped("7.0.0", "2.11.0" -> "7.0.6", "2.11.1" -> "7.0.6") - lazy val specs2Version = defaultOrMapped("1.12.3", "2.11.0" -> "2.3.11", "2.11.1" -> "2.3.11") - lazy val scalatestVersion = defaultOrMapped("1.9.1", "2.11.0" -> "2.1.3", "2.11.1" -> "2.1.3") + lazy val scalaz7Version = defaultOrMapped("7.0.0", "2.11.2" -> "7.0.6") + lazy val specs2Version = defaultOrMapped("1.12.3", "2.11.2" -> "2.3.11") + lazy val scalatestVersion = defaultOrMapped("1.9.1", "2.11.2" -> "2.1.3") // Compile scope: // Scope available in all classpath, transitive by default. From 26631912c9d1fef938fd483e5571a49e29a258c8 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Fri, 8 Aug 2014 23:56:50 -0400 Subject: [PATCH 1020/1949] Rework LazyLoad comet. We move the AsyncRenderComet to its own file. We also move the actual render function management into the AsyncRenderComet companion, including handling deferred function building. LazyLoad slims down a little, dealing with invoking the async render setup in AsyncRenderComet and rendering the placeholder. We also only apply a div wrapper to the default loading template, letting a custom template stand on its own, though we apply an id to it for the purposes of replacing it with the rendered contents after they are sent down. --- .../builtin/comet/AsyncRenderComet.scala | 119 ++++++++++++++++ .../liftweb/builtin/snippet/LazyLoad.scala | 132 +++++------------- 2 files changed, 151 insertions(+), 100 deletions(-) create mode 100644 web/webkit/src/main/scala/net/liftweb/builtin/comet/AsyncRenderComet.scala diff --git a/web/webkit/src/main/scala/net/liftweb/builtin/comet/AsyncRenderComet.scala b/web/webkit/src/main/scala/net/liftweb/builtin/comet/AsyncRenderComet.scala new file mode 100644 index 0000000000..741b193043 --- /dev/null +++ b/web/webkit/src/main/scala/net/liftweb/builtin/comet/AsyncRenderComet.scala @@ -0,0 +1,119 @@ +package net.liftweb +package builtin +package comet + +import scala.xml.NodeSeq + +import common._ +import http._ + import js._ +import util._ + import Helpers._ + +case class Compute(js: () => JsCmd) +private case class Render(js: JsCmd) + +/** + * `AsyncRenderComet` facilitates rendering anything that produces a `JsCmd` + * independently from a page request. All you have to do is create one and + * send it a `Compute` message with the function that will produce the `JsCmd`. + * `AsyncRenderComet` will take ownership of the function and run it in + * a separate thread comet context, sending the results down using a + * `partialUpdate` when it is done. + * + * Note that if you want to run a function that requires the context of the + * request you're running in, you'll want to use `LiftSession`'s + * `buildDeferredFunction` method to make sure that when the function is + * executed in a separate thread, it will retain request context. + * + * In general, consider using one of: + * - `AsyncRenderComet.asyncRender`. + * - The `lazy` snippet. + * - The `CanBind` implicits in the `net.liftweb.http` package that allow using + * `LAFuture` and Scala `Future` objects as the right-hand-side of a CSS + * selector binding. + * + * None of these requires explicit use of `buildDeferredFunction`. + */ +class AsyncRenderComet extends CometActor { + + override def lifespan: Box[TimeSpan] = Full(90.seconds) + + def render = NodeSeq.Empty + + // make this method visible so that we can initialize the actor + override def initCometActor(creationInfo: CometCreationInfo) { + super.initCometActor(creationInfo) + } + + // FIXME add lifecycle management that will nuke the comet here and on the + // FIXME client when nothing is left to compute + override def lowPriority : PartialFunction[Any, Unit] = { + // farm the request off to another thread + case Compute(js) => + Schedule.schedule(() => this ! Render(js()), 0.seconds) + + // render it + case Render(js) => + partialUpdate(js) + } +} + +object AsyncRenderComet { + private object pageAsyncRenderer extends TransientRequestVar[Box[AsyncRenderComet]]( + S.findOrCreateComet[AsyncRenderComet]( + cometName = Full(s"lazy-${S.renderVersion}"), + cometHtml = NodeSeq.Empty, + cometAttributes = Map.empty, + receiveUpdatesOnPage = true + ) + ) + + /** + * If you're going to be managing the asynchronicity of the render externally, + * make sure to call this so that the async plumbing will be set up on the + * page when it gets sent down. + * + * When possible, prefer the use of the `lazy` snippet, the `asyncRender` + * function, or the `CanBind` implicits for `Future` and `LAFuture`. + * + * Returns a `Failure` if something went wrong with setting up the + * asynchronous render. + */ + def setupAsync: Box[Unit] = { + // Dereference to make sure the comet exists. + pageAsyncRenderer.is.map(_ => ()) ?~! "Failed to create async renderer." + } + + /** + * If you're going to be managing the asynchronicity of the render externally + * (e.g., with futures), call this when you're ready to render your results + * and the rendering will be sent down to the client. + * + * When possible, prefer the use of the `lazy` snippet, the `asyncRender` + * function, or the `CanBind` implicits for `Future` and `LAFuture`. + * + * Returns a `Failure` if something went wrong with looking up the + * asynchronous renderer. + */ + def completeAsyncRender(command: JsCmd): Box[Unit] = { + pageAsyncRenderer.is.map(_ ! Render(command)) ?~! "Failed to create async renderer." + } + + /** + * Render the given function on a separate thread and send the resulting + * JavaScript to the current page when the function completes. Wraps the + * function so that it is executed in the current request and session context. + * + * Returns a `Failure` if something went wrong with setting up the + * asynchronous render. + */ + def asyncRender(renderFunction: ()=>JsCmd): Box[Unit] = { + for { + session <- S.session ?~ "Asynchronous rendering requires a session context." + renderer <- pageAsyncRenderer.is ?~! "Failed to create async renderer." + } yield { + renderer ! Compute(session.buildDeferredFunction(renderFunction)) + } + } +} diff --git a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/LazyLoad.scala b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/LazyLoad.scala index 6df6d35de3..3e92e29910 100644 --- a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/LazyLoad.scala +++ b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/LazyLoad.scala @@ -29,15 +29,13 @@ import JE._ import S._ import Helpers._ +import comet.AsyncRenderComet /** * Enclose your snippet tags on your template with LazyLoad and the snippet will execute * on a different thread, which avoids blocking the page render. */ object LazyLoad extends DispatchSnippet { - private object myFuncName extends TransientRequestVar(Helpers.nextFuncName) - private object myActor extends TransientRequestVar[Box[CometActor]](Empty) - def dispatch: DispatchIt = { case _ => render _ } @@ -45,110 +43,44 @@ object LazyLoad extends DispatchSnippet { /** * Enclose your snippet like this: * - *
        -   *   <div class="lift:LazyLoad">
        -   *     <div class="lift:MyLongRunningSnippet"></div>
        -   *   </div>
        -   * 
        - * - * You can add the template attribute to the LazyLoad tag and instead of - * showing the spinning circle, it will render your template. - * + * {{{ + *
        + *
        + *
        + * }}} * - *
        -   *   <div class="lift:LazyLoad?template='my-nice-wait-message-template'">
        -   *     <div class="lift:MyLongRunningSnippet"></div>
        -   *   </div>
        -   * 
        + * You can also add the `template` attribute to the `lazy-load` snippet to + * specify what to render as a placeholder: * + * {{{ + *
        + *
        + *
        + * }}} * + * Note that this must be a single element that will be replaced when the lazy + * content is loaded. By default, an AJAX spinner is displayed inside a `div`. */ def render(xhtml: NodeSeq): NodeSeq = { - (for { - session <- S.session ?~ ("FIXME: Invalid session") - } yield { - - // if we haven't created the actor yet, register on this - // thread to create the AsyncRenderComet actor - if (myActor.isEmpty) { - LiftRules.cometCreationFactory.request.set( - (ccinfo: CometCreationInfo) => - ccinfo match { - case CometCreationInfo(theType @ "AsyncRenderComet", - name, - defaultXml, - attributes, - session) => { - val ret = new AsyncRenderComet() - ret.initCometActor(ccinfo) - ret ! PerformSetupComet2(if (ret.sendInitialReq_?) - S.request.map(_.snapshot) else Empty) - - // and save it in the request var - myActor.set(Full(ret)) - - Full(ret) - } - - case _ => Empty - }) - } - - val id = Helpers.nextFuncName - - val func: () => JsCmd = - session.buildDeferredFunction(() => Replace(id, xhtml)) - -
        - { - S.attr("template") match { - case Full(template) => - case _ => Loading + val placeholderId = Helpers.nextFuncName + + AsyncRenderComet.asyncRender(()=>Replace(placeholderId, xhtml)).map { _ => + ("^ [id]" #> placeholderId).apply( + { + for { + templatePath <- S.attr("template") + renderedTemplate <- S.eval() + } yield { + renderedTemplate + } + } openOr { +
        Loading
        } - } -
        ++ (myActor.is match { - case Full(actor) => actor ! Ready(func); NodeSeq.Empty - case _ => session.setupComet("AsyncRenderComet", Full(myFuncName.is), Ready(func)) - - }) - }) match { - case Full(x) => x - case Empty => Comment("FIX"+ "ME: session or request are invalid") + ) + } match { + case Full(placeholderHtml) => placeholderHtml case Failure(msg, _, _) => Comment(msg) + case Empty => Comment("FIX"+"ME: Asynchronous rendering failed for unknown reason.") } - - } - -} - - -private case class Ready(js: () => JsCmd) -private case class Render(js: JsCmd) - - -/** - * The Comet Actor for sending down the computed page fragments - * - */ -class AsyncRenderComet extends CometActor { - - override def lifespan: Box[TimeSpan] = Full(90.seconds) - - def render = NodeSeq.Empty - - // make this method visible so that we can initialize the actor - override def initCometActor(creationInfo: CometCreationInfo) { - super.initCometActor(creationInfo) - } - - - override def lowPriority : PartialFunction[Any, Unit] = { - // farm the request off to another thread - case Ready(js) => - Schedule.schedule(() => this ! Render(js()), 0.seconds) - - // render it - case Render(js) => - partialUpdate(js) } } From 7d01f5e76619402274cf1be3d5715e1a19ae0d5d Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 9 Aug 2014 10:57:04 -0400 Subject: [PATCH 1021/1949] Add buildDeferredFunction for (A)=>T. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LiftSession’s buildDeferredFunction only existed for a no-arg function. We now have a version that builds a deferred function of one argument; invoking that function with an argument will restore the session/request context and then execute the original function with the passed argument. Also simplified our restoration of context in both versions of buildDeferredFunction. --- .../scala/net/liftweb/http/LiftSession.scala | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index 1ede2d8280..d9c4bc216f 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -1783,19 +1783,33 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, * it look to those threads as if the scope was the same as if it * had been executed on the thread that created the function. */ - def buildDeferredFunction[T](f: () => T): () => T = { - val currentReq: Box[Req] = S.request.map(_.snapshot) - + def buildDeferredFunction[T](deferredFunction: () => T): () => T = { + val currentReq = S.request.map(_.snapshot) val renderVersion = RenderVersion.get + val requestVarFunc = RequestVarHandler.generateSnapshotRestorer[T]() - val currentMap = snippetMap.is - val curLoc = S.location + () => { + requestVarFunc(() => + executeInScope(currentReq, renderVersion)(deferredFunction())) + } + } + /** + * Overload of `buildDeferredFunction` for functions that take a parameter. + * + * The returned function, when invoked with a parameter of type `A`, will + * invoke the passed `deferredFunction` with that parameter in the original + * request and session context. + */ + def buildDeferredFunction[A,T](deferredFunction: (A)=>T): (A)=>T = { + val currentReq = S.request.map(_.snapshot) + val renderVersion = RenderVersion.get val requestVarFunc = RequestVarHandler.generateSnapshotRestorer[T]() - () => { + (in: A) => { requestVarFunc(() => - executeInScope(currentReq, renderVersion)(f())) + executeInScope(currentReq, renderVersion)(deferredFunction(in)) + ) } } From a064cbf10acce2f65619168fde8388b605ded566 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 9 Aug 2014 00:15:02 -0400 Subject: [PATCH 1022/1949] Add first pass future CanBinds to the http package. Also clean up some old commented-out package object cruft. --- .../main/scala/net/liftweb/http/package.scala | 69 +++++++++++++++---- 1 file changed, 57 insertions(+), 12 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/package.scala b/web/webkit/src/main/scala/net/liftweb/http/package.scala index 36276d1cb4..9970ca13d4 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/package.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/package.scala @@ -16,24 +16,69 @@ package net.liftweb -import common._ +import scala.concurrent.{ExecutionContext,Future} +import scala.xml.{Comment,NodeSeq} + +import actor.LAFuture +import builtin.comet.AsyncRenderComet +import http.js.JsCmds.Replace import util._ -import xml.{Elem, MetaData, NodeSeq} -import java.util.{ResourceBundle, Locale, Date} package object http { + implicit def futureTransform[T](implicit innerTransform: CanBind[T], executionContext: ExecutionContext): CanBind[Future[T]] = { + new CanBind[Future[T]] { + def apply(future: =>Future[T])(ns: NodeSeq): Seq[NodeSeq] = { + val placeholderId = Helpers.nextFuncName + AsyncRenderComet.setupAsync + + val concreteFuture: Future[T] = future + + S.session.map { session => + // Capture context now. + val deferredRender = + session.buildDeferredFunction((futureResult: T) => { + AsyncRenderComet.completeAsyncRender( + Replace(placeholderId, innerTransform(futureResult)(ns).flatten) + ) + }) + + // Actually complete the render once the future is fulfilled. + concreteFuture.foreach { result => deferredRender(result) } + +
        Loading...
        + } openOr { + Comment("FIX"+"ME: Asynchronous rendering failed for unknown reason.") + } + } + } + } + + implicit def lafutureTransform[T](implicit innerTransform: CanBind[T]): CanBind[LAFuture[T]] = { + new CanBind[LAFuture[T]] { + def apply(future: =>LAFuture[T])(ns: NodeSeq): Seq[NodeSeq] = { + val placeholderId = Helpers.nextFuncName + AsyncRenderComet.setupAsync - /* - type CssBoundLiftScreen = screen.CssBoundLiftScreen - type CssBoundScreen = screen.CssBoundScreen + val concreteFuture: LAFuture[T] = future - type LiftScreen = screen.LiftScreen - type LiftScreenRules = screen.LiftScreenRules + S.session.map { session => + // Capture context now. + val deferredRender = + session.buildDeferredFunction((futureResult: T) => { + AsyncRenderComet.completeAsyncRender( + Replace(placeholderId, innerTransform(futureResult)(ns).flatten) + ) + }) - type Wizard = screen.Wizard - type WizardRules = screen.WizardRules + // Actually complete the render once the future is fulfilled. + concreteFuture.onSuccess { result => deferredRender(result) } - type WiringUI = wiring.WiringUI - */ +
        Loading...
        + } openOr { + Comment("FIX"+"ME: Asynchronous rendering failed for unknown reason.") + } + } + } + } } From c313a9c7851b472f45e88d927798f1e568751349 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 9 Aug 2014 10:58:21 -0400 Subject: [PATCH 1023/1949] Add failure messages to findOrCreateComet. If we fail to find the session, we now report as much in the Failure message. --- web/webkit/src/main/scala/net/liftweb/http/S.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/S.scala b/web/webkit/src/main/scala/net/liftweb/http/S.scala index 4b8453f9a2..41225a3918 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/S.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/S.scala @@ -831,7 +831,7 @@ trait S extends HasParams with Loggable with UserAgentCalculator { receiveUpdatesOnPage: Boolean = false ): Box[LiftCometActor] = { for { - session <- session + session <- session ?~ "Comet lookup and creation requires a session." cometActor <- session.findOrCreateComet(cometType, cometName, cometHtml, cometAttributes) } yield { if (receiveUpdatesOnPage) @@ -859,7 +859,7 @@ trait S extends HasParams with Loggable with UserAgentCalculator { receiveUpdatesOnPage: Boolean )(implicit cometManifest: Manifest[T]): Box[T] = { for { - session <- session + session <- session ?~ "Comet lookup and creation requires a session." cometActor <- session.findOrCreateComet[T](cometName, cometHtml, cometAttributes) } yield { if (receiveUpdatesOnPage) From 21f4d074ff53cd01f1799dbc6c88cef679688ce9 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 9 Aug 2014 12:15:54 -0400 Subject: [PATCH 1024/1949] Unify logic for Future and LAFuture CanBinds. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Still not as clean as I’d like, but getting there. --- .../main/scala/net/liftweb/http/package.scala | 48 +++++++------------ 1 file changed, 16 insertions(+), 32 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/package.scala b/web/webkit/src/main/scala/net/liftweb/http/package.scala index 9970ca13d4..a35b4d9511 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/package.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/package.scala @@ -25,25 +25,25 @@ import http.js.JsCmds.Replace import util._ package object http { - implicit def futureTransform[T](implicit innerTransform: CanBind[T], executionContext: ExecutionContext): CanBind[Future[T]] = { - new CanBind[Future[T]] { - def apply(future: =>Future[T])(ns: NodeSeq): Seq[NodeSeq] = { + private def bindResolvable[ResolvableType, ResolvedType](asyncResolver: (ResolvableType, (ResolvedType)=>Unit)=>Unit)(implicit innerTransform: CanBind[ResolvedType]) = { + new CanBind[ResolvableType] { + def apply(resolvable: =>ResolvableType)(ns: NodeSeq): Seq[NodeSeq] = { val placeholderId = Helpers.nextFuncName AsyncRenderComet.setupAsync - val concreteFuture: Future[T] = future + val concreteResolvable: ResolvableType = resolvable S.session.map { session => // Capture context now. val deferredRender = - session.buildDeferredFunction((futureResult: T) => { + session.buildDeferredFunction((resolved: ResolvedType) => { AsyncRenderComet.completeAsyncRender( - Replace(placeholderId, innerTransform(futureResult)(ns).flatten) + Replace(placeholderId, innerTransform(resolved)(ns).flatten) ) }) // Actually complete the render once the future is fulfilled. - concreteFuture.foreach { result => deferredRender(result) } + asyncResolver(concreteResolvable, resolvedResult => deferredRender(resolvedResult))
        Loading...
        } openOr { @@ -53,32 +53,16 @@ package object http { } } - implicit def lafutureTransform[T](implicit innerTransform: CanBind[T]): CanBind[LAFuture[T]] = { - new CanBind[LAFuture[T]] { - def apply(future: =>LAFuture[T])(ns: NodeSeq): Seq[NodeSeq] = { - val placeholderId = Helpers.nextFuncName - AsyncRenderComet.setupAsync - - val concreteFuture: LAFuture[T] = future - - S.session.map { session => - // Capture context now. - val deferredRender = - session.buildDeferredFunction((futureResult: T) => { - AsyncRenderComet.completeAsyncRender( - Replace(placeholderId, innerTransform(futureResult)(ns).flatten) - ) - }) - - // Actually complete the render once the future is fulfilled. - concreteFuture.onSuccess { result => deferredRender(result) } + implicit def futureTransform[T](implicit innerTransform: CanBind[T], executionContext: ExecutionContext): CanBind[Future[T]] = { + bindResolvable[Future[T],T]({ (future, onResolved) => + future.foreach(onResolved) + }) + } -
        Loading...
        - } openOr { - Comment("FIX"+"ME: Asynchronous rendering failed for unknown reason.") - } - } - } + implicit def lafutureTransform[T](implicit innerTransform: CanBind[T]): CanBind[LAFuture[T]] = { + bindResolvable[LAFuture[T],T]({ (future, onResolved) => + future.onSuccess(onResolved) + }) } } From e06e087acbe179bc6b5c5ffdf4668ed03fb61c57 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 9 Aug 2014 12:52:50 -0400 Subject: [PATCH 1025/1949] Move resolution to a CanResolveAsync type class. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now anyone can provide a CanResolveAsync implicit to deal with other future types (for example, Finagle’s Twitter Futures) and the CanBind will automatically apply. --- .../main/scala/net/liftweb/http/package.scala | 54 ++++++++++++++----- 1 file changed, 40 insertions(+), 14 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/package.scala b/web/webkit/src/main/scala/net/liftweb/http/package.scala index a35b4d9511..9e7320dd01 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/package.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/package.scala @@ -25,7 +25,45 @@ import http.js.JsCmds.Replace import util._ package object http { - private def bindResolvable[ResolvableType, ResolvedType](asyncResolver: (ResolvableType, (ResolvedType)=>Unit)=>Unit)(implicit innerTransform: CanBind[ResolvedType]) = { + trait CanResolveAsync[ResolvableType, ResolvedType] { + /** + * Should return a function that, when given the resolvable and a function + * that takes the resolved value, attaches the function to the resolvable + * so that it will asynchronously execute it when its value is resolved. + * + * See `CanResolveFuture` and `CanResolveLAFuture` for examples. + */ + def resolveAsync(resolvable: ResolvableType, onResolved: (ResolvedType)=>Unit): Unit + } + object CanResolveAsync { + implicit def resolveFuture[T](implicit executionContext: ExecutionContext) = { + new CanResolveAsync[Future[T],T] { + def resolveAsync(future: Future[T], onResolved: (T)=>Unit) = { + future.foreach(onResolved) + } + } + } + + implicit def resolveLaFuture[T] = { + new CanResolveAsync[LAFuture[T],T] { + def resolveAsync(future: LAFuture[T], onResolved: (T)=>Unit) = { + future.onSuccess(onResolved) + } + } + } + } + + /** + * Provides support for binding anything that has a `CanResolveAsync` + * implementation. Out of the box, that's just Scala `Future`s and + * `LAFuture`s, but it could just as easily be, for example, Twitter `Future`s + * if you're using Finagle; all you have to do is add a `CanResolveAsync` + * implicit for it. + */ + implicit def asyncResolvableTransform[ResolvableType, ResolvedType]( + implicit asyncResolveProvider: CanResolveAsync[ResolvableType,ResolvedType], + innerTransform: CanBind[ResolvedType] + ) = { new CanBind[ResolvableType] { def apply(resolvable: =>ResolvableType)(ns: NodeSeq): Seq[NodeSeq] = { val placeholderId = Helpers.nextFuncName @@ -43,7 +81,7 @@ package object http { }) // Actually complete the render once the future is fulfilled. - asyncResolver(concreteResolvable, resolvedResult => deferredRender(resolvedResult)) + asyncResolveProvider.resolveAsync(concreteResolvable, resolvedResult => deferredRender(resolvedResult))
        Loading...
        } openOr { @@ -52,17 +90,5 @@ package object http { } } } - - implicit def futureTransform[T](implicit innerTransform: CanBind[T], executionContext: ExecutionContext): CanBind[Future[T]] = { - bindResolvable[Future[T],T]({ (future, onResolved) => - future.foreach(onResolved) - }) - } - - implicit def lafutureTransform[T](implicit innerTransform: CanBind[T]): CanBind[LAFuture[T]] = { - bindResolvable[LAFuture[T],T]({ (future, onResolved) => - future.onSuccess(onResolved) - }) - } } From 111bb6de7f199d75d89dbf78b6d278fb6dc34766 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 10 Aug 2014 10:42:53 -0400 Subject: [PATCH 1026/1949] Fix unimplemented onDocumentReady error. --- web/webkit/src/main/resources/toserve/lift.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/webkit/src/main/resources/toserve/lift.js b/web/webkit/src/main/resources/toserve/lift.js index db2ea5e0ca..571750206a 100644 --- a/web/webkit/src/main/resources/toserve/lift.js +++ b/web/webkit/src/main/resources/toserve/lift.js @@ -65,7 +65,7 @@ consoleOrAlert("ajaxGet function must be defined in settings"); }, onDocumentReady: function(fn) { - consoleOrAlert("documentReady function must be defined in settings"); + consoleOrAlert("onDocumentReady function must be defined in settings"); }, cometGetTimeout: 140000, cometFailureRetryTimeout: 10000, From 79372aeb5468bd4be8aae120f83109d3542f77dc Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 10 Aug 2014 10:43:35 -0400 Subject: [PATCH 1027/1949] Refer to settings.onDocumentReady in init. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before we were referring to the version provided by options, which meant that if someone failed to provide an onDocumentReady implementation to init, we’d get an exception instead of a logged error. --- web/webkit/src/main/resources/toserve/lift.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/webkit/src/main/resources/toserve/lift.js b/web/webkit/src/main/resources/toserve/lift.js index 571750206a..d596f51d5b 100644 --- a/web/webkit/src/main/resources/toserve/lift.js +++ b/web/webkit/src/main/resources/toserve/lift.js @@ -550,7 +550,7 @@ this.extend(settings, options); var lift = this; - options.onDocumentReady(function() { + settings.onDocumentReady(function() { var gc = document.body.getAttribute('data-lift-gc'); if (gc) { lift.startGc(); From fd9ba88be6638c2c253a43bbc69409158f0510e5 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 10 Aug 2014 10:44:39 -0400 Subject: [PATCH 1028/1949] Fix lift.logError binding. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We were tying settings.logError directly to lift.logError, but this didn’t allow for the options passed to init to override the implementation because lift.logError is bound before the init function runs. We now delegate to settings.logError using apply, ensuring we’re always seeing the latest version of it. --- web/webkit/src/main/resources/toserve/lift.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/webkit/src/main/resources/toserve/lift.js b/web/webkit/src/main/resources/toserve/lift.js index d596f51d5b..5f166a0b00 100644 --- a/web/webkit/src/main/resources/toserve/lift.js +++ b/web/webkit/src/main/resources/toserve/lift.js @@ -581,7 +581,7 @@ // start the cycle doCycleIn200(); }, - logError: settings.logError, + logError: function() { settings.logError.apply(this, arguments) }, ajax: appendToQueue, startGc: successRegisterGC, ajaxOnSessionLost: function() { From a1feda5df5da34c54bff7840a4e889999fb203ae Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 10 Aug 2014 10:56:16 -0400 Subject: [PATCH 1029/1949] Add lift.onEvent. lift.onEvent is used to attach an event handler to an element, usually by id. There is both a jQuery and vanilla implementation, with the vanilla implementation reworking some of the onDocumentReady stuff. --- web/webkit/src/main/resources/toserve/lift.js | 38 ++++++++++++++----- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/web/webkit/src/main/resources/toserve/lift.js b/web/webkit/src/main/resources/toserve/lift.js index 5f166a0b00..0e25a5c51d 100644 --- a/web/webkit/src/main/resources/toserve/lift.js +++ b/web/webkit/src/main/resources/toserve/lift.js @@ -64,6 +64,9 @@ ajaxGet: function() { consoleOrAlert("ajaxGet function must be defined in settings"); }, + onEvent: function(elementOrId, eventName, fn) { + consoleOrAlert("onEvent function must be defined in settings"); + }, onDocumentReady: function(fn) { consoleOrAlert("onDocumentReady function must be defined in settings"); }, @@ -582,6 +585,7 @@ doCycleIn200(); }, logError: function() { settings.logError.apply(this, arguments) }, + onEvent: function() { settings.onEvent.apply(this, arguments) }, ajax: appendToQueue, startGc: successRegisterGC, ajaxOnSessionLost: function() { @@ -635,6 +639,12 @@ })(); window.liftJQuery = { + onEvent: function(elementOrId, eventName, fn) { + if (typeof elementOrId == 'string') + elementOrId = '#' + elementOrId; + + jQuery(elementOrId).on(eventName, fn); + }, onDocumentReady: jQuery(document).ready, ajaxPost: function(url, data, dataType, onSuccess, onFailure) { var processData = true, @@ -676,16 +686,26 @@ }; window.liftVanilla = { + // This and onDocumentReady adapted from https://github.com/dperini/ContentLoaded/blob/master/src/contentloaded.js, + // as also used (with modifications) in jQuery. + onEvent: function(elementOrId, eventName, fn) { + var win = window, + doc = win.document, + add = doc.addEventListener ? 'addEventListener' : 'attachEvent', + pre = doc.addEventListener ? '' : 'on'; + + var element = elementOrId; + if (typeof elementOrId == 'string') { + element = document.getElementById(elementOrId); + } + + element[add](pre + eventName, fn, false); + }, onDocumentReady: function(fn) { - // Taken from https://github.com/dperini/ContentLoaded/blob/master/src/contentloaded.js, - // as also used (with modifications) in jQuery. var done = false, top = true, - win = window, doc = win.document, root = doc.documentElement, - - add = doc.addEventListener ? 'addEventListener' : 'attachEvent', + pre = doc.addEventListener ? '' : 'on'; rem = doc.addEventListener ? 'removeEventListener' : 'detachEvent', - pre = doc.addEventListener ? '' : 'on', init = function(e) { if (e.type == 'readystatechange' && doc.readyState != 'complete') return; @@ -705,9 +725,9 @@ try { top = !win.frameElement; } catch(e) { } if (top) poll(); } - doc[add](pre + 'DOMContentLoaded', init, false); - doc[add](pre + 'readystatechange', init, false); - win[add](pre + 'load', init, false); + liftVanilla.onEvent(doc, 'DOMContentLoaded', init); + liftVanilla.onEvent(doc, 'readystatechange', init); + liftVanilla.onEvent(win, 'load', init); } }, ajaxPost: function(url, data, dataType, onSuccess, onFailure, onUploadProgress) { From e51895306f91d490596a68745d132ad832c509cc Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 10 Aug 2014 10:58:55 -0400 Subject: [PATCH 1030/1949] Extract on* attributes to page JavaScript. We now pull out all on* attributes, moving them instead to onEvent invocations in the page JavaScript. Where necessary, we mark the element in question with a unique id so we can address it in the onEvent invocation. This lets us remove all inline JavaScript from the page, preparing us to be able to set strong Content-Security-Policy settings out of the box. --- .../scala/net/liftweb/http/LiftMerge.scala | 119 ++++++++++++++++-- 1 file changed, 106 insertions(+), 13 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala index ddd032eb75..49fca842f2 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala @@ -17,13 +17,23 @@ package net.liftweb package http +import scala.collection.Map import scala.collection.mutable.{HashMap, ArrayBuffer, ListBuffer} import scala.xml._ + import net.liftweb.util._ import net.liftweb.common._ import net.liftweb.http.js._ + import JsCmds.Noop + import JE.{AnonFunc,Call,JsRaw} import Helpers._ +/** + * Represents an HTML attribute for an event handler. Carries the event name and + * the JS that should run when that event is triggered as a String. + */ +private case class EventAttribute(eventName: String, jsString: String) + private[http] trait LiftMerge { self: LiftSession => @@ -32,12 +42,21 @@ private[http] trait LiftMerge { } // Gather all page-specific JS into one JsCmd. - private def assemblePageSpecificJavaScript: JsCmd = { + private def assemblePageSpecificJavaScript(eventAttributesByElementId: Map[String,List[EventAttribute]]): JsCmd = { + val eventJs = + for { + (elementId, eventAttributes) <- eventAttributesByElementId + EventAttribute(eventName, jsString) <- eventAttributes + } yield { + Call("lift.onEvent", elementId, eventName, AnonFunc(JsRaw(jsString).cmd)).cmd + } + val allJs = LiftRules.javaScriptSettings.vend().map { settingsFn => LiftJavaScript.initCmd(settingsFn(this)) }.toList ++ - S.jsToAppend + S.jsToAppend ++ + eventJs allJs.foldLeft(js.JsCmds.Noop)(_ & _) } @@ -99,17 +118,66 @@ private[http] trait LiftMerge { addlHead ++= S.forHead() val addlTail = new ListBuffer[Node] addlTail ++= S.atEndOfBody() + val eventAttributesByElementId = new HashMap[String,List[EventAttribute]] val rewrite = URLRewriter.rewriteFunc val fixHref = Req.fixHref val contextPath: String = S.contextPath - def fixAttrs(original: MetaData, toFix: String, attrs: MetaData, fixURL: Boolean): MetaData = attrs match { - case Null => Null - 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)) + // Fix URLs using Req.fixHref and extract JS event attributes for putting + // into page JS. + def fixAttrs(original: MetaData, toFix: String, attrs: MetaData, fixURL: Boolean, eventAttributes: List[EventAttribute] = Nil): (Option[String], MetaData, List[EventAttribute]) = { + attrs match { + case Null => (None, Null, eventAttributes) + case u: UnprefixedAttribute if u.key == toFix => + val (id, remainingAttributes, updatedEventAttributes) = fixAttrs(original, toFix, attrs.next, fixURL) + + (id, new UnprefixedAttribute(toFix, fixHref(contextPath, attrs.value, fixURL, rewrite), remainingAttributes), updatedEventAttributes) + + case u: UnprefixedAttribute if u.key.startsWith("on") => + fixAttrs(original, toFix, attrs.next, fixURL, EventAttribute(u.key.substring(2), u.value.text) :: eventAttributes) + + case u: UnprefixedAttribute if u.key == "id" => + val (_, remainingAttributes, updatedEventAttributes) = fixAttrs(original, toFix, attrs.next, fixURL) + + (Option(u.value.text).filter(_.nonEmpty), remainingAttributes, updatedEventAttributes) + case _ => + val (id, remainingAttributes, updatedEventAttributes) = fixAttrs(original, toFix, attrs.next, fixURL, eventAttributes) + + (id, attrs.copy(remainingAttributes), updatedEventAttributes) + } + } + + // Fix the element's `attributeToFix` using `fixAttrs` and extract JS event + // attributes for putting into page JS. Return a fixed version of this + // element with fixed children. + def fixElementAndAttributes(element: Elem, attributeToFix: String, fixURL: Boolean, fixedChildren: NodeSeq) = { + val (id, fixedAttributes, eventAttributes) = fixAttrs(element.attributes, attributeToFix, element.attributes, fixURL) + + id.map { foundId => + eventAttributesByElementId += (foundId -> eventAttributes) + + element.copy( + attributes = fixedAttributes, + child = fixedChildren + ) + } getOrElse { + if (eventAttributes.nonEmpty) { + val generatedId = s"lift-event-js-$nextFuncName" + eventAttributesByElementId += (generatedId -> eventAttributes) + + element.copy( + attributes = new UnprefixedAttribute("id", generatedId, fixedAttributes), + child = fixedChildren + ) + } else { + element.copy( + attributes = fixedAttributes, + child = fixedChildren + ) + } + } } def _fixHtml(in: NodeSeq, _inHtml: Boolean, _inHead: Boolean, _justHead: Boolean, _inBody: Boolean, _justBody: Boolean, _bodyHead: Boolean, _bodyTail: Boolean, doMergy: Boolean): NodeSeq = { @@ -156,11 +224,36 @@ private[http] trait LiftMerge { 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, e.minimizeEmpty, _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, e.minimizeEmpty, _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, e.minimizeEmpty, _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, e.minimizeEmpty,_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, e.minimizeEmpty, _fixHtml(v.child, inHtml, inHead, justHead, inBody, justBody, bodyHead, bodyTail, doMergy): _*) + case e: Elem if e.label == "form" => + fixElementAndAttributes( + e, "action", fixURL = true, + _fixHtml(e.child, inHtml, inHead, justHead, inBody, justBody, bodyHead, bodyTail, doMergy) + ) + + case e: Elem if e.label == "script" => + fixElementAndAttributes( + e, "src", fixURL = false, + _fixHtml(e.child, inHtml, inHead, justHead, inBody, justBody, bodyHead, bodyTail, doMergy) + ) + + case e: Elem if e.label == "a" => + fixElementAndAttributes( + e, "href", fixURL = true, + _fixHtml(e.child, inHtml, inHead, justHead, inBody, justBody, bodyHead, bodyTail, doMergy) + ) + + case e: Elem if e.label == "link" => + fixElementAndAttributes( + e, "href", fixURL = false, + _fixHtml(e.child, inHtml, inHead, justHead, inBody, justBody, bodyHead, bodyTail, doMergy) + ) + + case e: Elem => + fixElementAndAttributes( + e, "src", fixURL = true, + _fixHtml(e.child, inHtml, inHead, justHead, inBody, justBody, bodyHead, bodyTail, doMergy) + ) + case c: Comment if stripComments => NodeSeq.Empty case _ => v } @@ -203,7 +296,7 @@ private[http] trait LiftMerge { bodyChildren += nl } - val pageJs = assemblePageSpecificJavaScript + val pageJs = assemblePageSpecificJavaScript(eventAttributesByElementId) if (pageJs.toJsCmd.trim.nonEmpty) { addlTail += pageScopedScriptFileWith(pageJs) } From 688f0bd394f6933136603384e34afdfeee0900cb Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Tue, 12 Aug 2014 17:33:43 -0700 Subject: [PATCH 1031/1949] toSingleBox now sets up the failure chain properly. The ParamFailure that toSingleBox returns when there are Failures in the list now has a chain of Failures on it that correspond to all of the Failures in the list. --- .../main/scala/net/liftweb/common/Box.scala | 44 ++++++++++++------- .../scala/net/liftweb/common/BoxSpec.scala | 21 ++++++++- 2 files changed, 49 insertions(+), 16 deletions(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index 2cd805c553..22e0c03ce0 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -55,29 +55,43 @@ object Box extends BoxTrait { **/ implicit class ListOfBoxes[T](val theListOfBoxes: List[Box[T]]) extends AnyVal { /** - * Convert a List of Boxes into a single Box containting a List[T], where T is - * the parameterized type of the Boxes. + * Convert a `List` of `Box`es into a single `Box` containting a `List[T]`, + * where `T` is the parameterized type of the `Box`es. * - * This method is useful for those cases where you have a lot of operations being - * executed that all return some Box[T]. You want just a List[T] if all of those - * operations succeeded, but you don't want to have Failures disappear if any were - * present in the list. + * This method is useful for those cases where you have a lot of operations + * being executed that all return some `Box[T]`. You want just a `List[T]` + * if all of those operations succeeded, but you don't want to have + * Failures disappear if any were present in the list. * - * If all of the Boxes in the List are Full or Empty, we return a Full box containing - * a List of all of the Full Box values that were present. If any of the Boxes contain - * a Failure, a ParamFailure is returned, containing the original List[Box[T]] as the - * param. + * If all of the `Box`es in the `List` are `Full` or `Empty`, we return a + * `Full` box containing a `List` of all of the `Full` `Box` values that + * were present. If any of the `Box`es contain a `Failure`, a + * `ParamFailure` is returned, containing the original `List[Box[T]]` as + * the param. The `ParamFailure` itself is chained to a `Failure` chain + * containing all of the `Failure` boxes in the list. * - * It is worth noting that the size of the list in the resulting Box[List[T]] may not be equal - * to the size of the List[Box[T]] that is fed as Empty values will disappear altogether in the - * conversion. + * It is worth noting that the size of the list in the resulting + * `Box[List[T]]` may not be equal to the size of the `List[Box[T]]` that + * is fed as `Empty` values will disappear altogether in the conversion. * * @param failureErrorMessage The string that should be placed in the message for the Failure. - * @return A Full[List[T]] if no Failures were present. ParamFailure[List[Box[T]]] otherwise. + * @return A `Full[List[T]]` if no `Failure`s were present. `ParamFailure[List[Box[T]]]` otherwise. **/ def toSingleBox(failureErrorMessage: String): Box[List[T]] = { if (theListOfBoxes.exists(_.isInstanceOf[Failure])) { - Failure(failureErrorMessage) ~> theListOfBoxes + val failureChain = + theListOfBoxes.collect { + case fail: Failure => fail + }.reduceRight { (topmostFailure, latestFailure) => + topmostFailure.copy(chain = Full(latestFailure)) + } + + ParamFailure( + failureErrorMessage, + Empty, + Full(failureChain), + theListOfBoxes + ) } else { Full(theListOfBoxes.flatten) } diff --git a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala index 4d60eb25cf..3ea237dc0a 100644 --- a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala +++ b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala @@ -365,7 +365,26 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { val someBoxes: List[Box[String]] = List(Full("bacon"), Full("sammich"), Failure("I HATE BACON")) val singleBox = someBoxes.toSingleBox("This should be in the param failure.") - singleBox must_== ParamFailure("This should be in the param failure.", None, None, someBoxes) + singleBox must beLike { + case ParamFailure(message, _, _, _) => + message must_== "This should be in the param failure." + } + } + + "chain the ParamFailure to the failures in the list when any are Failure" in { + val someBoxes: List[Box[String]] = List(Full("bacon"), Failure("I HATE BACON"), Full("sammich"), Failure("MORE BACON FAIL"), Failure("BACON WHY U BACON")) + + val singleBox = someBoxes.toSingleBox("Failure.") + + val expectedChain = + Failure("I HATE BACON", Empty, + Full(Failure("MORE BACON FAIL", Empty, + Full(Failure("BACON WHY U BACON"))))) + + singleBox must beLike { + case ParamFailure(_, _, chain, _) => + chain must_== Full(expectedChain) + } } } From dca0fb741c19ac43047a921414c5a4f30ea7974b Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 13 Aug 2014 23:03:51 -0400 Subject: [PATCH 1032/1949] Add AsyncRenderComet.asyncRenderDeferred. This is for doing an asyncRender when the function has already been wrapped with whatever deferred wrapperisms you may want, maybe including buildDeferredFunction. --- .../net/liftweb/builtin/comet/AsyncRenderComet.scala | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/web/webkit/src/main/scala/net/liftweb/builtin/comet/AsyncRenderComet.scala b/web/webkit/src/main/scala/net/liftweb/builtin/comet/AsyncRenderComet.scala index 741b193043..efbf2a11ba 100644 --- a/web/webkit/src/main/scala/net/liftweb/builtin/comet/AsyncRenderComet.scala +++ b/web/webkit/src/main/scala/net/liftweb/builtin/comet/AsyncRenderComet.scala @@ -116,4 +116,15 @@ object AsyncRenderComet { renderer ! Compute(session.buildDeferredFunction(renderFunction)) } } + + /** + * Similar to `asyncRender`, but any wrapping of the function in a request + * context is expected to be done before `renderFunction` is passed to this, + * while `asyncRender` takes care of the wrapping for you. + */ + def asyncRenderDeferred(renderFunction: ()=>JsCmd): Box[Unit] = { + pageAsyncRenderer.is.map { renderer => + renderer ! Compute(renderFunction) + } ?~! "Failed to create async renderer." + } } From 9670f2ef2389084583f1ea36e4535da74b4d3017 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 13 Aug 2014 23:04:29 -0400 Subject: [PATCH 1033/1949] Enrich LazyLoad snippet with some render tweaks. There is now an overload that takes a renderer function to which we hand the placeholder id that we generate, and which is charged in turn with doing the final rendering. It expectes the caller to wrap the passed function in whatever deferred handling may be needed. The main render method also now has an optional placeholderTemplate parameter so that when invoked from Scala it can be given a NodeSeq to use for the placeholder. --- .../liftweb/builtin/snippet/LazyLoad.scala | 76 +++++++++++++++---- 1 file changed, 60 insertions(+), 16 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/LazyLoad.scala b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/LazyLoad.scala index 3e92e29910..a83f97c932 100644 --- a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/LazyLoad.scala +++ b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/LazyLoad.scala @@ -40,6 +40,46 @@ object LazyLoad extends DispatchSnippet { case _ => render _ } + /** + * If you need to provide a custom `renderer` function, perhaps because you + * need to do further wrapping than what `buildDeferredFunction` gives you, + * then you can invoke this `render` method directly and pass it a function + * that will take the id of the placeholder container and render whatever + * needs to be rendered to a `JsCmd`. The snippet will: + * - Set up the placeholder markup with a unique id. + * - If the placeholder template `NodeSeq` isn't specified, first see if + * `S.attr("template")` is set and use that template if avilable, and then + * fall back on the default placeholder template. + * - Handle invoking the `AsyncRenderComet` correctly. + * + * Notably, the `renderer` function will *not* be wrapped in current request + * state; instead, you must do this manually using `buildDeferredFunction`. + * This method is for advanced use; most folks will probably want to interact + * with the snippet by just wrapping their snippet invocation in a + * `data-lift="lazy-load"` snippet. + */ + def render(renderer: (String)=>JsCmd, placeholderTemplate: Box[NodeSeq] = Empty): NodeSeq = { + val placeholderId = Helpers.nextFuncName + + handleMarkupBox( + AsyncRenderComet.asyncRenderDeferred(()=>renderer(placeholderId)).map { _ => + ("^ [id]" #> placeholderId).apply( + placeholderTemplate or + { + for { + templatePath <- S.attr("template") + renderedTemplate <- S.eval() + } yield { + renderedTemplate + } + } openOr { +
        Loading
        + } + ) + } + ) + } + /** * Enclose your snippet like this: * @@ -60,25 +100,29 @@ object LazyLoad extends DispatchSnippet { * * Note that this must be a single element that will be replaced when the lazy * content is loaded. By default, an AJAX spinner is displayed inside a `div`. + * + * If you invoke this from Scala rather than markup, you can optionally + * provide a `placeholderTemplate` that is a `NodeSeq` that should be used + * while the async rendering takes place. */ + def render(xhtml: NodeSeq, placeholderTemplate: Box[NodeSeq]): NodeSeq = { + handleMarkupBox( + S.session.map { session => + render(session.buildDeferredFunction({ placeholderId: String => + Replace(placeholderId, xhtml) + }), placeholderTemplate) + } ?~ "Asynchronous rendering requires a session context." + ) + } + def render(xhtml: NodeSeq): NodeSeq = { - val placeholderId = Helpers.nextFuncName + render(xhtml, Empty) + } - AsyncRenderComet.asyncRender(()=>Replace(placeholderId, xhtml)).map { _ => - ("^ [id]" #> placeholderId).apply( - { - for { - templatePath <- S.attr("template") - renderedTemplate <- S.eval() - } yield { - renderedTemplate - } - } openOr { -
        Loading
        - } - ) - } match { - case Full(placeholderHtml) => placeholderHtml + // Helper to deal with Boxed markup. + private def handleMarkupBox(markup: Box[NodeSeq]): NodeSeq = { + markup match { + case Full(html) => html case Failure(msg, _, _) => Comment(msg) case Empty => Comment("FIX"+"ME: Asynchronous rendering failed for unknown reason.") } From c1a46719902fe31b9836ffff130abde664d43c40 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Thu, 14 Aug 2014 22:50:54 -0400 Subject: [PATCH 1034/1949] Make LazyLoad.render wrap passed functions in deferred contexts. Initially, the LazyLoad.render overload that took a (String)=>JsCmd explicitly didn't wrap buildDeferredFunction around the function, but after thinking about it a little more, not needing buildDeferredFunction is low-level and rare enough that we'll leave it so using that requires using AsyncRenderComet.asyncRenderDeferred instead. --- .../liftweb/builtin/snippet/LazyLoad.scala | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/LazyLoad.scala b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/LazyLoad.scala index a83f97c932..1fec967b6e 100644 --- a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/LazyLoad.scala +++ b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/LazyLoad.scala @@ -42,7 +42,7 @@ object LazyLoad extends DispatchSnippet { /** * If you need to provide a custom `renderer` function, perhaps because you - * need to do further wrapping than what `buildDeferredFunction` gives you, + * need to do further wrapping beyond what `buildDeferredFunction` gives you, * then you can invoke this `render` method directly and pass it a function * that will take the id of the placeholder container and render whatever * needs to be rendered to a `JsCmd`. The snippet will: @@ -52,17 +52,16 @@ object LazyLoad extends DispatchSnippet { * fall back on the default placeholder template. * - Handle invoking the `AsyncRenderComet` correctly. * - * Notably, the `renderer` function will *not* be wrapped in current request - * state; instead, you must do this manually using `buildDeferredFunction`. - * This method is for advanced use; most folks will probably want to interact - * with the snippet by just wrapping their snippet invocation in a - * `data-lift="lazy-load"` snippet. + * The `renderer` function will be wrapped in current request state you must + * do this manually using `buildDeferredFunction`. This method is for advanced + * use; most folks will probably want to interact with the snippet by just + * wrapping their snippet invocation in a `data-lift="lazy-load"` snippet. */ def render(renderer: (String)=>JsCmd, placeholderTemplate: Box[NodeSeq] = Empty): NodeSeq = { val placeholderId = Helpers.nextFuncName handleMarkupBox( - AsyncRenderComet.asyncRenderDeferred(()=>renderer(placeholderId)).map { _ => + AsyncRenderComet.asyncRender(()=>renderer(placeholderId)).map { _ => ("^ [id]" #> placeholderId).apply( placeholderTemplate or { @@ -106,13 +105,7 @@ object LazyLoad extends DispatchSnippet { * while the async rendering takes place. */ def render(xhtml: NodeSeq, placeholderTemplate: Box[NodeSeq]): NodeSeq = { - handleMarkupBox( - S.session.map { session => - render(session.buildDeferredFunction({ placeholderId: String => - Replace(placeholderId, xhtml) - }), placeholderTemplate) - } ?~ "Asynchronous rendering requires a session context." - ) + render(Replace(_, xhtml), placeholderTemplate) } def render(xhtml: NodeSeq): NodeSeq = { From 19664c51820286ccb914fdd5aa9c24d4adbe0a43 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Fri, 15 Aug 2014 13:45:58 -0400 Subject: [PATCH 1035/1949] Clarify a boolean parameter by making it named. --- web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index 6419035ca7..d0d33377c4 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -1519,7 +1519,7 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, Templates.findTopLevelTemplate( splits, S.locale, - S.location.isDefined && S.request.exists(!_.ajax_?) + needAutoSurround = S.location.isDefined && S.request.exists(!_.ajax_?) ) } From d24047a9a6aedde302a1b7cff9fad7a0dcdd3ad7 Mon Sep 17 00:00:00 2001 From: Diego Medina Date: Fri, 15 Aug 2014 21:03:40 -0400 Subject: [PATCH 1036/1949] Lift 3.0 only builds for 2.11.x, so we always need the external xml dependency --- project/Build.scala | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index 9d02131cd1..d37acb929b 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -39,11 +39,7 @@ object BuildDef extends Build { lazy val common = coreProject("common") .settings(description := "Common Libraties and Utilities", - libraryDependencies ++= Seq(slf4j_api, logback, slf4j_log4j12), - libraryDependencies <++= scalaVersion { - case "2.11.2" => Seq(scala_xml, scala_parser) - case _ => Seq() - } + libraryDependencies ++= Seq(slf4j_api, logback, slf4j_log4j12, scala_xml, scala_parser) ) lazy val actor = @@ -56,11 +52,7 @@ object BuildDef extends Build { coreProject("markdown") .settings(description := "Markdown Parser", parallelExecution in Test := false, - libraryDependencies <++= scalaVersion { sv => Seq(scalatest(sv), junit) }, - libraryDependencies <++= scalaVersion { - case "2.11.2" => Seq(scala_xml, scala_parser) - case _ => Seq() - } + libraryDependencies <++= scalaVersion { sv => Seq(scalatest(sv), junit, scala_xml, scala_parser) } ) lazy val json = From 546f4685fa89fc497d8fcb5df6407c905f263981 Mon Sep 17 00:00:00 2001 From: Diego Medina Date: Fri, 15 Aug 2014 21:44:07 -0400 Subject: [PATCH 1037/1949] Build clean up now that we are only building for scala 2.11.x * Simpler crossMapped * Removed scalaz6 * Just use latest versions of specs2, scalatest and scalaz --- build.sbt | 2 +- project/Build.scala | 16 +++++----------- project/Dependencies.scala | 34 +++++++++++++--------------------- 3 files changed, 19 insertions(+), 33 deletions(-) diff --git a/build.sbt b/build.sbt index 5b79517ff9..b664165368 100644 --- a/build.sbt +++ b/build.sbt @@ -16,7 +16,7 @@ scalaVersion in ThisBuild := "2.11.2" crossScalaVersions in ThisBuild := Seq("2.11.2") -libraryDependencies in ThisBuild <++= scalaVersion {sv => Seq(specs2(sv), scalacheck, scalatest(sv)) } +libraryDependencies in ThisBuild <++= scalaVersion {sv => Seq(specs2, scalacheck, scalatest) } // Settings for Sonatype compliance pomIncludeRepository in ThisBuild := { _ => false } diff --git a/project/Build.scala b/project/Build.scala index d37acb929b..1d4f98b360 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -52,7 +52,7 @@ object BuildDef extends Build { coreProject("markdown") .settings(description := "Markdown Parser", parallelExecution in Test := false, - libraryDependencies <++= scalaVersion { sv => Seq(scalatest(sv), junit, scala_xml, scala_parser) } + libraryDependencies <++= scalaVersion { sv => Seq(scalatest, junit, scala_xml, scala_parser) } ) lazy val json = @@ -70,7 +70,7 @@ object BuildDef extends Build { coreProject("json-scalaz7") .dependsOn(json) .settings(description := "JSON Library based on Scalaz 7", - libraryDependencies <+= scalaVersion(scalaz7)) + libraryDependencies ++= Seq(scalaz7)) lazy val json_ext = coreProject("json-ext") @@ -84,14 +84,8 @@ object BuildDef extends Build { .settings(description := "Utilities Library", parallelExecution in Test := false, libraryDependencies <++= scalaVersion {sv => Seq(scala_compiler(sv), joda_time, - joda_convert, commons_codec, javamail, log4j, htmlparser)}, - excludeFilter <<= scalaVersion { scalaVersion => - if (scalaVersion.startsWith("2.11")) { - HiddenFileFilter - } else { - HiddenFileFilter || "Position.scala" - } - }) + joda_convert, commons_codec, javamail, log4j, htmlparser)} + ) // Web Projects // ------------ @@ -111,7 +105,7 @@ object BuildDef extends Build { .settings(description := "Webkit Library", parallelExecution in Test := false, libraryDependencies <++= scalaVersion { sv => - Seq(commons_fileupload, rhino, servlet_api, specs2(sv).copy(configurations = Some("provided")), jetty6, + Seq(commons_fileupload, rhino, servlet_api, specs2.copy(configurations = Some("provided")), jetty6, jwebunit) }, initialize in Test <<= (sourceDirectory in Test) { src => diff --git a/project/Dependencies.scala b/project/Dependencies.scala index d4e31d31bd..5f57d17032 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -1,5 +1,5 @@ /* - * Copyright 2011-2013 WorldWide Conferencing, LLC + * Copyright 2011-2014 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. @@ -23,16 +23,10 @@ object Dependencies { type ModuleMap = String => ModuleID - lazy val CVMappingAll = crossMapped("2.10.0" -> "2.10", "2.10.1" -> "2.10", "2.10.2" -> "2.10", "2.10.3" -> "2.10", "2.10.4" -> "2.10", "2.11.0" -> "2.11", "2.11.1" -> "2.11", "2.11.2" -> "2.11") + lazy val CVMappingAll = crossMapped("2.11.2" -> "2.11") lazy val slf4jVersion = "1.7.2" - lazy val scalazGroup = defaultOrMapped("org.scalaz") - lazy val scalazVersion = defaultOrMapped("6.0.4") - lazy val scalaz7Version = defaultOrMapped("7.0.0", "2.11.2" -> "7.0.6") - lazy val specs2Version = defaultOrMapped("1.12.3", "2.11.2" -> "2.3.11") - lazy val scalatestVersion = defaultOrMapped("1.9.1", "2.11.2" -> "2.1.3") - // Compile scope: // Scope available in all classpath, transitive by default. lazy val commons_codec = "commons-codec" % "commons-codec" % "1.6" @@ -47,17 +41,15 @@ object Dependencies { lazy val scalajpa = "org.scala-libs" % "scalajpa" % "1.5" cross CVMappingAll lazy val scalap: ModuleMap = "org.scala-lang" % "scalap" % _ lazy val scala_compiler: ModuleMap = "org.scala-lang" % "scala-compiler" % _ - lazy val scalaz_core: ModuleMap = sv => scalazGroup(sv) % "scalaz-core" % scalazVersion(sv) cross CVMappingAll - lazy val scalaz7_core: ModuleMap = sv => scalazGroup(sv) % "scalaz-core" % scalaz7Version(sv) cross CVMappingAll - lazy val slf4j_api = "org.slf4j" % "slf4j-api" % slf4jVersion + lazy val scalaz7_core = "org.scalaz" % "scalaz-core" % "7.0.6" cross CVMappingAll lazy val squeryl = "org.squeryl" % "squeryl" % "0.9.5-7" cross CVMappingAll + lazy val slf4j_api = "org.slf4j" % "slf4j-api" % slf4jVersion lazy val scala_xml = "org.scala-lang.modules" %% "scala-xml" % "1.0.1" - lazy val rhino = "org.mozilla" % "rhino" % "1.7R4" + lazy val rhino = "org.mozilla" % "rhino" % "1.7R4" lazy val scala_parser = "org.scala-lang.modules" %% "scala-parser-combinators" % "1.0.1" // Aliases lazy val mongo_driver = mongo_java_driver - lazy val scalaz = scalaz_core lazy val scalaz7 = scalaz7_core @@ -82,13 +74,13 @@ object Dependencies { // Test scope: // Scope available only in test classpath, non-transitive by default. // TODO: See if something alternative with lesser footprint can be used instead of mega heavy apacheds - lazy val apacheds = "org.apache.directory.server" % "apacheds-server-integ" % "1.5.5" % "test" // TODO: 1.5.7 - lazy val jetty6 = "org.mortbay.jetty" % "jetty" % "6.1.26" % "test" - lazy val jwebunit = "net.sourceforge.jwebunit" % "jwebunit-htmlunit-plugin" % "2.5" % "test" - lazy val mockito_all = "org.mockito" % "mockito-all" % "1.9.0" % "test" - lazy val scalacheck = "org.scalacheck" %% "scalacheck" % "1.10.1" % "test" - lazy val specs2: ModuleMap = sv => "org.specs2" %% "specs2" % specs2Version(sv) % "test" - lazy val scalatest: ModuleMap = sv => "org.scalatest" %% "scalatest" % scalatestVersion(sv) % "test" - lazy val junit = "junit" % "junit" % "4.8.2" % "test" + lazy val apacheds = "org.apache.directory.server" % "apacheds-server-integ" % "1.5.5" % "test" // TODO: 1.5.7 + lazy val jetty6 = "org.mortbay.jetty" % "jetty" % "6.1.26" % "test" + lazy val jwebunit = "net.sourceforge.jwebunit" % "jwebunit-htmlunit-plugin" % "2.5" % "test" + lazy val mockito_all = "org.mockito" % "mockito-all" % "1.9.0" % "test" + lazy val scalacheck = "org.scalacheck" %% "scalacheck" % "1.10.1" % "test" + lazy val specs2 = "org.specs2" %% "specs2" % "2.3.11" % "test" + lazy val scalatest = "org.scalatest" %% "scalatest" % "2.1.3" % "test" + lazy val junit = "junit" % "junit" % "4.8.2" % "test" } From 5e91795b4c9d07b67bfddd63fccb2813eb4e5308 Mon Sep 17 00:00:00 2001 From: Diego Medina Date: Sat, 16 Aug 2014 00:34:01 -0400 Subject: [PATCH 1038/1949] `S.init` now takes a `Box[Req]` This helps in cases where a CometActor should see an `Empty` `S.request` instead of the `Req.nil` which is a fake Req object --- .../scala/net/liftweb/http/LiftServlet.scala | 8 +-- .../scala/net/liftweb/http/LiftSession.scala | 5 +- .../scala/net/liftweb/http/MVCHelper.scala | 8 +-- .../src/main/scala/net/liftweb/http/Req.scala | 2 +- .../src/main/scala/net/liftweb/http/S.scala | 49 ++++++++++--------- .../scala/net/liftweb/mockweb/MockWeb.scala | 2 +- 6 files changed, 39 insertions(+), 35 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala index ebc450d98e..f9eaec6a96 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala @@ -93,7 +93,7 @@ class LiftServlet extends Loggable { private def wrapState[T](req: Req, session: Box[LiftSession])(f: => T): T = { session match { - case Full(ses) => S.init(req, ses)(f) + case Full(ses) => S.init(Box !! req, ses)(f) case _ => CurrentReq.doWith(req)(f) } } @@ -369,7 +369,7 @@ class LiftServlet extends Loggable { def doSession(r2: Req, s2: LiftSession, continue: Box[() => Nothing]): () => Box[LiftResponse] = { try { - S.init(r2, s2) { + S.init(Box !! r2, s2) { dispatchStatefulRequest(S.request.openOrThrowException("I'm pretty sure this is a full box here"), liftSession, r2, continue) } } catch { @@ -833,7 +833,7 @@ class LiftServlet extends Loggable { private def setupContinuation(request: Req, session: LiftSession, actors: List[(LiftCometActor, Long)]): Any = { val cont = new ContinuationActor(request, session, actors, answers => request.request.resume( - (request, S.init(request, session) + (request, S.init(Box !! request, session) (LiftRules.performTransform( convertAnswersToCometResponse(session, answers.toList, actors)))))) @@ -920,7 +920,7 @@ class LiftServlet extends Loggable { val ret2 = f.get(cometTimeout) openOr Nil - Full(S.init(originalRequest, session) { + Full(S.init(Box !! originalRequest, session) { convertAnswersToCometResponse(session, ret2, actors) }) } finally { diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index d0d33377c4..e3a69696ee 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -332,7 +332,7 @@ object SessionMaster extends LiftActor with Loggable { lockAndBump { Full(SessionInfo(liftSession, userAgent, ipAddress, -1, 0L)) // bumped twice during session creation. Ticket #529 DPP } - S.init(req, liftSession) { + S.init(Box !! req, liftSession) { liftSession.startSession() LiftSession.afterSessionCreate.foreach(_(liftSession, req)) } @@ -1805,7 +1805,7 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, } req match { - case Full(r) => S.init(r, this)(doExec()) + case r@Full(_) => S.init(r, this)(doExec()) case _ => S.initIfUninitted(this)(doExec()) } } @@ -2640,7 +2640,6 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, S.request .filter(_ => comet.sendInitialReq_?) .map(_.snapshot) - comet ! PerformSetupComet2(initialRequest) comet.setCometActorLocale(S.locale) diff --git a/web/webkit/src/main/scala/net/liftweb/http/MVCHelper.scala b/web/webkit/src/main/scala/net/liftweb/http/MVCHelper.scala index 06cffc2432..384acf77bc 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/MVCHelper.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/MVCHelper.scala @@ -75,7 +75,7 @@ trait MVCHelper extends LiftRules.DispatchPF { case _ => curRequest.set(in) - S.init(in, curSession.is) { + S.init(Box !! in, curSession.is) { dispatch.find(_.isDefinedAt(in.path.partPath)).isDefined } } @@ -87,15 +87,15 @@ trait MVCHelper extends LiftRules.DispatchPF { def apply(in: Req): () => Box[LiftResponse] = { val path = in.path.partPath S.session match { - case Full(_) => { + case Full(_) => val resp = dispatch.find(_.isDefinedAt(path)).get. apply(path).toResponse () => resp - } + case _ => - S.init(in, curSession.is) { + S.init(Box !! in, curSession.is) { val resp = dispatch.find(_.isDefinedAt(path)).get. apply(path).toResponse diff --git a/web/webkit/src/main/scala/net/liftweb/http/Req.scala b/web/webkit/src/main/scala/net/liftweb/http/Req.scala index d5af23d6b2..e7c65bf3ac 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Req.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Req.scala @@ -1158,7 +1158,7 @@ class Req(val path: ParsePath, true, this.paramCalculator, this.addlParams) - S.withReq(newReq) { + S.withReq(Full(newReq)) { f(path) } case NotFoundAsNode(node) => Full(LiftRules.convertResponse((node, 404), diff --git a/web/webkit/src/main/scala/net/liftweb/http/S.scala b/web/webkit/src/main/scala/net/liftweb/http/S.scala index 4b8453f9a2..baec6070fc 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/S.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/S.scala @@ -384,7 +384,7 @@ trait S extends HasParams with Loggable with UserAgentCalculator { * * @see Req */ - def request: Box[Req] = (Box !! _request.value) or CurrentReq.box + def request: Box[Req] = _request.box or CurrentReq.box /** @@ -1326,12 +1326,14 @@ trait S extends HasParams with Loggable with UserAgentCalculator { * @param session the LiftSession for this request * @param f Function to execute within the scope of the request and session */ - def init[B](request: Req, session: LiftSession)(f: => B): B = { + def init[B](request: Box[Req], session: LiftSession)(f: => B): B = { if (inS.value) f else { - if (request.stateless_?) + if (request.map(_.stateless_?).openOr(false) ){ session.doAsStateless(_init(request, session)(() => f)) - else _init(request, session)(() => f) + } else { + _init(request, session)(() => f) + } } } @@ -1348,8 +1350,7 @@ trait S extends HasParams with Loggable with UserAgentCalculator { case _ => { val fakeSess = LiftRules.statelessSession.vend.apply(request) try { - _init(request, - fakeSess)(() => f) + _init(Box !! request, fakeSess)(() => f) } finally { // ActorPing.schedule(() => fakeSess.doShutDown(), 0 seconds) } @@ -1693,18 +1694,20 @@ trait S extends HasParams with Loggable with UserAgentCalculator { } } - private def doStatefulRewrite(old: Req): Req = { + private def doStatefulRewrite(old: Box[Req]): Box[Req] = { // Don't even try to rewrite Req.nil - if (statefulRequest_? && - !old.path.partPath.isEmpty && - (old.request ne null)) - Req(old, S.sessionRewriter.map(_.rewrite) ::: + old.map { req => + if (statefulRequest_? && req.path.partPath.nonEmpty && (req.request ne null) ) { + Req(req, S.sessionRewriter.map(_.rewrite) ::: LiftRules.statefulRewrite.toList, Nil, - LiftRules.statelessReqTest.toList) - else old + LiftRules.statelessReqTest.toList) + } else { + req + } + } } - private def _innerInit[B](request: Req, f: () => B): B = { + private def _innerInit[B](request: Box[Req], f: () => B): B = { _lifeTime.doWith(false) { _attrs.doWith((Null,Nil)) { _resBundle.doWith(Nil) { @@ -1712,7 +1715,8 @@ trait S extends HasParams with Loggable with UserAgentCalculator { val statefulRequest = doStatefulRewrite(request) withReq(statefulRequest) { // set the request for standard requests - if (statefulRequest.standardRequest_?) _originalRequest.set(Full(statefulRequest)) + if (statefulRequest.map(_.standardRequest_?).openOr(false)) + _originalRequest.set(statefulRequest) _nest2InnerInit(f) } @@ -1722,9 +1726,9 @@ trait S extends HasParams with Loggable with UserAgentCalculator { } } - private[http] def withReq[T](req: Req)(f: => T): T = { - CurrentReq.doWith(req) { - _request.doWith(req) { + private[http] def withReq[T](req: Box[Req])(f: => T): T = { + CurrentReq.doWith(req openOr null) { + _request.doWith(req openOr null) { f } } @@ -1739,8 +1743,8 @@ trait S extends HasParams with Loggable with UserAgentCalculator { c <- ca) yield c - private def _init[B](request: Req, session: LiftSession)(f: () => B): B = - this._request.doWith(request) { + private def _init[B](request: Box[Req], session: LiftSession)(f: () => B): B = { + this._request.doWith(request.openOr(null)) { _sessionInfo.doWith(session) { _responseHeaders.doWith(new ResponseInfoHolder) { TransientRequestVarHandler(Full(session), @@ -1754,6 +1758,7 @@ trait S extends HasParams with Loggable with UserAgentCalculator { } } } + } /** * This method is a convenience accessor for LiftRules.loggedInTest. You can define your own @@ -2140,7 +2145,7 @@ trait S extends HasParams with Loggable with UserAgentCalculator { */ def initIfUninitted[B](session: LiftSession)(f: => B): B = { if (inS.value) f - else init(Req.nil, session)(f) + else init(Empty, session)(f) } /** @@ -2813,7 +2818,7 @@ trait S extends HasParams with Loggable with UserAgentCalculator { ret } - init(req, ses) { + init(Box !! req, ses) { doRender(ses) } } diff --git a/web/webkit/src/main/scala/net/liftweb/mockweb/MockWeb.scala b/web/webkit/src/main/scala/net/liftweb/mockweb/MockWeb.scala index 021c240924..9aa455f8c0 100644 --- a/web/webkit/src/main/scala/net/liftweb/mockweb/MockWeb.scala +++ b/web/webkit/src/main/scala/net/liftweb/mockweb/MockWeb.scala @@ -180,7 +180,7 @@ object MockWeb { */ private def realTestS [T](newSession : Box[LiftSession])(f : () => T)(req : Req) : T = { val session = newSession openOr LiftSession(req) - S.init(req, session) { + S.init(Box !! req, session) { f() } } From 4a758497a6f909b271cb74e106f8b8a8a98e4aa2 Mon Sep 17 00:00:00 2001 From: Diego Medina Date: Sat, 16 Aug 2014 00:49:02 -0400 Subject: [PATCH 1039/1949] fixed tests --- web/webkit/src/test/scala/net/liftweb/http/SnippetSpec.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/webkit/src/test/scala/net/liftweb/http/SnippetSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/SnippetSpec.scala index a1595412ed..805acfd86c 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/SnippetSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/SnippetSpec.scala @@ -31,9 +31,9 @@ import util.Helpers._ object SnippetSpec extends Specification with XmlMatchers { "SnippetSpec Specification".title - def makeReq = new Req(Req.NilPath, "", GetRequest, Empty, null, + def makeReq = Full(new Req(Req.NilPath, "", GetRequest, Empty, null, System.nanoTime, System.nanoTime, false, - () => ParamCalcInfo(Nil, Map.empty, Nil, Empty), Map()) + () => ParamCalcInfo(Nil, Map.empty, Nil, Empty), Map())) "Templates" should { "Correctly process lift:content_id" in { From 04f6f4ab009424a5e29ef72973a0e90199be8a5d Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 16 Aug 2014 11:21:40 -0400 Subject: [PATCH 1040/1949] Fix issue with new password verification in ProtoUser. When we converted from the original bind strategy, I forgot that the old bind strategy evaluated things eagerly, while CSS selector transforms call by name. This meant that the old bind strategy, when it bound both new password fields (the new password and repeat new password fields) to a single SHtml.password_* invocation, actually bound both fields to the same password input with the same GUID. In the CSS selector transform port, the result was instead two fields with two different GUIDs, which broke the expected behavior of the password field (it was supposed to generate a List of two passwords, which could then be checked for equality). To fix this, we now create the single field ahead of time and pass that to the selector transform so that the same field gets used twice instead of a different field being generated each time. --- .../proto/src/main/scala/net/liftweb/proto/ProtoUser.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/persistence/proto/src/main/scala/net/liftweb/proto/ProtoUser.scala b/persistence/proto/src/main/scala/net/liftweb/proto/ProtoUser.scala index afd8ff8170..9f3c14e57b 100644 --- a/persistence/proto/src/main/scala/net/liftweb/proto/ProtoUser.scala +++ b/persistence/proto/src/main/scala/net/liftweb/proto/ProtoUser.scala @@ -1033,8 +1033,11 @@ trait ProtoUser { } val bind = { + // Use the same password input for both new password fields. + val passwordInput = SHtml.password_*("", LFuncHolder(s => newPassword = s)) + ".old-password" #> SHtml.password("", s => oldPassword = s) & - ".new-password" #> SHtml.password_*("", LFuncHolder(s => newPassword = s)) & + ".new-password" #> passwordInput & "type=submit" #> changePasswordSubmitButton(S.?("change"), testAndSet _) } From d313dd2284372e503b13b00c5b3e8e61c171cdf4 Mon Sep 17 00:00:00 2001 From: Peter Brant Date: Wed, 20 Aug 2014 23:06:46 +0200 Subject: [PATCH 1041/1949] Defer Ajax requests until initialization is complete --- web/webkit/src/main/resources/toserve/lift.js | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/web/webkit/src/main/resources/toserve/lift.js b/web/webkit/src/main/resources/toserve/lift.js index db2ea5e0ca..f23f1e7560 100644 --- a/web/webkit/src/main/resources/toserve/lift.js +++ b/web/webkit/src/main/resources/toserve/lift.js @@ -10,6 +10,7 @@ cometPath = function() { return settings.liftPath + '/comet' }, doCycleQueueCnt = 0, ajaxShowing = false, + initialized = false, pageId = "", uriSuffix, sessionId = "", @@ -122,8 +123,11 @@ ajaxQueue.push(toSend); ajaxQueueSort(); - doCycleQueueCnt++; - doAjaxCycle(); + + if (initialized) { + doCycleQueueCnt++; + doAjaxCycle(); + } return false; // buttons in forms don't trigger the form } @@ -551,11 +555,6 @@ var lift = this; options.onDocumentReady(function() { - var gc = document.body.getAttribute('data-lift-gc'); - if (gc) { - lift.startGc(); - } - var attributes = document.body.attributes, cometGuid, cometVersion, comets = {}; @@ -576,10 +575,12 @@ if (typeof cometGuid != 'undefined') { registerComets(comets, true); } - }); - // start the cycle - doCycleIn200(); + initialized = true; + + // start the cycle + doCycleIn200(); + }); }, logError: settings.logError, ajax: appendToQueue, From 3152ef5d17a87e99378f35d98c9c86cae2b5a795 Mon Sep 17 00:00:00 2001 From: Peter Brant Date: Thu, 21 Aug 2014 12:54:05 +0200 Subject: [PATCH 1042/1949] Check settings.enableGc before starting function GC --- web/webkit/src/main/resources/toserve/lift.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/web/webkit/src/main/resources/toserve/lift.js b/web/webkit/src/main/resources/toserve/lift.js index f23f1e7560..23e0efa42f 100644 --- a/web/webkit/src/main/resources/toserve/lift.js +++ b/web/webkit/src/main/resources/toserve/lift.js @@ -561,7 +561,9 @@ for (var i = 0; i < attributes.length; ++i) { if (attributes[i].name == 'data-lift-gc') { pageId = attributes[i].value; - lift.startGc(); + if (settings.enableGc) { + lift.startGc(); + } } else if (attributes[i].name.match(/^data-lift-comet-/)) { cometGuid = attributes[i].name.substring('data-lift-comet-'.length).toUpperCase(); cometVersion = parseInt(attributes[i].value) From cba6212f46108e1a1e51e92aa1b79565aace4e5f Mon Sep 17 00:00:00 2001 From: Peter Brant Date: Sun, 24 Aug 2014 03:44:23 +0200 Subject: [PATCH 1043/1949] Comet fixes - Unify lastListenerTime (which was previously always zero) with lastListenTime - Make lastRenderTime, delta timestamps, and lastListenerTime consistent with each other as they are directly compared - More accurately track whether any deltas have been sent - Pretend initial render happened at creation time to prevent a second render call from running on the initial Listen message - Rename devMode to alwaysReRenderOnPageLoad --- .../scala/net/liftweb/http/CometActor.scala | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala b/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala index 38f1a698dc..d082c0f4f1 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala @@ -451,7 +451,7 @@ trait CometActor extends LiftActor with LiftCometActor with CssBindImplicits { private val logger = Logger(classOf[CometActor]) val uniqueId = Helpers.nextFuncName private var spanId = uniqueId - @volatile private var lastRenderTime = Helpers.nextNum + @volatile private var lastRenderTime = millis /** * If we're going to cache the last rendering, here's the @@ -466,7 +466,7 @@ trait CometActor extends LiftActor with LiftCometActor with CssBindImplicits { def renderClock: Long = lastRenderTime @volatile - private var _lastListenerTime: Long = 0 + private var _lastListenerTime: Long = lastRenderTime /** * The last "when" sent from the listener @@ -495,6 +495,7 @@ trait CometActor extends LiftActor with LiftCometActor with CssBindImplicits { } } + private var receivedDelta = false private var wasLastFullRender = false @transient private var listeners: List[(ListenerId, AnswerRender => Unit)] = Nil @@ -504,12 +505,12 @@ trait CometActor extends LiftActor with LiftCometActor with CssBindImplicits { private var deltas: List[Delta] = Nil private var jsonHandlerChain: PartialFunction[Any, JsCmd] = Map.empty private val notices = new ListBuffer[(NoticeType.Value, NodeSeq, Box[String])] - private var lastListenTime = millis + private var initialRenderPending = true private var _deltaPruner: (CometActor, List[Delta]) => List[Delta] = (actor, d) => { val m = Helpers.millis - d.filter(d => (m - d.timestamp) < 120000L) + d.filter(d => (m - d.when) < 120000L) } private var _theSession: LiftSession = _ @@ -585,7 +586,7 @@ trait CometActor extends LiftActor with LiftCometActor with CssBindImplicits { /** * Set to 'true' if we should run "render" on every page load */ - protected def devMode = false + protected def alwaysReRenderOnPageLoad = false def hasOuter = true @@ -787,7 +788,7 @@ trait CometActor extends LiftActor with LiftCometActor with CssBindImplicits { private lazy val _mediumPriority: PartialFunction[Any, Unit] = { case l@Unlisten(seq) => { - lastListenTime = millis + _lastListenerTime = millis askingWho match { case Full(who) => forwardMessageTo(l, who) // forward l case _ => listeners = listeners.filter(_._1 != seq) @@ -797,7 +798,7 @@ trait CometActor extends LiftActor with LiftCometActor with CssBindImplicits { case l@Listen(when, seqId, toDo) => { - lastListenTime = millis + _lastListenerTime = millis askingWho match { case Full(who) => forwardMessageTo(l, who) // who forward l case _ => @@ -847,7 +848,7 @@ trait CometActor extends LiftActor with LiftCometActor with CssBindImplicits { askingWho match { case Full(who) => forwardMessageTo(AskRender, who) // forward AskRender case _ => { - if (!deltas.isEmpty || devMode) + if (receivedDelta || alwaysReRenderOnPageLoad) try { performReRender(false) } catch { @@ -910,7 +911,7 @@ trait CometActor extends LiftActor with LiftCometActor with CssBindImplicits { case ShutdownIfPastLifespan => for { - ls <- lifespan if listeners.isEmpty && (lastListenTime + ls.millis + 1000l) < millis + ls <- lifespan if listeners.isEmpty && (lastListenerTime + ls.millis + 1000l) < millis } { this ! ShutDown } @@ -937,8 +938,9 @@ trait CometActor extends LiftActor with LiftCometActor with CssBindImplicits { case PartialUpdateMsg(cmdF) => { val cmd: JsCmd = cmdF.apply - val time = Helpers.nextNum + val time = millis val delta = JsDelta(time, cmd) + receivedDelta = true theSession.updateFunctionMap(S.functionMap, uniqueId, time) S.clearFunctionMap deltas = _deltaPruner(this, (delta :: deltas)) @@ -1039,7 +1041,12 @@ trait CometActor extends LiftActor with LiftCometActor with CssBindImplicits { protected def manualWiringDependencyManagement = false private def performReRender(sendAll: Boolean) { - lastRenderTime = Helpers.nextNum + // Pretend initial render happened at creation time + if (initialRenderPending) { + initialRenderPending = false + } else { + lastRenderTime = millis + } if (sendAll) { cachedFixedRender.reset @@ -1051,6 +1058,7 @@ trait CometActor extends LiftActor with LiftCometActor with CssBindImplicits { wasLastFullRender = sendAll & hasOuter deltas = Nil + receivedDelta = false if (!dontCacheRendering) { lastRendering = render @@ -1122,6 +1130,7 @@ trait CometActor extends LiftActor with LiftCometActor with CssBindImplicits { askingWho = Empty whosAsking = Empty deltas = Nil + receivedDelta = false jsonHandlerChain = Map.empty _running = false _shutDownAt = millis @@ -1308,8 +1317,6 @@ trait CometActor extends LiftActor with LiftCometActor with CssBindImplicits { abstract class Delta(val when: Long) { def js: JsCmd - - val timestamp = millis } case class JsDelta(override val when: Long, js: JsCmd) extends Delta(when) From f4726c838d6a4e8092af92c4a2c55d6102ea4e9d Mon Sep 17 00:00:00 2001 From: Peter Brant Date: Tue, 26 Aug 2014 23:59:34 +0200 Subject: [PATCH 1044/1949] Futher Comet tweaks - Remove (nonworking) attempt to reduce the number of renders and use a different approach that works --- .../scala/net/liftweb/http/CometActor.scala | 40 +++++++++++-------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala b/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala index d082c0f4f1..899250bc53 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala @@ -505,7 +505,6 @@ trait CometActor extends LiftActor with LiftCometActor with CssBindImplicits { private var deltas: List[Delta] = Nil private var jsonHandlerChain: PartialFunction[Any, JsCmd] = Map.empty private val notices = new ListBuffer[(NoticeType.Value, NodeSeq, Box[String])] - private var initialRenderPending = true private var _deltaPruner: (CometActor, List[Delta]) => List[Delta] = (actor, d) => { @@ -848,15 +847,25 @@ trait CometActor extends LiftActor with LiftCometActor with CssBindImplicits { askingWho match { case Full(who) => forwardMessageTo(AskRender, who) // forward AskRender case _ => { - if (receivedDelta || alwaysReRenderOnPageLoad) - try { - performReRender(false) - } catch { - case e if exceptionHandler.isDefinedAt(e) => exceptionHandler(e) - case e: Exception => reportError("Failed performReRender", e) + val out = + if (receivedDelta || alwaysReRenderOnPageLoad) { + try { + Full(performReRender(false)) + } catch { + case e if exceptionHandler.isDefinedAt(e) => { + exceptionHandler(e) + Empty + } + case e: Exception => { + reportError("Failed performReRender", e) + Empty + } + } + } else { + Empty } - reply(AnswerRender(new XmlOrJsCmd(spanId, lastRendering, + reply(AnswerRender(new XmlOrJsCmd(spanId, out.openOr(lastRendering), buildSpan _, notices.toList), whosAsking openOr this, lastRenderTime, true)) clearNotices @@ -1040,13 +1049,8 @@ trait CometActor extends LiftActor with LiftCometActor with CssBindImplicits { */ protected def manualWiringDependencyManagement = false - private def performReRender(sendAll: Boolean) { - // Pretend initial render happened at creation time - if (initialRenderPending) { - initialRenderPending = false - } else { - lastRenderTime = millis - } + private def performReRender(sendAll: Boolean): RenderOut = { + lastRenderTime = millis if (sendAll) { cachedFixedRender.reset @@ -1066,13 +1070,17 @@ trait CometActor extends LiftActor with LiftCometActor with CssBindImplicits { theSession.updateFunctionMap(S.functionMap, uniqueId, lastRenderTime) + val out = lastRendering + val rendered: AnswerRender = - AnswerRender(new XmlOrJsCmd(spanId, lastRendering, buildSpan _, notices.toList), + AnswerRender(new XmlOrJsCmd(spanId, out, buildSpan _, notices.toList), this, lastRenderTime, sendAll) clearNotices listeners.foreach(_._2(rendered)) listeners = Nil + + out } def unWatch = partialUpdate(Call("lift.unlistWatch", uniqueId)) From 6ad175ed7419ff7035ae18d48416ee2db2345d60 Mon Sep 17 00:00:00 2001 From: Tim Nelson Date: Mon, 15 Sep 2014 10:10:59 -0500 Subject: [PATCH 1045/1949] Issue #1622 - Revert DefaultConnectionIdentifier.jndiName to var --- .../src/main/scala/net/liftweb/util/ConnectionIdentifier.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/util/src/main/scala/net/liftweb/util/ConnectionIdentifier.scala b/core/util/src/main/scala/net/liftweb/util/ConnectionIdentifier.scala index fbfc28a7e6..9482c47ee1 100644 --- a/core/util/src/main/scala/net/liftweb/util/ConnectionIdentifier.scala +++ b/core/util/src/main/scala/net/liftweb/util/ConnectionIdentifier.scala @@ -34,5 +34,5 @@ trait ConnectionIdentifier { } case object DefaultConnectionIdentifier extends ConnectionIdentifier { - val jndiName = "lift" + var jndiName = "lift" } From 659503a24bfc151d57e1279c7500fadd50ee3c18 Mon Sep 17 00:00:00 2001 From: Peter Brant Date: Tue, 16 Sep 2014 18:26:23 -0500 Subject: [PATCH 1046/1949] Fix sequence/timestamp issues for real this time --- .../scala/net/liftweb/http/CometActor.scala | 20 ++++++++++++------- .../scala/net/liftweb/http/LiftSession.scala | 2 +- .../src/main/scala/net/liftweb/http/S.scala | 2 +- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala b/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala index 899250bc53..4431c88044 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala @@ -325,6 +325,7 @@ trait LiftCometActor extends TypedActor[Any, Any] with ForwardableActor[Any, Any * @return the last when sent from the listener */ def lastListenerTime: Long + def lastRenderTime: Long private[http] def callInitCometActor(creationInfo: CometCreationInfo) { initCometActor(creationInfo) @@ -451,7 +452,7 @@ trait CometActor extends LiftActor with LiftCometActor with CssBindImplicits { private val logger = Logger(classOf[CometActor]) val uniqueId = Helpers.nextFuncName private var spanId = uniqueId - @volatile private var lastRenderTime = millis + @volatile private var _lastRenderTime = Helpers.nextNum /** * If we're going to cache the last rendering, here's the @@ -466,7 +467,7 @@ trait CometActor extends LiftActor with LiftCometActor with CssBindImplicits { def renderClock: Long = lastRenderTime @volatile - private var _lastListenerTime: Long = lastRenderTime + private var _lastListenerTime: Long = millis /** * The last "when" sent from the listener @@ -474,6 +475,8 @@ trait CometActor extends LiftActor with LiftCometActor with CssBindImplicits { */ def lastListenerTime: Long = _lastListenerTime + def lastRenderTime: Long = _lastRenderTime + /** * The last rendering (cached or not) */ @@ -509,7 +512,7 @@ trait CometActor extends LiftActor with LiftCometActor with CssBindImplicits { private var _deltaPruner: (CometActor, List[Delta]) => List[Delta] = (actor, d) => { val m = Helpers.millis - d.filter(d => (m - d.when) < 120000L) + d.filter(d => (m - d.timestamp) < 120000L) } private var _theSession: LiftSession = _ @@ -807,16 +810,17 @@ trait CometActor extends LiftActor with LiftCometActor with CssBindImplicits { whosAsking openOr this, lastRenderTime, wasLastFullRender)) clearNotices } else { - lastRenderTime = when + _lastRenderTime = when deltas.filter(_.when > when) match { case Nil => listeners = (seqId, toDo) :: listeners - case all@(hd :: xs) => + case all@(hd :: xs) => { toDo(AnswerRender(new XmlOrJsCmd(spanId, Empty, Empty, Full(all.reverse.foldLeft(Noop)(_ & _.js)), Empty, buildSpan, false, notices.toList), whosAsking openOr this, hd.when, false)) clearNotices deltas = _deltaPruner(this, deltas) + } } } } @@ -947,7 +951,7 @@ trait CometActor extends LiftActor with LiftCometActor with CssBindImplicits { case PartialUpdateMsg(cmdF) => { val cmd: JsCmd = cmdF.apply - val time = millis + val time = Helpers.nextNum val delta = JsDelta(time, cmd) receivedDelta = true theSession.updateFunctionMap(S.functionMap, uniqueId, time) @@ -1050,7 +1054,7 @@ trait CometActor extends LiftActor with LiftCometActor with CssBindImplicits { protected def manualWiringDependencyManagement = false private def performReRender(sendAll: Boolean): RenderOut = { - lastRenderTime = millis + _lastRenderTime = Helpers.nextNum if (sendAll) { cachedFixedRender.reset @@ -1325,6 +1329,8 @@ trait CometActor extends LiftActor with LiftCometActor with CssBindImplicits { abstract class Delta(val when: Long) { def js: JsCmd + + val timestamp = millis } case class JsDelta(override val when: Long, js: JsCmd) extends Delta(when) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index 06ef367a9b..4ac2b595ea 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -2382,7 +2382,7 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, private def lastWhenDeltaPruner: (LiftCometActor, List[Delta]) => List[Delta] = (ca, dl) => { val when = ca.lastListenerTime - dl.filter(d => when <= d.when) + dl.filter(d => when <= d.timestamp) } diff --git a/web/webkit/src/main/scala/net/liftweb/http/S.scala b/web/webkit/src/main/scala/net/liftweb/http/S.scala index 41225a3918..e9fa2df101 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/S.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/S.scala @@ -808,7 +808,7 @@ trait S extends HasParams with Loggable with UserAgentCalculator { */ def addComet(cometActor: LiftCometActor): Unit = { requestCometVersions.set( - requestCometVersions.is + CVP(cometActor.uniqueId, cometActor.lastListenerTime) + requestCometVersions.is + CVP(cometActor.uniqueId, cometActor.lastRenderTime) ) } From 2036c11eec73b6b60c1d1abe1d7bf0f5499d2d27 Mon Sep 17 00:00:00 2001 From: Peter Brant Date: Thu, 25 Sep 2014 23:54:21 +0200 Subject: [PATCH 1047/1949] Remove lastWhenDeltaPruner It was a no-op before (as lastListenerTime was always zero) and can result in lost partial updates now that it works. --- .../main/scala/net/liftweb/http/LiftSession.scala | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index 4ac2b595ea..30587afc82 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -2372,20 +2372,10 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, S.addComet(ca) - ca ! SetDeltaPruner(lastWhenDeltaPruner) - ca } } - - private def lastWhenDeltaPruner: (LiftCometActor, List[Delta]) => List[Delta] = - (ca, dl) => { - val when = ca.lastListenerTime - dl.filter(d => when <= d.timestamp) - } - - /** * Processes the surround tag and other lift tags * @@ -2831,8 +2821,6 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, ca ! PerformSetupComet2(Empty) - ca ! SetDeltaPruner(lastWhenDeltaPruner) - S.addComet(ca) val currentReq: Box[Req] = S.request.map(_.snapshot) From c3bfd2688e34b9b43867eb871e6a3300e8c5d005 Mon Sep 17 00:00:00 2001 From: Tim Nelson Date: Sat, 27 Sep 2014 06:34:46 -0500 Subject: [PATCH 1048/1949] Deprecate and use property to set DefaultConnectionIdentifier.jndiName --- .../liftweb/util/ConnectionIdentifier.scala | 3 +- .../src/test/resources/test.default.props | 1 + .../util/ConnectionIdentifierSpec.scala | 34 +++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 core/util/src/test/resources/test.default.props create mode 100644 core/util/src/test/scala/net/liftweb/util/ConnectionIdentifierSpec.scala diff --git a/core/util/src/main/scala/net/liftweb/util/ConnectionIdentifier.scala b/core/util/src/main/scala/net/liftweb/util/ConnectionIdentifier.scala index 9482c47ee1..b2aa20702f 100644 --- a/core/util/src/main/scala/net/liftweb/util/ConnectionIdentifier.scala +++ b/core/util/src/main/scala/net/liftweb/util/ConnectionIdentifier.scala @@ -34,5 +34,6 @@ trait ConnectionIdentifier { } case object DefaultConnectionIdentifier extends ConnectionIdentifier { - var jndiName = "lift" + @deprecated("will become a read-only val. It can be modified by setting 'default.jndi.name' in the props file", "2.6") + var jndiName = Props.get("default.jndi.name", "lift") } diff --git a/core/util/src/test/resources/test.default.props b/core/util/src/test/resources/test.default.props new file mode 100644 index 0000000000..ad4fa3f29b --- /dev/null +++ b/core/util/src/test/resources/test.default.props @@ -0,0 +1 @@ +default.jndi.name=from_props diff --git a/core/util/src/test/scala/net/liftweb/util/ConnectionIdentifierSpec.scala b/core/util/src/test/scala/net/liftweb/util/ConnectionIdentifierSpec.scala new file mode 100644 index 0000000000..52e679801e --- /dev/null +++ b/core/util/src/test/scala/net/liftweb/util/ConnectionIdentifierSpec.scala @@ -0,0 +1,34 @@ +/* + * Copyright 2014 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 util + +import org.specs2.mutable.Specification + +/** + * Systems under specification for ConnectionIdentifier. + */ +object ConnectionIdentifierSpec extends Specification { + "ConnectionIdentifier Specification".title + + "Connection identifier" should { + + "be set by property" in { + DefaultConnectionIdentifier.jndiName must_== "from_props" + } + } +} From 77ba2b73e800358405f9bbbe1e2ac3f26126d951 Mon Sep 17 00:00:00 2001 From: Torsten Uhlmann Date: Sat, 4 Oct 2014 10:59:47 +0200 Subject: [PATCH 1049/1949] Fixes #1627 --- .../mapper/src/main/scala/net/liftweb/mapper/MetaMapper.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/persistence/mapper/src/main/scala/net/liftweb/mapper/MetaMapper.scala b/persistence/mapper/src/main/scala/net/liftweb/mapper/MetaMapper.scala index 6bc2078f5f..af73372cef 100644 --- a/persistence/mapper/src/main/scala/net/liftweb/mapper/MetaMapper.scala +++ b/persistence/mapper/src/main/scala/net/liftweb/mapper/MetaMapper.scala @@ -678,6 +678,7 @@ trait MetaMapper[A<:Mapper[A]] extends BaseMetaMapper with Mapper[A] { case JField("$persisted", JBool(per)) => ret.persisted_? = per true + case _ => false } for { From 0388770d2c1580b50a88de242def52111c44c582 Mon Sep 17 00:00:00 2001 From: Torsten Uhlmann Date: Sat, 4 Oct 2014 11:50:44 +0200 Subject: [PATCH 1050/1949] Fixes #1621. Adds JValue as a specific return type to all asJValue definitions --- .../net/liftweb/mongodb/record/field/BsonRecordField.scala | 2 +- .../scala/net/liftweb/mongodb/record/field/DBRefField.scala | 2 +- .../scala/net/liftweb/mongodb/record/field/JObjectField.scala | 2 +- .../liftweb/mongodb/record/field/MongoCaseClassField.scala | 4 ++-- .../net/liftweb/mongodb/record/field/MongoListField.scala | 4 ++-- .../net/liftweb/mongodb/record/field/MongoMapField.scala | 2 +- .../record/src/main/scala/net/liftweb/record/MetaRecord.scala | 2 +- .../record/src/main/scala/net/liftweb/record/Record.scala | 2 +- .../src/main/scala/net/liftweb/record/field/BinaryField.scala | 2 +- .../main/scala/net/liftweb/record/field/BooleanField.scala | 2 +- .../main/scala/net/liftweb/record/field/DateTimeField.scala | 2 +- .../main/scala/net/liftweb/record/field/DecimalField.scala | 2 +- .../src/main/scala/net/liftweb/record/field/DoubleField.scala | 2 +- .../scala/net/liftweb/record/field/joda/JodaTimeField.scala | 2 +- .../src/test/scala/net/liftweb/squerylrecord/Fixtures.scala | 2 +- 15 files changed, 17 insertions(+), 17 deletions(-) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala index c6825ccfee..6664a0d099 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala @@ -94,7 +94,7 @@ class BsonRecordListField[OwnerType <: BsonRecord[OwnerType], SubRecordType <: B valueMeta.fromDBObject(dbo.get(k.toString).asInstanceOf[DBObject]) }))) - override def asJValue = JArray(value.map(_.asJValue)) + override def asJValue: JValue = JArray(value.map(_.asJValue)) override def setFromJValue(jvalue: JValue) = jvalue match { case JNothing|JNull if optional_? => setBox(Empty) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/DBRefField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/DBRefField.scala index 2cdc15dccc..a411d04ef0 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/DBRefField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/DBRefField.scala @@ -58,7 +58,7 @@ class DBRefField[OwnerType <: BsonRecord[OwnerType], RefType <: MongoRecord[RefT def asJs = Str(toString) - def asJValue = (JNothing: JValue) // not implemented + def asJValue: JValue = (JNothing: JValue) // not implemented def setFromJValue(jvalue: JValue) = Empty // not implemented diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala index 3a62172e6b..987a820862 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala @@ -36,7 +36,7 @@ with MongoFieldFlavor[JObject] { def owner = rec - def asJValue = valueBox openOr (JNothing: JValue) + def asJValue: JValue = valueBox openOr (JNothing: JValue) def setFromJValue(jvalue: JValue): Box[JObject] = jvalue match { case JNothing|JNull if optional_? => setBox(Empty) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoCaseClassField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoCaseClassField.scala index ab624a4f86..1e2bd346c0 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoCaseClassField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoCaseClassField.scala @@ -47,7 +47,7 @@ class MongoCaseClassField[OwnerType <: Record[OwnerType],CaseType](rec: OwnerTyp override def defaultValue = null.asInstanceOf[MyType] override def optional_? = true - def asJValue = valueBox.map(v => Extraction.decompose(v)) openOr (JNothing: JValue) + def asJValue: JValue = valueBox.map(v => Extraction.decompose(v)) openOr (JNothing: JValue) def setFromJValue(jvalue: JValue): Box[CaseType] = jvalue match { case JNothing|JNull => setBox(Empty) @@ -94,7 +94,7 @@ class MongoCaseClassListField[OwnerType <: Record[OwnerType],CaseType](rec: Owne override def defaultValue: MyType = Nil override def optional_? = true - def asJValue = JArray(value.map(v => Extraction.decompose(v))) + def asJValue: JValue = JArray(value.map(v => Extraction.decompose(v))) def setFromJValue(jvalue: JValue): Box[MyType] = jvalue match { case JArray(contents) => setBox(Full(contents.flatMap(s => Helpers.tryo[CaseType]{ s.extract[CaseType] }))) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoListField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoListField.scala index 0dd4d5110b..b7125da796 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoListField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoListField.scala @@ -120,7 +120,7 @@ class MongoListField[OwnerType <: BsonRecord[OwnerType], ListType: Manifest](rec if (options.length > 0) Full(elem) else Empty - def asJValue = JArray(value.map(li => li.asInstanceOf[AnyRef] match { + def asJValue: JValue = JArray(value.map(li => li.asInstanceOf[AnyRef] match { case x if primitive_?(x.getClass) => primitive2jvalue(x) case x if mongotype_?(x.getClass) => mongotype2jvalue(x)(owner.meta.formats) case x if datetype_?(x.getClass) => datetype2jvalue(x)(owner.meta.formats) @@ -167,7 +167,7 @@ class MongoJsonObjectListField[OwnerType <: BsonRecord[OwnerType], JObjectType < valueMeta.create(JObjectParser.serialize(dbo.get(k.toString))(owner.meta.formats).asInstanceOf[JObject])(owner.meta.formats) }))) - override def asJValue = JArray(value.map(_.asJObject()(owner.meta.formats))) + override def asJValue: JValue = JArray(value.map(_.asJObject()(owner.meta.formats))) override def setFromJValue(jvalue: JValue) = jvalue match { case JNothing|JNull if optional_? => setBox(Empty) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoMapField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoMapField.scala index af9d756909..c9102dc373 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoMapField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoMapField.scala @@ -75,7 +75,7 @@ class MongoMapField[OwnerType <: BsonRecord[OwnerType], MapValueType](rec: Owner def toForm: Box[NodeSeq] = Empty - def asJValue = JObject(value.keys.map { + def asJValue: JValue = JObject(value.keys.map { k => JField(k, value(k).asInstanceOf[AnyRef] match { case x if primitive_?(x.getClass) => primitive2jvalue(x) diff --git a/persistence/record/src/main/scala/net/liftweb/record/MetaRecord.scala b/persistence/record/src/main/scala/net/liftweb/record/MetaRecord.scala index 1d3c13b090..4787f5e788 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/MetaRecord.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/MetaRecord.scala @@ -245,7 +245,7 @@ trait MetaRecord[BaseRecord <: Record[BaseRecord]] { } /** Encode a record instance into a JValue */ - def asJValue(rec: BaseRecord): JObject = { + def asJValue(rec: BaseRecord): JValue = { JObject(fields(rec).map(f => JField(f.name, f.asJValue))) } diff --git a/persistence/record/src/main/scala/net/liftweb/record/Record.scala b/persistence/record/src/main/scala/net/liftweb/record/Record.scala index c6d3fdf35f..1d3f910516 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/Record.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/Record.scala @@ -91,7 +91,7 @@ trait Record[MyType <: Record[MyType]] extends FieldContainer { def asJsExp: JsExp = meta.asJsExp(this) /** Encode this record instance as a JObject */ - def asJValue: JObject = meta.asJValue(this) + def asJValue: JValue = meta.asJValue(this) /** Set the fields of this record from the given JValue */ def setFieldsFromJValue(jvalue: JValue): Box[Unit] = meta.setFieldsFromJValue(this, jvalue) diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/BinaryField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/BinaryField.scala index 7c1f319249..cfe34b4fd1 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/BinaryField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/BinaryField.scala @@ -43,7 +43,7 @@ trait BinaryTypedField extends TypedField[Array[Byte]] { def asJs = valueBox.map(v => Str(hexEncode(v))) openOr JsNull - def asJValue = asJString(base64Encode _) + def asJValue: JValue = asJString(base64Encode _) def setFromJValue(jvalue: JValue) = setFromJString(jvalue)(s => tryo(base64Decode(s))) } diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/BooleanField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/BooleanField.scala index eb055a0096..7751bdcc8d 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/BooleanField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/BooleanField.scala @@ -60,7 +60,7 @@ trait BooleanTypedField extends TypedField[Boolean] { def asJs: JsExp = valueBox.map(boolToJsExp) openOr JsNull - def asJValue = valueBox.map(JBool) openOr (JNothing: JValue) + def asJValue: JValue = valueBox.map(JBool) openOr (JNothing: JValue) def setFromJValue(jvalue: JValue) = jvalue match { case JNothing|JNull if optional_? => setBox(Empty) case JBool(b) => setBox(Full(b)) diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/DateTimeField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/DateTimeField.scala index 9ef21ba577..0103ec65d0 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/DateTimeField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/DateTimeField.scala @@ -64,7 +64,7 @@ trait DateTimeTypedField extends TypedField[Calendar] { def asJs = valueBox.map(v => Str(formats.dateFormat.format(v.getTime))) openOr JsNull - def asJValue = asJString(v => formats.dateFormat.format(v.getTime)) + def asJValue: JValue = asJString(v => formats.dateFormat.format(v.getTime)) def setFromJValue(jvalue: JValue) = setFromJString(jvalue) { v => formats.dateFormat.parse(v).map(d => { val cal = Calendar.getInstance diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/DecimalField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/DecimalField.scala index f46a1a697b..26ac91b523 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/DecimalField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/DecimalField.scala @@ -49,7 +49,7 @@ trait DecimalTypedField extends NumericTypedField[BigDecimal] { def set_!(in: BigDecimal): BigDecimal = new BigDecimal(in.bigDecimal.setScale(scale, context.getRoundingMode)) - def asJValue = asJString(_.toString) + def asJValue: JValue = asJString(_.toString) def setFromJValue(jvalue: JValue) = setFromJString(jvalue)(s => tryo(BigDecimal(s))) } diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/DoubleField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/DoubleField.scala index e1cad37d8d..276e086c25 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/DoubleField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/DoubleField.scala @@ -42,7 +42,7 @@ trait DoubleTypedField extends NumericTypedField[Double] { def defaultValue = 0.0 - def asJValue = valueBox.map(JDouble) openOr (JNothing: JValue) + def asJValue: JValue = valueBox.map(JDouble) openOr (JNothing: JValue) def setFromJValue(jvalue: JValue) = jvalue match { case JNothing|JNull if optional_? => setBox(Empty) diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/joda/JodaTimeField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/joda/JodaTimeField.scala index 3ebd2d6bef..646d97d8d7 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/joda/JodaTimeField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/joda/JodaTimeField.scala @@ -62,7 +62,7 @@ trait JodaTimeTypedField extends TypedField[DateTime] with JodaHelpers { protected def asJInt(encode: MyType => BigInt): JValue = valueBox.map(v => JInt(encode(v))) openOr (JNothing: JValue) - def asJValue = asJInt(v => v.getMillis) + def asJValue: JValue = asJInt(v => v.getMillis) def setFromJValue(jvalue: JValue) = setFromJInt(jvalue) { v => toDateTime(v) } diff --git a/persistence/squeryl-record/src/test/scala/net/liftweb/squerylrecord/Fixtures.scala b/persistence/squeryl-record/src/test/scala/net/liftweb/squerylrecord/Fixtures.scala index 5d8b826f4c..b736c39b8f 100644 --- a/persistence/squeryl-record/src/test/scala/net/liftweb/squerylrecord/Fixtures.scala +++ b/persistence/squeryl-record/src/test/scala/net/liftweb/squerylrecord/Fixtures.scala @@ -115,7 +115,7 @@ class SpecialField[OwnerType <: Record[OwnerType]](rec: OwnerType) case v => setBox(Full(v.toString)) } override def setFromJValue(jValue: JValue) = setBox(Full(jValue.toString)) - override def asJValue = JString(get) + override def asJValue: JValue = JString(get) override def asJs = Str(get) override def toForm = Full(scala.xml.Text(get)) } From 7bc9a68983aede87ae74a8626241c034e69e4cf6 Mon Sep 17 00:00:00 2001 From: Diego Medina Date: Sun, 19 Oct 2014 15:41:24 -0400 Subject: [PATCH 1051/1949] Add implicit from tuple to SelectableOption fixed #1628 Add implicit from tuple to SelectableOption --- web/webkit/src/main/scala/net/liftweb/http/SHtml.scala | 2 ++ .../src/test/scala/net/liftweb/http/SHtmlSpec.scala | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala b/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala index 647909cdc5..ec9f00f80a 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala @@ -1842,6 +1842,8 @@ trait SHtml { seq.collect { case (value, label) => SelectableOption(value, label) } + implicit def tupleToSelectableOption[T](tuple: (T, String)): SelectableOption[T] = + SelectableOption(tuple._1, tuple._2) } private def optionToElem(option: SelectableOption[String]): Elem = diff --git a/web/webkit/src/test/scala/net/liftweb/http/SHtmlSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/SHtmlSpec.scala index d055d7d9e6..c06036316d 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/SHtmlSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/SHtmlSpec.scala @@ -24,7 +24,7 @@ import Helpers._ import net.liftweb.mockweb.MockWeb._ object SHtmlSpec extends Specification with XmlMatchers { - "NamedCometPerTabSpec Specification".title + "SHtmlSpec Specification".title val html1= @@ -48,6 +48,10 @@ object SHtmlSpec extends Specification with XmlMatchers { "create a number input field with step='1.0'" in { inputField3 must \("input", "step" -> "1.0") } - + "Use the implicit from tuple to SelectableOption" in { + testS("/")( ("#number" #> SHtml.select(Seq("Yes" -> "Yes" , "No" -> "No"), Some("value"), s => println(s) , "class" -> "form-control")).apply(html1) ) + //compiling is enough for this test + 1 must_== 1 + } } } From 521e5f044c4b586cf1c84522dddded0d69b08526 Mon Sep 17 00:00:00 2001 From: Dave Whittaker Date: Tue, 21 Oct 2014 18:24:33 -0400 Subject: [PATCH 1052/1949] Remove unnecessary synchronization while getting/setting ContainerVar values. Refs issue #1629. --- web/webkit/src/main/scala/net/liftweb/http/Vars.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/Vars.scala b/web/webkit/src/main/scala/net/liftweb/http/Vars.scala index 50dc194b13..d8901dd05d 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Vars.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Vars.scala @@ -181,6 +181,7 @@ private[http] trait HasLogUnreadVal { * provides a subset of these. */ abstract class ContainerVar[T](dflt: => T)(implicit containerSerializer: ContainerSerializer[T]) extends AnyVar[T, ContainerVar[T]](dflt) with LazyLoggable { + override protected def findFunc(name: String): Box[T] = S.session match { case Full(session) => { localGet(session, name) match { @@ -188,7 +189,6 @@ abstract class ContainerVar[T](dflt: => T)(implicit containerSerializer: Contain case _ => Empty } } - case _ => { if (showWarningWhenAccessedOutOfSessionScope_?) logger.warn("Getting a SessionVar " + name + " outside session scope") // added warning per issue 188 @@ -233,7 +233,7 @@ abstract class ContainerVar[T](dflt: => T)(implicit containerSerializer: Contain * In the case of ContainerVar, we synchronize on the ContainerVar * instance itself. */ - def doSync[F](f: => F): F = this.synchronized(f) + def doSync[F](f: => F): F = f def showWarningWhenAccessedOutOfSessionScope_? = false From 63889f6eaf2eb7dead176cc029a19fe088c8b57b Mon Sep 17 00:00:00 2001 From: Dave Whittaker Date: Thu, 23 Oct 2014 10:13:28 -0400 Subject: [PATCH 1053/1949] Updated documentation explaining why ContainerVar doesn't need explicit synchronization. Refs #1629. --- web/webkit/src/main/scala/net/liftweb/http/Vars.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/Vars.scala b/web/webkit/src/main/scala/net/liftweb/http/Vars.scala index d8901dd05d..c22dc8d923 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Vars.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Vars.scala @@ -230,8 +230,8 @@ abstract class ContainerVar[T](dflt: => T)(implicit containerSerializer: Contain * Different Vars require different mechanisms for synchronization. This method implements * the Var specific synchronization mechanism. * - * In the case of ContainerVar, we synchronize on the ContainerVar - * instance itself. + * In the case of ContainerVar, we don't need to do any explicit synchronization. Values are + * stored in the HttpSession, which already gives us atomic get and set operations. */ def doSync[F](f: => F): F = f From c6171b580a2e60b2bb19822dd373d171329a4689 Mon Sep 17 00:00:00 2001 From: Diego Medina Date: Fri, 31 Oct 2014 23:12:10 -0400 Subject: [PATCH 1054/1949] Renaming the Position.scala file to ScalaPosition.scala to avoid name conflicts with the private class fixes #1635 --- .../main/scala/net/liftweb/util/PCDataMarkupParser.scala | 4 ++-- .../liftweb/util/{Position.scala => ScalaPosition.scala} | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) rename core/util/src/main/scala/net/liftweb/util/{Position.scala => ScalaPosition.scala} (92%) diff --git a/core/util/src/main/scala/net/liftweb/util/PCDataMarkupParser.scala b/core/util/src/main/scala/net/liftweb/util/PCDataMarkupParser.scala index 315e6a88a1..d4eb7c90eb 100644 --- a/core/util/src/main/scala/net/liftweb/util/PCDataMarkupParser.scala +++ b/core/util/src/main/scala/net/liftweb/util/PCDataMarkupParser.scala @@ -157,8 +157,8 @@ class PCDataXmlParser(val input: Source) extends ConstructingHandler with PCData import scala.io._ - val line = Position.line(pos) - val col = Position.column(pos) + val line = ScalaPosition.line(pos) + val col = ScalaPosition.column(pos) val report = curInput.descr + ":" + line + ":" + col + ": " + msg System.err.println(report) try { diff --git a/core/util/src/main/scala/net/liftweb/util/Position.scala b/core/util/src/main/scala/net/liftweb/util/ScalaPosition.scala similarity index 92% rename from core/util/src/main/scala/net/liftweb/util/Position.scala rename to core/util/src/main/scala/net/liftweb/util/ScalaPosition.scala index f3ac82021f..fcade4c012 100644 --- a/core/util/src/main/scala/net/liftweb/util/Position.scala +++ b/core/util/src/main/scala/net/liftweb/util/ScalaPosition.scala @@ -36,10 +36,11 @@ package io /** * This was made private in scala 2.11.0 but there is no alternative for us to use, so here, copy/paste for now. + * We renamed it because having a private vs public class with the same name causes errors with the assembly plugin + * and may/(will?) cause errors at runtime. */ -@deprecated("This class will be removed.", "2.10.0") -abstract class Position { +abstract class ScalaPosition { /** Definable behavior for overflow conditions. */ def checkInput(line: Int, column: Int): Unit @@ -73,7 +74,7 @@ abstract class Position { def toString(pos: Int): String = line(pos) + ":" + column(pos) } -object Position extends Position { +object ScalaPosition extends ScalaPosition { def checkInput(line: Int, column: Int) { if (line < 0) throw new IllegalArgumentException(line + " < 0") From fa1383fa514665672091ea1868d1c1ab2765a1ca Mon Sep 17 00:00:00 2001 From: Diego Medina Date: Fri, 31 Oct 2014 23:18:02 -0400 Subject: [PATCH 1055/1949] remove sbt code related to Position.scala --- project/Build.scala | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index fb25b70a2d..bcb6cf2551 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -103,14 +103,8 @@ object BuildDef extends Build { .settings(description := "Utilities Library", parallelExecution in Test := false, libraryDependencies <++= scalaVersion {sv => Seq(scala_compiler(sv), joda_time, - joda_convert, commons_codec, javamail, log4j, htmlparser)}, - excludeFilter <<= scalaVersion { scalaVersion => - if (scalaVersion.startsWith("2.11")) { - HiddenFileFilter - } else { - HiddenFileFilter || "Position.scala" - } - }) + joda_convert, commons_codec, javamail, log4j, htmlparser)} + ) // Web Projects // ------------ From 17871dd508e37dca0a00183d987f236b0d38331b Mon Sep 17 00:00:00 2001 From: Diego Medina Date: Fri, 31 Oct 2014 23:23:46 -0400 Subject: [PATCH 1056/1949] ajaxPostTimeout doesn't have to be multiplied by 1000, because it is used as is on ScriptRenderer, so the client should just get the same value in both cases (well, ajaxPostTimeout should be the same timeout + 500ms) fixes #1617 --- web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala index 4d6821c53a..f5ea5bfe7f 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala @@ -543,7 +543,7 @@ class LiftServlet extends Loggable { // we want the original thread to complete so that it can provide an // answer for future retries, we don't want retries tying up resources // when the client won't receive the response anyway. - private lazy val ajaxPostTimeout: Long = LiftRules.ajaxPostTimeout * 1000L + 500L + private lazy val ajaxPostTimeout: Long = LiftRules.ajaxPostTimeout + 500L /** * Kick off AJAX handling. Extracts relevant versions and handles the * begin/end servicing requests. Then checks whether to wait on an From 972c4742d83a9fb977f1c5c0687f9abbafe53be8 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 8 Nov 2014 19:53:53 -0500 Subject: [PATCH 1057/1949] Allow arbitrary ResponseShortcutExceptions in AJAX calls. --- web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala index f9eaec6a96..c6ecaff38f 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala @@ -623,6 +623,8 @@ class LiftServlet extends Loggable { case ResponseShortcutException(_, Full(to), _) => import net.liftweb.http.js.JsCmds._ List(RedirectTo(to)) + case responseShortcut: ResponseShortcutException => + List(responseShortcut.response) }) val what2 = what.flatMap { From e24f48ff80eace687b976e70313981ce6e9df78f Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 16 Nov 2014 14:08:10 -0500 Subject: [PATCH 1058/1949] Issue #1622 - Make DefaultConnectionIdentifier.jndiName immutable. We make it a val and undeprecate it. --- .../src/main/scala/net/liftweb/util/ConnectionIdentifier.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/ConnectionIdentifier.scala b/core/util/src/main/scala/net/liftweb/util/ConnectionIdentifier.scala index b2aa20702f..0d9cc88a40 100644 --- a/core/util/src/main/scala/net/liftweb/util/ConnectionIdentifier.scala +++ b/core/util/src/main/scala/net/liftweb/util/ConnectionIdentifier.scala @@ -34,6 +34,5 @@ trait ConnectionIdentifier { } case object DefaultConnectionIdentifier extends ConnectionIdentifier { - @deprecated("will become a read-only val. It can be modified by setting 'default.jndi.name' in the props file", "2.6") - var jndiName = Props.get("default.jndi.name", "lift") + val jndiName = Props.get("default.jndi.name", "lift") } From d5fecefce7ad6a6bf55ca4789bca8f614086ddf4 Mon Sep 17 00:00:00 2001 From: Peter Brant Date: Fri, 26 Sep 2014 15:34:07 +0200 Subject: [PATCH 1059/1949] New comet mode Add additional comet actor mode that supports a continuous stream of partial updates. --- .../scala/net/liftweb/http/CometActor.scala | 38 ++++++++++++++++--- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala b/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala index 4431c88044..ccba48d906 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala @@ -452,7 +452,11 @@ trait CometActor extends LiftActor with LiftCometActor with CssBindImplicits { private val logger = Logger(classOf[CometActor]) val uniqueId = Helpers.nextFuncName private var spanId = uniqueId - @volatile private var _lastRenderTime = Helpers.nextNum + @volatile private var _lastRenderTime = + if (partialUpdateStream_?) + 0 + else + Helpers.nextNum /** * If we're going to cache the last rendering, here's the @@ -477,12 +481,30 @@ trait CometActor extends LiftActor with LiftCometActor with CssBindImplicits { def lastRenderTime: Long = _lastRenderTime + /** + * If this method returns true, this CometActor will be treated as a source + * of continuous partial updates. Usually the basic assumption of a Lift + * Comet Actor is that render will display the current state of the actor. + * Partial updates may modify that display, but it is always possible to + * recreate the complete display by calling render again (in which case + * pending partial updates can be safely discarded). If this method + * returns true, render will not be called, but partial updates are + * guaranteed to always be sent to the client. This is useful as a means to + * push arbitrary JavaScript to the client without a distinct UI for the + * comet actor itself. + */ + protected def partialUpdateStream_? : Boolean = false + /** * The last rendering (cached or not) */ private def lastRendering: RenderOut = if (dontCacheRendering) { - val ret = render + val ret = + if (partialUpdateStream_?) + nsToNsFuncToRenderOut(_ => NodeSeq.Empty) + else + render theSession.updateFunctionMap(S.functionMap, uniqueId, lastRenderTime) ret } else { @@ -804,7 +826,7 @@ trait CometActor extends LiftActor with LiftCometActor with CssBindImplicits { askingWho match { case Full(who) => forwardMessageTo(l, who) // who forward l case _ => - if (when < lastRenderTime) { + if (when < lastRenderTime && ! partialUpdateStream_?) { toDo(AnswerRender(new XmlOrJsCmd(spanId, lastRendering, buildSpan _, notices.toList), whosAsking openOr this, lastRenderTime, wasLastFullRender)) @@ -819,9 +841,9 @@ trait CometActor extends LiftActor with LiftCometActor with CssBindImplicits { Full(all.reverse.foldLeft(Noop)(_ & _.js)), Empty, buildSpan, false, notices.toList), whosAsking openOr this, hd.when, false)) clearNotices - deltas = _deltaPruner(this, deltas) } } + deltas = _deltaPruner(this, deltas) } } listenerTransition() @@ -1054,7 +1076,9 @@ trait CometActor extends LiftActor with LiftCometActor with CssBindImplicits { protected def manualWiringDependencyManagement = false private def performReRender(sendAll: Boolean): RenderOut = { - _lastRenderTime = Helpers.nextNum + if (! partialUpdateStream_?) { + _lastRenderTime = Helpers.nextNum + } if (sendAll) { cachedFixedRender.reset @@ -1065,7 +1089,9 @@ trait CometActor extends LiftActor with LiftCometActor with CssBindImplicits { } wasLastFullRender = sendAll & hasOuter - deltas = Nil + if (! partialUpdateStream_?) { + deltas = Nil + } receivedDelta = false if (!dontCacheRendering) { From 5817a950073959059c2b19f0deb23ddaac64739e Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 22 Nov 2014 21:50:34 -0500 Subject: [PATCH 1060/1949] Four out of five dentists recommend these names for understandability. --- web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala | 4 ++-- .../src/main/scala/net/liftweb/http/LiftSession.scala | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index b45f7b8693..36d25ff1cb 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -918,8 +918,8 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { } dataAttributeProcessor.append { - case ("lift", dataLiftContents, element, liftSession) => - dataLiftContents.charSplit('?') match { + case ("lift", snippetInvocation, element, liftSession) => + snippetInvocation.charSplit('?') match { case Nil => // This shouldn't ever happen. NodeSeq.Empty diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index 6dc8c9394a..cd4532600a 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -2185,11 +2185,11 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, private object _lastFoundSnippet extends ThreadGlobal[String] private object DataAttrNode { - val rules = LiftRules.dataAttributeProcessor.toList + val dataAttributeProcessors = LiftRules.dataAttributeProcessor.toList def unapply(in: Node): Option[DataAttributeProcessorAnswer] = { in match { - case element: Elem if ! rules.isEmpty => + case element: Elem if dataAttributeProcessors.nonEmpty => element.attributes.toStream.flatMap { case UnprefixedAttribute(key, value, _) if key.toLowerCase().startsWith("data-") => val dataProcessorName = key.substring(5).toLowerCase() @@ -2201,7 +2201,7 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, NamedPF.applyBox( (dataProcessorName, dataProcessorInputValue, element.copy(attributes = filteredAttributes), LiftSession.this), - rules + dataAttributeProcessors ) case _ => Empty }.headOption From c8eb87287851f65ec9f91c2db7fdc93017997963 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 22 Nov 2014 21:56:23 -0500 Subject: [PATCH 1061/1949] Remove dat element... without a massive match. --- .../src/main/scala/net/liftweb/http/LiftSession.scala | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index cd4532600a..07903341c2 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -2194,13 +2194,10 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, case UnprefixedAttribute(key, value, _) if key.toLowerCase().startsWith("data-") => val dataProcessorName = key.substring(5).toLowerCase() val dataProcessorInputValue = value.text - val filteredAttributes = element.attributes.filter{ - case UnprefixedAttribute(k2, _, _) => k2 != key - case _ => true - } + val filteredElement = removeAttribute(key, element) NamedPF.applyBox( - (dataProcessorName, dataProcessorInputValue, element.copy(attributes = filteredAttributes), LiftSession.this), + (dataProcessorName, dataProcessorInputValue, filteredElement, LiftSession.this), dataAttributeProcessors ) case _ => Empty From 6b7171dd8728f979a4b8b4b0663338391bcf9895 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 22 Nov 2014 22:02:37 -0500 Subject: [PATCH 1062/1949] Make snippy a bit easier to understand w/ rename + case class. Here we add the case class SnippetInformation to describe the return type from snippy, and change snippy's name to snippetInformationForElement. Also, rename some of the internal variables in the method to make it a bit easier to follow. --- .../scala/net/liftweb/http/LiftSession.scala | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index 07903341c2..2473511548 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -3026,8 +3026,10 @@ private object SnippetNode { private def isLiftClass(s: String): Boolean = s.startsWith("lift:") || s.startsWith("l:") - private def snippy(in: Elem): Option[(String, MetaData)] = { - val snippetContents = { + case class SnippetInformation(name: String, attributes: MetaData) + + private def snippetInformationForElement(in: Elem): Option[SnippetInformation] = { + val snippetInvocation = { for { cls <- in.attribute("class") snip <- cls.text.charSplit(' ').find(isLiftClass) @@ -3036,11 +3038,16 @@ private object SnippetNode { } } orElse in.attribute("lift").map(_.text) - snippetContents.map { snip => + snippetInvocation.map { snip => snip.charSplit('?') match { - case Nil => "this should never happen" -> Null - case x :: Nil => urlDecode(removeLift(x)) -> Null - case x :: xs => urlDecode(removeLift(x)) -> pairsToMetaData(xs.flatMap(_.roboSplit("[;&]"))) + case Nil => + SnippetInformation("this should never happen", Null) + + case snippetName :: Nil => + SnippetInformation(urlDecode(removeLift(snippetName)), Null) + + case snippetName :: snippetArguments => + SnippetInformation(urlDecode(removeLift(snippetName)), pairsToMetaData(snippetArguments.flatMap(_.roboSplit("[;&]")))) } } } @@ -3094,7 +3101,7 @@ private object SnippetNode { case elm: Elem => { for { - (snippetName, lift) <- snippy(elm) + SnippetInformation(snippetName, lift) <- snippetInformationForElement(elm) } yield { val (par, nonLift) = liftAttrsAndParallel(elm.attributes) val newElm = new Elem(elm.prefix, elm.label, From 14bf37c4ac3855850bcde89e0a886e3c1790cc39 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 22 Nov 2014 22:17:06 -0500 Subject: [PATCH 1063/1949] Move pairsToMetadata and makeMetaData to HtmlHelpers. Now that I've gotten rid of this duplication I feel better about myself. --- .../scala/net/liftweb/util/HtmlHelpers.scala | 32 +++++++++++++++++++ .../scala/net/liftweb/http/LiftRules.scala | 20 ------------ .../scala/net/liftweb/http/LiftSession.scala | 20 ------------ 3 files changed, 32 insertions(+), 40 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/HtmlHelpers.scala b/core/util/src/main/scala/net/liftweb/util/HtmlHelpers.scala index 61294fab16..c946f826c6 100644 --- a/core/util/src/main/scala/net/liftweb/util/HtmlHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/HtmlHelpers.scala @@ -19,6 +19,8 @@ package util import scala.language.higherKinds +import StringHelpers._ + import scala.xml._ import common._ @@ -321,4 +323,34 @@ trait HtmlHelpers extends CssBindImplicits { case _ => Empty } } + + /** + * Make a MetaData instance from a key and a value. If key contains a colon, this + * method will generate a PrefixedAttribute with the text before the colon used as + * the prefix. Otherwise, it will produce an UnprefixedAttribute. + **/ + def makeMetaData(key: String, value: String, rest: MetaData): MetaData = key.indexOf(":") match { + case x if x > 0 => new PrefixedAttribute(key.substring(0, x), + key.substring(x + 1), + value, rest) + + case _ => new UnprefixedAttribute(key, value, rest) + } + + /** + * Convert a list of strings into a MetaData of attributes. + * + * The strings in the list must be in the format of key=value. + **/ + def pairsToMetaData(in: List[String]): MetaData = in match { + case Nil => Null + case x :: xs => { + val rest = pairsToMetaData(xs) + x.charSplit('=').map(Helpers.urlDecode) match { + case Nil => rest + case x :: Nil => makeMetaData(x, "", rest) + case x :: y :: _ => makeMetaData(x, y, rest) + } + } + } } diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 36d25ff1cb..aae5d32f0d 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -897,26 +897,6 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { */ val dataAttributeProcessor: RulesSeq[DataAttributeProcessor] = new RulesSeq() - private def makeMetaData(key: String, value: String, rest: MetaData): MetaData = key.indexOf(":") match { - case x if x > 0 => new PrefixedAttribute(key.substring(0, x), - key.substring(x + 1), - value, rest) - - case _ => new UnprefixedAttribute(key, value, rest) - } - - private def pairsToMetaData(in: List[String]): MetaData = in match { - case Nil => Null - case x :: xs => { - val rest = pairsToMetaData(xs) - x.charSplit('=').map(Helpers.urlDecode) match { - case Nil => rest - case x :: Nil => makeMetaData(x, "", rest) - case x :: y :: _ => makeMetaData(x, y, rest) - } - } - } - dataAttributeProcessor.append { case ("lift", snippetInvocation, element, liftSession) => snippetInvocation.charSplit('?') match { diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index 2473511548..8b5a48d2df 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -3003,26 +3003,6 @@ private object SnippetNode { case _ => str } - private def makeMetaData(key: String, value: String, rest: MetaData): MetaData = key.indexOf(":") match { - case x if x > 0 => new PrefixedAttribute(key.substring(0, x), - key.substring(x + 1), - value, rest) - - case _ => new UnprefixedAttribute(key, value, rest) - } - - private def pairsToMetaData(in: List[String]): MetaData = in match { - case Nil => Null - case x :: xs => { - val rest = pairsToMetaData(xs) - x.charSplit('=').map(Helpers.urlDecode) match { - case Nil => rest - case x :: Nil => makeMetaData(x, "", rest) - case x :: y :: _ => makeMetaData(x, y, rest) - } - } - } - private def isLiftClass(s: String): Boolean = s.startsWith("lift:") || s.startsWith("l:") From 114611f9f8c95b46bb357d51bf6ec95424908588 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 22 Nov 2014 22:45:22 -0500 Subject: [PATCH 1064/1949] Remove upper bound on email TLD in emailRegexPattern. The suggested solution by Diego was to make the limit 10, but making it unbounded probably makes more sense. It lets people use custom TLDs in local delivery / email testing without having to worry about length. --- .../proto/src/main/scala/net/liftweb/proto/ProtoRules.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/persistence/proto/src/main/scala/net/liftweb/proto/ProtoRules.scala b/persistence/proto/src/main/scala/net/liftweb/proto/ProtoRules.scala index 792bc108c3..adaabfe7ef 100644 --- a/persistence/proto/src/main/scala/net/liftweb/proto/ProtoRules.scala +++ b/persistence/proto/src/main/scala/net/liftweb/proto/ProtoRules.scala @@ -32,7 +32,7 @@ object ProtoRules extends Factory with LazyLoggable { * The regular expression pattern for matching email addresses. This * assumes that the email address has been converted to lower case. */ - val emailRegexPattern = new FactoryMaker(Pattern.compile("^[a-z0-9._%\\-+]+@(?:[a-z0-9\\-]+\\.)+[a-z]{2,4}$")) {} + val emailRegexPattern = new FactoryMaker(Pattern.compile("^[a-z0-9._%\\-+]+@(?:[a-z0-9\\-]+\\.)+[a-z]{2,}$")) {} } From 295ba58151689de55676ea5941b58a85034aa5e0 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 22 Nov 2014 22:50:49 -0500 Subject: [PATCH 1065/1949] Make emailRegexPattern case insensitive. --- .../proto/src/main/scala/net/liftweb/proto/ProtoRules.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/persistence/proto/src/main/scala/net/liftweb/proto/ProtoRules.scala b/persistence/proto/src/main/scala/net/liftweb/proto/ProtoRules.scala index adaabfe7ef..a59d750890 100644 --- a/persistence/proto/src/main/scala/net/liftweb/proto/ProtoRules.scala +++ b/persistence/proto/src/main/scala/net/liftweb/proto/ProtoRules.scala @@ -32,7 +32,7 @@ object ProtoRules extends Factory with LazyLoggable { * The regular expression pattern for matching email addresses. This * assumes that the email address has been converted to lower case. */ - val emailRegexPattern = new FactoryMaker(Pattern.compile("^[a-z0-9._%\\-+]+@(?:[a-z0-9\\-]+\\.)+[a-z]{2,}$")) {} + val emailRegexPattern = new FactoryMaker(Pattern.compile("(?i)^[a-z0-9._%\\-+]+@(?:[a-z0-9\\-]+\\.)+[a-z]{2,}$")) {} } From d0515772da424a8913f74e6309e827569f5b8044 Mon Sep 17 00:00:00 2001 From: Diego Medina Date: Sat, 22 Nov 2014 23:47:19 -0500 Subject: [PATCH 1066/1949] Prepare for 2.6-RC2 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index cea41bdc97..c21ef26040 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ import Dependencies._ organization in ThisBuild := "net.liftweb" -version in ThisBuild := "2.6-SNAPSHOT" +version in ThisBuild := "2.6-RC2" homepage in ThisBuild := Some(url("http://www.liftweb.net")) From 4e03a029e3e4590ffa4f6c5e86e94df074688539 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sun, 23 Nov 2014 17:04:27 -0500 Subject: [PATCH 1067/1949] Update comment for emailRegexPattern. --- .../proto/src/main/scala/net/liftweb/proto/ProtoRules.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/persistence/proto/src/main/scala/net/liftweb/proto/ProtoRules.scala b/persistence/proto/src/main/scala/net/liftweb/proto/ProtoRules.scala index a59d750890..3c495913a8 100644 --- a/persistence/proto/src/main/scala/net/liftweb/proto/ProtoRules.scala +++ b/persistence/proto/src/main/scala/net/liftweb/proto/ProtoRules.scala @@ -29,8 +29,7 @@ import java.util.regex.Pattern */ object ProtoRules extends Factory with LazyLoggable { /** - * The regular expression pattern for matching email addresses. This - * assumes that the email address has been converted to lower case. + * The regular expression pattern for matching email addresses. */ val emailRegexPattern = new FactoryMaker(Pattern.compile("(?i)^[a-z0-9._%\\-+]+@(?:[a-z0-9\\-]+\\.)+[a-z]{2,}$")) {} From dd4f9823ef5956f6b91f803ab3e6690ef2764120 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sun, 23 Nov 2014 17:07:23 -0500 Subject: [PATCH 1068/1949] Change the using the CASE_INSENSITIVE flag in compile. This should make the case insensitivity of the pattern more visible to someone else who is looking at the code. --- .../proto/src/main/scala/net/liftweb/proto/ProtoRules.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/persistence/proto/src/main/scala/net/liftweb/proto/ProtoRules.scala b/persistence/proto/src/main/scala/net/liftweb/proto/ProtoRules.scala index 3c495913a8..d31d8b4dce 100644 --- a/persistence/proto/src/main/scala/net/liftweb/proto/ProtoRules.scala +++ b/persistence/proto/src/main/scala/net/liftweb/proto/ProtoRules.scala @@ -31,7 +31,7 @@ object ProtoRules extends Factory with LazyLoggable { /** * The regular expression pattern for matching email addresses. */ - val emailRegexPattern = new FactoryMaker(Pattern.compile("(?i)^[a-z0-9._%\\-+]+@(?:[a-z0-9\\-]+\\.)+[a-z]{2,}$")) {} + val emailRegexPattern = new FactoryMaker(Pattern.compile("^[a-z0-9._%\\-+]+@(?:[a-z0-9\\-]+\\.)+[a-z]{2,}$", Pattern.CASE_INSENSITIVE)) {} } From ae60c5d658784216f2dd84316ac45b88d25aa29f Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 29 Nov 2014 19:35:27 -0500 Subject: [PATCH 1069/1949] Fix comment style. --- core/util/src/main/scala/net/liftweb/util/HtmlHelpers.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/HtmlHelpers.scala b/core/util/src/main/scala/net/liftweb/util/HtmlHelpers.scala index c946f826c6..9957b8d2c8 100644 --- a/core/util/src/main/scala/net/liftweb/util/HtmlHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/HtmlHelpers.scala @@ -328,7 +328,7 @@ trait HtmlHelpers extends CssBindImplicits { * Make a MetaData instance from a key and a value. If key contains a colon, this * method will generate a PrefixedAttribute with the text before the colon used as * the prefix. Otherwise, it will produce an UnprefixedAttribute. - **/ + */ def makeMetaData(key: String, value: String, rest: MetaData): MetaData = key.indexOf(":") match { case x if x > 0 => new PrefixedAttribute(key.substring(0, x), key.substring(x + 1), @@ -341,7 +341,7 @@ trait HtmlHelpers extends CssBindImplicits { * Convert a list of strings into a MetaData of attributes. * * The strings in the list must be in the format of key=value. - **/ + */ def pairsToMetaData(in: List[String]): MetaData = in match { case Nil => Null case x :: xs => { From a5125bc7e03b7154739863a8d857ceaa2c078256 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 8 Dec 2014 23:28:28 -0500 Subject: [PATCH 1070/1949] Move comet commands into S.jsToAppend. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We do this because jsToAppend is referenced in a few different places, and it’s better to reuse it than to try and add commandForComets to all of those places (especially since some of them are inside CometActors, for example). --- .../scala/net/liftweb/http/LiftServlet.scala | 26 +--------- .../src/main/scala/net/liftweb/http/S.scala | 52 +++++++++++++++---- 2 files changed, 42 insertions(+), 36 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala index c4acc4271d..3a83d66581 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala @@ -577,29 +577,6 @@ class LiftServlet extends Loggable { } } - /** - * Generates the JsCmd needed to initialize comets in - * `S.requestCometVersions` on the client. - */ - private def commandForComets: JsCmd = { - val cometVersions = S.requestCometVersions.is - - if (cometVersions.nonEmpty) { - js.JE.Call( - "lift.registerComets", - js.JE.JsObj( - S.requestCometVersions.is.toList.map { - case CometVersionPair(guid, version) => - (guid, js.JE.Num(version)) - }: _* - ), - true - ).cmd - } else { - js.JsCmds.Noop - } - } - /** * Runs the actual AJAX processing. This includes handling __lift__GC, * or running the parameters in the session. It returns once the AJAX @@ -642,8 +619,7 @@ class LiftServlet extends Loggable { (js :: (xs.collect { case js: JsCmd => js }).reverse) & - S.jsToAppend & - commandForComets + S.jsToAppend ).toResponse } diff --git a/web/webkit/src/main/scala/net/liftweb/http/S.scala b/web/webkit/src/main/scala/net/liftweb/http/S.scala index 325fd3f4b1..36b6d1a89d 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/S.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/S.scala @@ -904,6 +904,32 @@ trait S extends HasParams with Loggable with UserAgentCalculator { */ def appendGlobalJs(js: JsCmd*): Unit = _globalJsToAppend.is ++= js + /** + * Generates the JsCmd needed to initialize comets in + * `S.requestCometVersions` on the client. + */ + private def commandsForComets: List[JsCmd] = { + val cometVersions = requestCometVersions.is + requestCometVersions.set(Set()) + + if (cometVersions.nonEmpty) { + List( + js.JE.Call( + "lift.registerComets", + js.JE.JsObj( + cometVersions.toList.map { + case CometVersionPair(guid, version) => + (guid, js.JE.Num(version)) + }: _* + ), + true + ).cmd + ) + } else { + Nil + } + } + /** * Get the accumulated JavaScript * @@ -911,18 +937,22 @@ trait S extends HasParams with Loggable with UserAgentCalculator { */ def jsToAppend(): List[JsCmd] = { import js.JsCmds._ - _globalJsToAppend.is.toList ::: ( - S.session.map( sess => - sess.postPageJavaScript(RenderVersion.get :: - S.currentCometActor.map(_.uniqueId).toList) - ) match { - case Full(xs) if !xs.isEmpty => List(OnLoad(_jsToAppend.is.toList ::: xs)) - case _ => _jsToAppend.is.toList match { - case Nil => Nil - case xs => List(OnLoad(xs)) - } + + val globalJs = _globalJsToAppend.is.toList + val postPageJs = + S.session.toList.flatMap { session => + session.postPageJavaScript(RenderVersion.get :: + currentCometActor.map(_.uniqueId).toList) } - ) + val cometJs = commandsForComets + + globalJs ::: { + postPageJs ::: cometJs ::: _jsToAppend.is.toList match { + case Nil => Nil + case loadJs => + List(OnLoad(loadJs)) + } + } } /** From 36178466c9a080426bb41621a5c2497e94d312f4 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 8 Dec 2014 23:30:22 -0500 Subject: [PATCH 1071/1949] Don't lose jsToAppend during comet AJAX request. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When dealing with an AJAX request in a comet context, we were partial-updating jsToAppend at the beginning of the request, but any updates that might have been made to it during the request processing were lost. We now send the partial update message after the request functions have run, so that any JS they’ve appended also gets sent down to the client. --- .../src/main/scala/net/liftweb/http/CometActor.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala b/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala index 38f1a698dc..20c9c27219 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala @@ -865,11 +865,6 @@ trait CometActor extends LiftActor with LiftCometActor with CssBindImplicits { case ActionMessageSet(msgs, req) => S.doCometParams(req.params) { - S.jsToAppend() match { - case Nil => - case js => partialUpdate(js) - } - val computed: List[Any] = msgs.flatMap { f => try { @@ -880,6 +875,11 @@ trait CometActor extends LiftActor with LiftCometActor with CssBindImplicits { } } + S.jsToAppend() match { + case Nil => + case js => partialUpdate(js) + } + reply(computed ::: List(S.noticesToJsCmd)) } From 48e1e056cb06dbb337f524f1a1b5df9ce33df0ef Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 8 Dec 2014 23:32:47 -0500 Subject: [PATCH 1072/1949] Avoid duplicate requests for comet-sourced comets. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If a comet was sent down from within a comet context, we would force a comet request reset. On the client, this would schedule a new comet request and attempt to abort the old one; however, there is no way to abort the old one, since its success handler is what is kicking off this request reset. The end result is that the request’s success handler finished running, which would also schedule a new comet request, and we’d get two comet requests running in parallel and walking all over each other. When generating the comet commands, we now check to see if we’re executing in a comet context, and if we are we ask the client not to reset the comet cycle, knowing that the response we’re currently sending will immediately kick off a new request once it’s done executing. --- web/webkit/src/main/scala/net/liftweb/http/S.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/S.scala b/web/webkit/src/main/scala/net/liftweb/http/S.scala index 36b6d1a89d..e1489a7494 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/S.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/S.scala @@ -922,7 +922,9 @@ trait S extends HasParams with Loggable with UserAgentCalculator { (guid, js.JE.Num(version)) }: _* ), - true + // Don't kick off a new comet request client-side if we're responding + // to a comet request right now. + ! currentCometActor.isDefined ).cmd ) } else { From 78ada31a43ffd8114503b6caab544d5853919481 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Tue, 9 Dec 2014 00:01:48 -0500 Subject: [PATCH 1073/1949] Handle comet send-down in partialUpdate. We now pull jsToAppend handling out of the message handler for comet-context AJAX request processing all the way to the wrapper around handling all messages. This ensures that things like partial updates will properly render their JS to append. It also seems to incidentally fix S.jsToAppend within comet rendering, which seemed to trigger the JS twice instead of just once before this. --- .../scala/net/liftweb/http/CometActor.scala | 57 ++++++++++++------- 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala b/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala index 20c9c27219..684ca4abf4 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala @@ -679,6 +679,12 @@ trait CometActor extends LiftActor with LiftCometActor with CssBindImplicits { case e if exceptionHandler.isDefinedAt(e) => exceptionHandler(e) case e: Exception => reportError("Message dispatch for " + in, e) } + + val updatedJs = S.jsToAppend + if (updatedJs.nonEmpty) { + partialUpdate(updatedJs) + } + if (S.functionMap.size > 0) { theSession.updateFunctionMap(S.functionMap, uniqueId, lastRenderTime) @@ -875,11 +881,6 @@ trait CometActor extends LiftActor with LiftCometActor with CssBindImplicits { } } - S.jsToAppend() match { - case Nil => - case js => partialUpdate(js) - } - reply(computed ::: List(S.noticesToJsCmd)) } @@ -1179,15 +1180,16 @@ trait CometActor extends LiftActor with LiftCometActor with CssBindImplicits { * is helpful if you use Lift's CSS Selector Transforms to define * rendering. */ - protected implicit def nsToNsFuncToRenderOut(f: NodeSeq => NodeSeq) = - new RenderOut((Box !! defaultHtml).map(f), internalFixedRender, if (autoIncludeJsonCode) Full(jsonToIncludeInCode & S.jsToAppend()) - else { - S.jsToAppend match { - case Nil => Empty - case x :: Nil => Full(x) - case xs => Full(xs.reduceLeft(_ & _)) + protected implicit def nsToNsFuncToRenderOut(f: NodeSeq => NodeSeq) = { + val additionalJs = + if (autoIncludeJsonCode) { + Full(jsonToIncludeInCode) + } else { + Empty } - }, Empty, false) + + new RenderOut((Box !! defaultHtml).map(f), internalFixedRender, additionalJs, Empty, false) + } /** * Convert a Seq[Node] (the superclass of NodeSeq) to a RenderOut. @@ -1196,16 +1198,27 @@ trait CometActor extends LiftActor with LiftCometActor with CssBindImplicits { * (in Java) will convert a NodeSeq to a RenderOut. This * is helpful if you return a NodeSeq from your render method. */ - protected implicit def arrayToRenderOut(in: Seq[Node]): RenderOut = new RenderOut(Full(in: NodeSeq), internalFixedRender, if (autoIncludeJsonCode) Full(jsonToIncludeInCode & S.jsToAppend()) - else { - S.jsToAppend match { - case Nil => Empty - case x :: Nil => Full(x) - case xs => Full(xs.reduceLeft(_ & _)) - } - }, Empty, false) + protected implicit def arrayToRenderOut(in: Seq[Node]): RenderOut = { + val additionalJs = + if (autoIncludeJsonCode) { + Full(jsonToIncludeInCode) + } else { + Empty + } + + new RenderOut(Full(in: NodeSeq), internalFixedRender, additionalJs, Empty, false) + } - protected implicit def jsToXmlOrJsCmd(in: JsCmd): RenderOut = new RenderOut(Empty, internalFixedRender, if (autoIncludeJsonCode) Full(in & jsonToIncludeInCode & S.jsToAppend()) else Full(in & S.jsToAppend()), Empty, false) + protected implicit def jsToXmlOrJsCmd(in: JsCmd): RenderOut = { + val additionalJs = + if (autoIncludeJsonCode) { + Full(in & jsonToIncludeInCode) + } else { + Full(in) + } + + new RenderOut(Empty, internalFixedRender, additionalJs, Empty, false) + } implicit def pairToPair(in: (String, Any)): (String, NodeSeq) = (in._1, Text(in._2 match { case null => "null" From f6d5ebc3e6b9a86aac914611185d6322e1f5dae0 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Tue, 9 Dec 2014 23:59:14 -0500 Subject: [PATCH 1074/1949] Map function on ajaxOnSubmit invocation. Before, we bound the function when we processed the element. Because this occurred in NodeSeq=>NodeSeq function, we had no guarantee that it would run in the correct formGroup, oneShot, callOnce, etc contexts. To deal with the form group part of this, we were capturing the form group and setting it when we went to actually run the binding function. However, this left oneShot, callOnce, and potential future contexts like them out in the dark. We now run the fmapFunc call as soon as ajaxOnSubmit is called, and the NodeSeq=>NodeSeq function is used solely to bind the resulting id to the matching element(s). This means the function mapping happens in the calling context, and we avoid all of the above issues. There is a subtle shift in behavior: multiple matching elements will get the same function id, where before they would get different ones. Realistically, this shouldn't cause a big problem, since they'll be invoking the same function anyway. --- .../src/main/scala/net/liftweb/http/SHtml.scala | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala b/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala index 6d68adde18..2387293d49 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala @@ -1585,9 +1585,14 @@ trait SHtml { *
        "type=submit" #> ajaxOnSubmit(() => Alert("Done!"))
        */ def ajaxOnSubmit(func: () => JsCmd): (NodeSeq)=>NodeSeq = { - val fgSnap = S._formGroup.get + val functionId = + _formGroup.is match { + case Empty => + formGroup(1)(fmapFunc(func)(id => id)) + case _ => fmapFunc(func)(id => id) + } - (in: NodeSeq) => S._formGroup.doWith(fgSnap) { + (in: NodeSeq) => { def runNodes(ns: NodeSeq): NodeSeq = { def addAttributes(elem: Elem, name: String) = { val clickJs = "lift.setUriSuffix('" + name + "=_'); return true;" @@ -1600,11 +1605,7 @@ trait SHtml { case e: Elem if (e.label == "button") || (e.label == "input" && e.attribute("type").map(_.text) == Some("submit")) => - _formGroup.is match { - case Empty => - formGroup(1)(fmapFunc(func)(addAttributes(e, _))) - case _ => fmapFunc(func)(addAttributes(e, _)) - } + addAttributes(e, functionId) } } From f18e34025de93a44837529b7e99a41d97eb8417c Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 10 Dec 2014 23:49:08 -0500 Subject: [PATCH 1075/1949] Distinguish LiftSession uniqueId from underlyingId. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit What used to be uniqueId is now called underlyingId, to indicate the fact that it is tied to the underlying host’s session id. A new uniqueId is introduced, which is secure and unique, and is not tied to the host id and cannot be used to look the session up in the future. The only place that is currently continuing to use uniqueId is when we emit the id into the page as data-lift-session-id for cache-busting purposes. The underlying id used to be emitted into the page, which triggered some small security concerns, so we switch to this model to be on the safe side. --- .../scala/net/liftweb/http/LiftServlet.scala | 4 ++-- .../scala/net/liftweb/http/LiftSession.scala | 23 +++++++++++++------ .../provider/servlet/HTTPServletSession.scala | 2 +- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala index c4acc4271d..355eb02ca5 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala @@ -653,7 +653,7 @@ class LiftServlet extends Loggable { case _ => JsCommands(S.noticesToJsCmd :: JsCmds.Noop :: S.jsToAppend).toResponse } - LiftRules.cometLogger.debug("AJAX Response: " + liftSession.uniqueId + " " + ret) + LiftRules.cometLogger.debug("AJAX Response: " + liftSession.underlyingId + " " + ret) Full(ret) } finally { @@ -684,7 +684,7 @@ class LiftServlet extends Loggable { private def handleAjax(liftSession: LiftSession, requestState: Req): Box[LiftResponse] = { extractVersions(requestState.path.partPath) { versionInfo => - LiftRules.cometLogger.debug("AJAX Request: " + liftSession.uniqueId + " " + requestState.params) + LiftRules.cometLogger.debug("AJAX Request: " + liftSession.underlyingId + " " + requestState.params) tryo { LiftSession.onBeginServicing.foreach(_(liftSession, requestState)) } diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index ff4f93c0bd..62744c751c 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -310,7 +310,7 @@ object SessionMaster extends LiftActor with Loggable { private def lockAndBump(f: => Box[SessionInfo]): Box[LiftSession] = this.synchronized { f.map { s => - nsessions.put(s.session.uniqueId, SessionInfo(s.session, s.userAgent, s.ipAddress, s.requestCnt + 1, millis)) + nsessions.put(s.session.underlyingId, SessionInfo(s.session, s.userAgent, s.ipAddress, s.requestCnt + 1, millis)) s.session } @@ -372,7 +372,7 @@ object SessionMaster extends LiftActor with Loggable { val ses = lockRead(nsessions) (Box !! ses.get(sessionId)).foreach { case SessionInfo(s, _, _, _, _) => - killedSessions.put(s.uniqueId, Helpers.millis) + killedSessions.put(s.underlyingId, Helpers.millis) s.markedForShutDown_? = true Schedule.schedule(() => { try { @@ -412,7 +412,7 @@ object SessionMaster extends LiftActor with Loggable { f(ses, shutDown => { if (!shutDown.session.markedForShutDown_?) { shutDown.session.markedForShutDown_? = true - this.sendMsg(RemoveSession(shutDown.session.uniqueId)) + this.sendMsg(RemoveSession(shutDown.session.underlyingId)) } }) } else { @@ -424,7 +424,7 @@ object SessionMaster extends LiftActor with Loggable { this ! RemoveSession(shutDown. session. - uniqueId) + underlyingId) } } ), 0.seconds) @@ -595,13 +595,22 @@ private[http] class BooleanThreadGlobal extends ThreadGlobal[Boolean] { /** * The LiftSession class containg the session state information */ -class LiftSession(private[http] val _contextPath: String, val uniqueId: String, +class LiftSession(private[http] val _contextPath: String, val underlyingId: String, val httpSession: Box[HTTPSession]) extends LiftMerge with Loggable with HowStateful { def sessionHtmlProperties = LiftRules.htmlProperties.session.is.make openOr LiftRules.htmlProperties.default.is.vend val requestHtmlProperties: TransientRequestVar[HtmlProperties] = new TransientRequestVar[HtmlProperties](sessionHtmlProperties(S.request openOr Req.nil)) {} + /** + * The unique id of this session. Can be used to securely and uniquely + * identify the session, unlike underlyingId which is tied to the host + * session id. This id should be secure to emit into a page as needed (for + * example, it is used to provide randomness/cache-busting to the comet + * request path). + */ + val uniqueId: String = nextFuncName + @volatile private[http] var markedForTermination = false @@ -897,7 +906,7 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, * Destroy this session and the underlying container session. */ def destroySession() { - SessionMaster ! RemoveSession(this.uniqueId) + SessionMaster ! RemoveSession(this.underlyingId) S.request.foreach(_.request.session.terminate) this.doShutDown() @@ -1126,7 +1135,7 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String, _running_? = false - SessionMaster.sendMsg(RemoveSession(this.uniqueId)) + SessionMaster.sendMsg(RemoveSession(this.underlyingId)) import scala.collection.JavaConversions._ nasyncComponents.foreach { diff --git a/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPServletSession.scala b/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPServletSession.scala index 3be06a3cf8..2981442d0e 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPServletSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPServletSession.scala @@ -28,7 +28,7 @@ class HTTPServletSession(session: HttpSession) extends HTTPSession { def sessionId: String = session.getId - def link(liftSession: LiftSession) = session.setAttribute(LiftMagicID, SessionToServletBridge(liftSession.uniqueId)) + def link(liftSession: LiftSession) = session.setAttribute(LiftMagicID, SessionToServletBridge(liftSession.underlyingId)) def unlink(liftSession: LiftSession) = session.removeAttribute(LiftMagicID) From 0f0fb2e69f3be99a8cca9c7399383f1c151ee920 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 14 Dec 2014 20:15:10 -0500 Subject: [PATCH 1076/1949] Add ability to link to other Scaladocs in ours. We do two things: turn on autoAPIMappings, which automatically lets us link to Scaladocs for modules that set their public URIs, and explicitly add the mapping for the scala-xml module. The addition of the scala-xml module is also reasonably generic, so we can add more module-specific URIs fairly easily in the future as needed. --- project/Build.scala | 44 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index 1d4f98b360..17afdcfd3d 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -19,9 +19,35 @@ import Keys._ import net.liftweb.sbt.LiftBuildPlugin._ import Dependencies._ +/** + * Pattern-matches an attributed file, extracting its module organization, + * name, and revision if available in its attributes. + */ +object MatchingModule { + def unapply(file: Attributed[File]): Option[(String,String,String)] = { + file.get(moduleID.key).map { moduleInfo => + (moduleInfo.organization, moduleInfo.name, moduleInfo.revision) + } + } +} object BuildDef extends Build { + /** + * A helper that returns the revision and JAR file for a given dependency. + * Useful when trying to attach API doc URI information. + */ + def findManagedDependency(classpath: Seq[Attributed[File]], + organization: String, + name: String): Option[(String,File)] = { + classpath.collectFirst { + case entry @ MatchingModule(moduleOrganization, moduleName, revision) + if moduleOrganization == organization && + moduleName.startsWith(name) => + (revision, entry.data) + } + } + lazy val liftProjects = core ++ web ++ persistence lazy val framework = @@ -179,6 +205,20 @@ object BuildDef extends Build { liftProject(id = if (module.startsWith(prefix)) module else prefix + module, base = file(base) / module.stripPrefix(prefix)) - def liftProject(id: String, base: File): Project = - Project(id, base).settings(liftBuildSettings: _*).settings(scalacOptions ++= List("-feature", "-language:implicitConversions")) + def liftProject(id: String, base: File): Project = { + Project(id, base) + .settings(liftBuildSettings: _*) + .settings(scalacOptions ++= List("-feature", "-language:implicitConversions")) + .settings( + autoAPIMappings := true, + apiMappings ++= { + val cp: Seq[Attributed[File]] = (fullClasspath in Compile).value + + findManagedDependency(cp, "org.scala-lang.modules", "scala-xml").map { + case (revision, file) => + (file -> url("http://www.scala-lang.org/api/" + version)) + }.toMap + } + ) + } } From c307b50f3b35551392f72a777ae10ee837f6d343 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 14 Dec 2014 20:22:00 -0500 Subject: [PATCH 1077/1949] Clean up/improve lift-common Scaladocs. Some places get more detailed Scaladocs, others get some facelifts and get converted to wiki syntax instead of HTML. Examples added throughout. --- .../main/scala/net/liftweb/common/Box.scala | 838 +++++++++++------- .../net/liftweb/common/CombinableBox.scala | 15 +- .../net/liftweb/common/Conversions.scala | 58 +- .../net/liftweb/common/FuncJBridge.scala | 6 +- .../main/scala/net/liftweb/common/HList.scala | 91 +- .../main/scala/net/liftweb/common/LRU.scala | 60 +- .../net/liftweb/common/LoanWrapper.scala | 30 +- .../scala/net/liftweb/common/Logging.scala | 134 ++- .../net/liftweb/common/SimpleActor.scala | 46 +- .../scala/net/liftweb/common/SimpleList.scala | 51 +- 10 files changed, 859 insertions(+), 470 deletions(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index 2cd805c553..d85c565999 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -24,7 +24,12 @@ import scala.reflect.Manifest import java.util.{Iterator => JavaIterator, ArrayList => JavaArrayList} /** - * The bridge from Java to Scala Box + * A bridge to make using Lift `[[Box]]` from Java easier. + * + * In particular, provides access to the `Box` companion object so that + * functions like `[[Box$.legacyNullTest legacyNullTest]]` can be used easily + * from Java, as well as access to the `[[Empty]]` singleton so that empty + * values can be created easily from Java. */ class BoxJBridge { /** @@ -33,25 +38,25 @@ class BoxJBridge { def box: BoxTrait = Box /** - * Get the None singleton + * Get the `[[Empty]]` singleton. */ def empty: EmptyBox = Empty } /** * The Box companion object provides methods to create a Box from: - *
          - *
        • an Option
        • - *
        • a List
        • - *
        • any AnyRef object
        • - *
        + * - an `[[scala.Option Option]]` + * - a `[[scala.collection.immutable.List List]]` + * - any `[[scala.AnyRef AnyRef]]` object, converting `null` to `[[Empty]]` and + * anything else to a `[[Full]]` with the given object * - * It also provides implicit methods to transform Option to Box, Box to Iterable, and Box to Option + * It also provides implicit methods to transform `Option` to `Box`, `Box` to + * `[[scala.collection.Iterable Iterable]]`, and `Box` to `Option`. */ object Box extends BoxTrait { /** - * Helper class to provide an easy way for converting Lists of Boxes[T] into - * a Box of List[T]. + * Helper class to provide an easy way for converting a `List[Box[T]]` into + * a `Box[List[T]]`. **/ implicit class ListOfBoxes[T](val theListOfBoxes: List[Box[T]]) extends AnyVal { /** @@ -87,17 +92,10 @@ object Box extends BoxTrait { } /** - * The Box companion object provides methods to create a Box from: - *
          - *
        • an Option
        • - *
        • a List
        • - *
        • any AnyRef object
        • - *
        - * - * It also provides implicit methods to transform Option to Box, Box to Iterable, and Box to Option + * Implementation for the `[[Box$]]` singleton. */ sealed trait BoxTrait { - val primativeMap: Map[Class[_], Class[_]] = Map( + val primitiveMap: Map[Class[_], Class[_]] = Map( java.lang.Boolean.TYPE -> classOf[java.lang.Boolean], java.lang.Character.TYPE -> classOf[java.lang.Character], java.lang.Byte.TYPE -> classOf[java.lang.Byte], @@ -107,9 +105,14 @@ sealed trait BoxTrait { java.lang.Long.TYPE -> classOf[java.lang.Long], java.lang.Short.TYPE -> classOf[java.lang.Short]) + @deprecated("Use the correctly-spelled primitiveMap instead.","3.0") + val primativeMap = primitiveMap + /** - * Create a Box from the specified Option. - * @return a Box created from an Option. Full(x) if the Option is Some(x) and Empty otherwise + * Create a `Box` from the specified `Option`. + * + * @return `Full` with the contents if the `Option` is `Some` + * and `Empty` otherwise. */ def apply[T](in: Option[T]) = in match { case Some(x) => Full(x) @@ -117,9 +120,9 @@ sealed trait BoxTrait { } /** - * Create a Box from the specified Box, checking for null. - * @return Full(x) if in is Full(x) and x is not null - * Empty otherwise + * Create a `Box` from the specified `Box`, checking for `null`. + * + * @return `Full(in)` if `in` is non-null, `Empty` otherwise. */ def apply[T](in: Box[T]) = in match { case Full(x) => legacyNullTest(x) @@ -128,8 +131,12 @@ sealed trait BoxTrait { } /** - * Transform a List with zero or one elements to a Box. - * @return a Box object containing the head of a List. Full(x) if the List contains at least one element and Empty otherwise. + * Transform a `List` with zero or one elements to a `Box`. + * + * Note that any elements past the head of the list are lost! + * + * @return `Full(x)` with the head of the list if it contains at least one + * element and `Empty` otherwise. */ def apply[T](in: List[T]) = in match { case x :: _ => Full(x) @@ -137,47 +144,65 @@ sealed trait BoxTrait { } /** - * Apply the specified PartialFunction to the specified value and return the result - * in a Full Box; if the pf is undefined at that point return Empty. - * @param pf the partial function to use to transform the value - * @param value the value to transform - * @return a Full box containing the transformed value if pf.isDefinedAt(value); Empty otherwise + * Apply the specified `PartialFunction` to the specified `value` and return the result + * in a `Full`; if the `pf`` is not defined at that point return `Empty`. + * + * @param pf The partial function to use to transform the value. + * @param value The value to transform. + * + * @return A `Full` containing the transformed value if + * `pf.isDefinedAt(value)` and `Empty` otherwise. */ def apply[InType, OutType](pf: PartialFunction[InType, OutType])(value: InType): Box[OutType] = if (pf.isDefinedAt(value)) Full(pf(value)) else Empty /** - * Apply the specified PartialFunction to the specified value and return the result - * in a Full Box; if the pf is undefined at that point return Empty. - * @param pf the partial function to use to transform the value - * @param value the value to transform - * @return a Full box containing the transformed value if pf.isDefinedAt(value); Empty otherwise + * Apply the specified `PartialFunction` to the specified `value` and return + * the result in a `Full`; if the `pf`` is not defined at that point return + * `Empty`. + * + * @param pf The partial function to use to transform the value. + * @param value The value to transform. + * @return A `Full` containing the transformed value if + * `pf.isDefinedAt(value)` and `Empty` otherwise. */ def apply[InType, OutType](value: InType)(pf: PartialFunction[InType, OutType]): Box[OutType] = if (pf.isDefinedAt(value)) Full(pf(value)) else Empty /** - * This implicit transformation allows one to use a Box as an Iterable - * @return List(in) if this Box is Full(in); Nil otherwise + * This implicit transformation allows one to use a `Box` as an `Iterable` of + * zero or one elements. + * + * @return A single-element `List` with the contents if the box is `Full` + * and `[[scala.collection.immutable.Nil Nil]]` otherwise. */ implicit def box2Iterable[T](in: Box[T]): Iterable[T] = in.toList /** - * This implicit transformation allows one to use an Option as a Box. - * @return a Box object from an Option. Full(in) if the Option is Some(in); Empty otherwise + * This implicit transformation allows one to use an `Option` as a `Box`. + * + * @return `Full` with the contents if the `Option` is `Some` and `Empty` + * otherwise. */ implicit def option2Box[T](in: Option[T]): Box[T] = Box(in) /** - * This implicit transformation allows one to use a Box as an Option. - * @return Some(in) if this Box is Full(in); None otherwise + * This implicit transformation allows one to use a `Box` as an `Option`. + * + * Note that `Box` implements `get` specifically to avoid usage of `.get` on + * `Box` instances. Boxes should be opened using `openOrThrowException` and + * their contents compared using `== Full(expectedValue)`. + * + * @return `Some` with the contents if the box is `Full` and `[[scala.None None]]` + * otherwise. */ implicit def box2Option[T](in: Box[T]): Option[T] = in.toOption /** - * This method allows one to encapsulate any object in a Box in a null-safe manner, - * treating null values to Empty - * @return Full(in) if in is not null; Empty otherwise + * This method allows one to encapsulate any object in a Box in a null-safe + * manner, converting `null` values to `Empty`. + * + * @return `Full` if `in` is not null and `Empty` otherwise. */ def legacyNullTest[T](in: T): Box[T] = in match { case null => Empty @@ -185,19 +210,29 @@ sealed trait BoxTrait { } /** - * Alias for legacyNullTest. - * This method allows one to encapsulate any object in a Box in a null-safe manner, - * returning Empty if the specified value is null. - * @return Full(in) if in is not null Empty otherwise + * Alias for `[[legacyNullTest]]`. */ def !![T](in: T): Box[T] = legacyNullTest(in) /** - * Create a Full box containing the specified value if "in" is an instance - * of the specified class, or Empty otherwise. + * Create a `Full` box containing the specified value if `in` is an instance of + * the specified class `clz` and `Empty` otherwise. + * + * This is basically a Java-friendly version of `[[asA]]`, which you should + * prefer when using Scala. + * + * For example: + * {{{ + * scala> Box.isA("boom", classOf[Int]) + * res0: net.liftweb.common.Box[Int] = Empty + * + * scala> Box.isA(5, classOf[Int]) + * res1: net.liftweb.common.Box[Int] = Full(5) + * }}} */ - def isA[A, B](in: A, clz: Class[B]): Box[B] = - (Box !! in).isA(clz) + def isA[A, B](in: A, clz: Class[B]): Box[B] = { + (Box !! in).isA(clz) + } // NOTE: We use an existential type here so that you can invoke asA with // just one type parameter. To wit, this lets you do: @@ -210,108 +245,213 @@ sealed trait BoxTrait { // // Uglier, and generally not as nice. /** - * Create a Full box containing the specified value if in is of - * type B; Empty otherwise. + * Create a `Full` box containing the specified value if `in` is of type + * `B` and `Empty` otherwise. + * + * For example: + * {{{ + * scala> Box.asA[Int]("boom") + * res0: net.liftweb.common.Box[Int] = Empty + * + * scala> Box.asA[Int](5) + * res1: net.liftweb.common.Box[Int] = Full(5) + * }}} */ - def asA[B](in: T forSome { type T })(implicit m: Manifest[B]): Box[B] = - (Box !! in).asA[B] + def asA[B](in: T forSome { type T })(implicit m: Manifest[B]): Box[B] = { + (Box !! in).asA[B] + } } /** - * The Box class is a container which is able to declare if it is Full (containing a single non-null value) or EmptyBox. An EmptyBox, or empty, can be the Empty singleton, Failure or ParamFailure. - * Failure and ParamFailure contain information about why the Box is empty including - * exception information, chained Failures and a String. - * It serves a similar purpose to the Option class from Scala standard library but adds several features: - *
          - *
        • you can transform it to a Failure object if it is Empty (with the ?~ method)
        • - *
        • you can chain failure messages on Failure Boxes
        • - *
        • you "run" a function on your Box, with a default value: Full(1).run("zero") { (x: String, y: Int) => y.toString }
        • - *
        • you can "pass" a Box to a function for side effects: Full(1) $ { x: Box[Int] => println(x openOr 0) }
        • - *
        + * Used as a return type for certain methods that should not be called. One + * example is the `get` method on a Lift `Box`. It exists to prevent client + * code from using `.get` as an easy way to open a `Box`, so it needs a return + * type that will match no valid client return types. + */ +final class DoNotCallThisMethod + +/** + * The `Box` class is a container which is able to declare if it is `Full` + * (containing a single non-null value) or `EmptyBox`. An EmptyBox, or empty, + * can be the `Empty` singleton, `Failure` or `ParamFailure`. `Failure` and + * `ParamFailure` contain information about why the `Box` is empty including + * exception information, possibly chained `Failures` and a `String` message. + * + * This serves a similar purpose to the `[[scala.Option Option]]` class from + * Scala standard library but adds several features: + * - You can transform it to a `Failure` object if it is `Empty` (with the + * `[[?~]]` or `[[failMsg]]` method). + * - You can chain failure messages on `Failure`s (with the `?~!` or + * `[[compoundFailMsg]]` method). + * - You "run" a function on a `Box`, with a default to return if the box is + * `Empty`: + * {{{ + * val littleTeddyBears: Box[Int] = Full(10) + * littleTeddyBears.run("and then there were none") { (default: String, teddyBears: Int) => + * s"\$teddyBears little teddy bears" + * } // => 10 little teddy bears + * + * val updatedTeddyBears: Box[Int] = Empty + * littleTeddyBears.run("and then there were none") { (default: String, teddyBears: Int) => + * s"\$teddyBears little teddy bears" + * } // => and then there were none + * }}} + * - You can "pass" a `Box` to a function for side effects: + * {{{ + * val littleTeddyBears: Box[Int] = Full(10) + * + * doSomething( + * littleTeddyBears $ { teddyBears: Box[Int] => + * println("Are there any?") + * println(teddyBears openOr 0) + * } + * ) // doSomething gets a Box[Int] as well + * }}} + * + * If you grew up on Java, you're used to `Exceptions` as part of your program + * logic. The Scala philosophy and the Lift philosophy is that exceptions are + * for exceptional conditions such as failure of an external resource (e.g., + * your database goes offline) rather than simply indicating that a parameter + * wasn't supplied or couldn't be parsed. + * + * Lift's `Box` and Scala's `Option` provide mechanisms for being explicit + * about a value existing or not existing rather than relying on a reference + * being not-null. However, extracting a value from a `Box` should be done + * correctly. Available options are: + * - Using a `for` comprehension, especially for multiple boxes: + * {{{ + * val loggedInUser: Box[User] = + * for { + * username <- possibleUsername + * password <- possiblePassword + * user <- User.find("username" -> username) + * if User.checkPassword(password, user.password) + * } yield { + * user + * } + * }}} + * - Using `map`, `flatMap`, `filter`, and `foreach` (`for` comprehensions + * use these under the covers): + * {{{ + * val fullName: Box[String] = + * loggedInUser.map { user => + * user.name + " (" + user.nickname + ")" + * } + * val bestFriend: Box[User] = + * loggedInUser.flatMap { user => + * UserFriends.find(user.bestFriend.id) + * } + * val allowedUser: Box[User] = + * loggedInUser.filter(_.canAccess_?(currentPage)) + * + * fullName.foreach { name => + * logger.info(s"User \$name is in the building.") + * } + * }}} + * - Using pattern-matching (a good way to deal with `Failure`s): + * {{{ + * val loginMessage: String = + * loggedInUser match { + * case Full(user) => + * "Login successful!" + * case Failure(message, _, _) => + * s"Login failed: \$message" + * case Empty => + * s"Unknown failure logging in." + * } + * }}} + * - For comparisons (e.g., in tests), use `==` and `===`: + * {{{ + * loggedInUser must_== Full(mockUser) + * (loggedInUser === mockUser) must beTrue + * }}} */ sealed abstract class Box[+A] extends Product with Serializable{ self => /** - * Returns true if this Box contains no value (is Empty or Failure or ParamFailure) - * @return true if this Box contains no value + * Returns `true` if this `Box` contains no value (i.e., it is `Empty` or + * `Failure` or `ParamFailure`). */ def isEmpty: Boolean /** * Returns true if the box contains a value. - * @return true if this Box contains a value */ def isDefined: Boolean = !isEmpty /** - * If you grew up on Java, you're used to Exceptions as part of your program logic. - * The Scala philosophy and the Lift philosophy is that exceptions are for exceptional - * conditions such as failure of an external resource (e.g., your database goes offline) - * rather than simply indicating that a parameter wasn't supplied or couldn't be parsed. - * - * Lift's Box and Scala's Option provide a mechanism for being explicit about a value - * existing or not existing rather than relying on a reference being not-null. However, - * extracting a value from a Box should be done correctly. Correctly can be (in order of use - * in David Pollak's code): a for comprehension; using map, flatMap or foreach; or using pattern matching. + * The only time when you should be using this method is if the value is + * guaranteed to be available based on a guard outside of the method. In these + * cases, please provide that information in the justification `String`. + * For example, User.currentUser.openOrThrowException("This snippet is only + * used on pages where the user is logged in"). For tests, use `[[==]]` or + * `[[===]]` instead. See the class documentation for more information. * - * The only times when you should be using this method are: the value is guaranteed to be available based - * on a guard outside of the method using the Box or in tests. For example, - * User.currentUser.openOrThrowException("This snippet is used on pages where the user is logged in") + * A valid justification for using this method should not be "I want my code + * to fail fast when I call it." Using exceptions in the core logic of your + * application should be strongly discouraged. * - * A valid justification for using this method should not be "I want my code to fail fast when I call it." - * Using exceptions in the core logic of your application should be strongly discouraged. + * @param justification Justify why calling this method is okay and why it + * will not result in an exception being thrown. This serves both as + * mandatory documentation and as a very clear indication of what + * unexpected thing happened in the event you were wrong about the + * guard. * - * This method replaces open_! because people used open_! and generally ignored the reason for the "!", - * so we're making it more explicit that this method should not commonly be used and should be justified - * when used. - * - * @param justification Justify why calling this method is okay and why it will not result in an Exception - * - * @return The contents of the Box if it has one or an exception if not + * @return The contents of the `Box` if it is `Full`. + * @throws NullPointerException If you attempt to call it on an `EmptyBox`, + * with a message that includes the provided `justification`. */ def openOrThrowException(justification: String): A /** - * Exists to avoid the implicit conversion from Box to Option. Opening a Box - * unsafely should be done using openOrThrowException. + * Exists to avoid the implicit conversion from `Box` to `Option`. Opening a + * `Box` unsafely should be done using `openOrThrowException`. + * + * This method *always* throws an exception. */ - final def get: Nothing = { + final def get: DoNotCallThisMethod = { throw new Exception("Attempted to open a Box incorrectly. Please use openOrThrowException.") } /** - * Return the value contained in this Box if it is full; otherwise return the specified default - * @return the value contained in this Box if it is full; otherwise return the specified default + * Return the value contained in this `Box` if it is full; otherwise return + * the specified default. Equivalent to `Option`'s `[[scala.Option.getOrElse getOrElse]]`. */ def openOr[B >: A](default: => B): B = default /** - * Apply a function to the value contained in this Box if it exists and return - * a new Box containing the result, or empty otherwise. - * @return the modified Box or empty + * Apply a function to the value contained in this `Box` if it exists and return + * a `Full` containing the result. If this `Box` is not already `Full`, return + * the unchanged box. + * + * @note This means that using `map` with a `Failure` will preserve the + * `Failure.` */ def map[B](f: A => B): Box[B] = Empty /** - * Apply a function returning a Box to the value contained in this Box if it exists - * and return the result, or empty otherwise. - * @return the modified Box or empty + * Apply a function returning a `Box` to the value contained in this `Box` if + * it exists and return the resulting `Box`. If this `Box` is not already + * `Full`, return the unchanged box. + * + * @note This means that using `map` with a `Failure` will preserve the + * `Failure.` */ def flatMap[B](f: A => Box[B]): Box[B] = Empty /** - * Return this Box if it contains a value satisfying the specified predicate; Empty otherwise - * @return this Box if it contains a value satisfying the specified predicate; Empty otherwise + * If this `Box` contains a value and it satisfies the specified `predicate`, + * return the `Box` unchanged. Otherwise, return an `Empty`. */ def filter(p: A => Boolean): Box[A] = this /** - * Makes Box play better with Scala 2.8 for comprehensions + * Makes `Box` play better with Scala `for` comprehensions. */ def withFilter(p: A => Boolean): WithFilter = new WithFilter(p) /** - * Play NiceLike with the Scala 2.8 for comprehension + * Makes `Box` play better with Scala `for` comprehensions. */ class WithFilter(p: A => Boolean) { def map[B](f: A => B): Box[B] = self.filter(p).map(f) @@ -322,55 +462,81 @@ sealed abstract class Box[+A] extends Product with Serializable{ } /** - * Determine whether this Box contains a value which satisfies the specified predicate + * If this `Box` contains a value and it satisfies the specified `predicate`, + * return `true`. Otherwise, return `false`. + * * @return true if this Box does contain a value and it satisfies the predicate */ def exists(func: A => Boolean): Boolean = false /** - * Determine whether all Box values satisfy the predicate - * @return true if the Box is empty, or if Box's value satisfies the predicate + * If this `Box` contains a value and it does not satisfy the specified + * `predicate`, return `false`. Otherwise, return `true`. + * + * @return true If the `Box` is empty, or if its value satisfies the + * predicate. */ def forall(func: A => Boolean): Boolean = true /** - * Creates a Box if the current Box is Full and the value does not satisfy the predicate, f. - * - * @param f the predicate used to test value. - * - * @return a Box + * + * If this `Box` contains a value and it does *not* satisfy the specified + * `predicate`, return the `Box` unchanged. Otherwise, return an `Empty`. */ def filterNot(f: A => Boolean): Box[A] = filter(a => !f(a)) /** - * Perform a side effect by calling the specified function - * with the value contained in this box. + * Perform a side effect by calling the specified function with the value + * contained in this box. The function does not run if this `Box` is empty. */ def foreach[U](f: A => U): Unit = {} /** - * Return a Full[B] if the contents of this Box is an instance of the specified class, - * otherwise return Empty + * Create a `Full` box containing the specified value if `in` is an instance of + * the specified class `clz` and `Empty` otherwise. + * + * This is basically a Java-friendly version of `[[asA]]`, which you should + * prefer when using Scala. + * + * For example: + * {{{ + * scala> Full("boom").isA(classOf[Int]) + * res0: net.liftweb.common.Box[Int] = Empty + * + * scala> Full(5).isA(classOf[Int]) + * res1: net.liftweb.common.Box[Int] = Full(5) + * }}} */ def isA[B](cls: Class[B]): Box[B] = Empty /** - * Return a Full[B] if the contents of this Box is of type B, otherwise return Empty + * Create a `Full` box containing the specified value if `in` is of type + * `B` and `Empty` otherwise. + * + * For example: + * {{{ + * scala> Full("boom").asA[Int] + * res0: net.liftweb.common.Box[Int] = Empty + * + * scala> Full(5).asA[Int] + * res1: net.liftweb.common.Box[Int] = Full(5) + * }}} */ def asA[B](implicit m: Manifest[B]): Box[B] = Empty /** - * Return this Box if Full, or the specified alternative if this is empty + * Return this Box if `Full`, or the specified alternative if it is empty. */ def or[B >: A](alternative: => Box[B]): Box[B] = alternative /** - * Returns an Iterator over the value contained in this Box + * Returns an `[[scala.collection.Iterator Iterator]]` over the value + * contained in this `Box`, if any. */ def elements: Iterator[A] = Iterator.empty /** - * Get a Java Iterator from the Box + * Get a `java.util.Iterator` from the Box. */ def javaIterator[B >: A]: JavaIterator[B] = { val ar = new JavaArrayList[B]() @@ -379,96 +545,136 @@ sealed abstract class Box[+A] extends Product with Serializable{ } /** - * Returns an Iterator over the value contained in this Box + * Returns an `[[scala.collection.Iterator Iterator]]` over the value + * contained in this `Box`, if any. + * + * Synonym for `[[elements]]`. */ def iterator: Iterator[A] = this.elements /** - * Returns a List of one element if this is Full, or an empty list if empty. + * Returns a `[[scala.collection.immutable.List List]]` of one element if this + * is Full, or an empty list if empty. */ def toList: List[A] = Nil /** - * Returns the contents of this box in an Option if this is Full, or - * None if this is a empty (Empty, Failure or ParamFailure) + * Returns the contents of this box wrapped in `Some` if this is Full, or + * `None` if this is a empty (meaning an `Empty`, `Failure` or + * `ParamFailure`). */ def toOption: Option[A] = None /** - * Transform an Empty to a Failure with the specified message. - * @param msg the failure message - * @return a Failure with the message if this Box is Empty + * Transform an Empty to a Failure with the specified message. Otherwise + * leaves returns the same box. + * + * @note This means a `Failure` will also remain unchanged; see `?~!` to + * change these. + * + * @return A Failure with the message if this `Box` is `Empty`, this box + * otherwise. */ def ?~(msg: => String): Box[A] = this - - /** - * Transform an Empty to a ParamFailure with the specified typesafe - * parameter. - * @param errorCode a value indicating the error - * @return a ParamFailure with the specified value + * Transform an `Empty` or `Failure` to a `ParamFailure` with the specified + * type-safe parameter. + * + * @param errorCode A value indicating the error. + * @return A `ParamFailure` with the specified value, unless this is already a + * `ParamFailure` or a `Full`. If this is a `Failure`, the + * `ParamFailure` will preserve the message of the `Failure`. */ def ~>[T](errorCode: => T): Box[A] = this /** - * Alias for ?~ + * Alias for `[[?~]]`. */ def failMsg(msg: => String): Box[A] = ?~(msg) /** - * Transform an EmptyBox to a Failure with the specified message and chain - * the new Failure to any previous Failure represented by this Box. - * @param msg the failure message - * @return a Failure with the message if this Box is an Empty Box. Chain the messages if it is already a Failure + * Chain the given `msg` as a `Failure` ahead of any failures this `Box` may + * represent. + * + * If this is an `Empty`, this method behaves like `[[?~]]`. If it is a `Failure`, + * however, this method returns a new `Failure` with the given `msg` and with its + * `[[Failure.chain chain]]` set to this `Failure`. + * + * As with `[[?~]]`, if this is a `Full`, we return it unchanged. + * + * @return A Failure with the message if this Box is an Empty Box. Chain this + * box to the new `Failure` if this is a `Failure`. The unchanged box + * if it is a `Full`. */ def ?~!(msg: => String): Box[A] = ?~(msg) /** - * Alias for ?~! + * Alias for `?~!`. */ def compoundFailMsg(msg: => String): Box[A] = ?~!(msg) /** - * Filter this box on the specified predicate, returning a Failure with the specified - * message if the predicate is not satisfied. - * @param msg the failure message - * @param p a predicate - * @return a Failure with the message if the predicate is not satisfied by the value contained in this Box + * If this `Box` contains a value and it satisfies the specified `predicate`, + * return the `Box` unchanged. Otherwise, return a `Failure` with the given + * `msg`. + * + * @see [[filter]] + * + * @return A `Failure` with the message if the box is empty or the predicate + * is not satisfied by the value contained in this Box. */ def filterMsg(msg: String)(p: A => Boolean): Box[A] = filter(p) ?~ msg /** - * This method calls the specified function with the value contained in this Box - * @return the result of the function or a default value + * This method calls the specified function with the specified `in` value and + * the value contained in this `Box`. If this box is empty, returns the `in` + * value directly. + * + * @return The result of the function or the `in` value. */ def run[T](in: => T)(f: (T, A) => T) = in /** - * Perform a side effect by passing this Box to the specified function - * and return this Box unmodified. - * @return this Box + * Perform a side effect by passing this Box to the specified function and + * return this Box unmodified. Similar to `foreach`, except that `foreach` + * returns `Unit`, while this method allows chained use of the `Box`. + * + * @return This box. */ def pass(f: Box[A] => Unit): Box[A] = {f(this) ; this} /** - * Alias for pass + * Alias for `[[pass]]`. */ def $(f: Box[A] => Unit): Box[A] = pass(f) /** - * Determines equality based upon the contents of this Box instead of the box itself. - * As a result, it is not symmetric. Which means that for + * For `Full` and `Empty`, this has the expected behavior. Equality in terms + * of Failure checks for equivalence of failure causes: + * {{{ + * Failure("boom") == Failure("boom") + * Failure("bam") != Failure("boom") + * Failure("boom", Full(someException), Empty) != Failure("boom") + * }}} * - *
        -   *     val foo = "foo"
        -   *     val boxedFoo = Full(foo)
        -   *     foo == boxedFoo //is false
        -   *     boxedFoo == foo //is true
        -   * 
        + * For other values, determines equality based upon the contents of this `Box` + * instead of the box itself. As a result, it is not symmetric. As an example: + * {{{ + * val foo = "foo" + * val boxedFoo = Full(foo) + * foo == boxedFoo //is false + * boxedFoo == foo //is true + * }}} * - * For Full and Empty, this has the expected behavior. Equality in terms of Failure - * checks for equivalence of failure causes. + * It is safest to use `===` explicitly when you're looking for this behavior, + * and use `==` only for box-to-box comparisons: + * {{{ + * Full("magic") == Full("magic") + * Full("magic") != Full("another") + * Full("magic") != Empty + * Full("magic") != Failure("something's gone wrong") + * }}} */ override def equals(other: Any): Boolean = (this, other) match { case (Full(x), Full(y)) => x == y @@ -478,8 +684,7 @@ sealed abstract class Box[+A] extends Product with Serializable{ } /** - * Apply the function f1 to the contents of this Box if available; if this - * is empty return the specified alternative. + * Equivalent to `flatMap(f1).or(alternative)`. */ def choice[B](f1: A => Box[B])(alternative: => Box[B]): Box[B] = this match { case Full(x) => f1(x) @@ -487,46 +692,59 @@ sealed abstract class Box[+A] extends Product with Serializable{ } /** - * Returns true if the value contained in this box is equal to the specified value. + * Returns true if the value contained in this box is equal to the specified + * value. This is the same thing that `==` does when it's handed a value that + * isn't a `Box`, but using this is recommended because it's clearer that the + * behavior will be different than the usual expectation. */ def ===[B >: A](to: B): Boolean = false /** - * Equivalent to map(f).openOr(Full(dflt)) + * Equivalent to `map(f).openOr(Full(dflt))`. */ def dmap[B](dflt: => B)(f: A => B): B = dflt /** - * If the Box is Full, apply the transform function on the - * value, otherwise just return the value untransformed + * If the `Box` is `Full`, apply the transform function `f` on the value `v`; + * otherwise, just return the value untransformed. + * + * The transform function is expected to be a function that will take the + * value `v` and produce a function from the value in the box to a new value + * of the same type as `v`. * - * @param v the value - * @param f the transformation function - * @tparam T the type of the value - * @return the value or the transformed value is the Box is Full + * For example: + * {{{ + * val myBox = Full(10) + * myBox.fullXForm("No teddy bears left.")({ message => + * { teddyBears: Int => + * s"\$message Oh wait, there are \$teddyBears left!" + * } + * }) + * }}} + * + * @tparam T The type of the initial value, default value, and transformed + * value. + * @return If the `Box` is `Full`, the value once transformed by the function + * returned by `f`. Otherwise, the initial value `v`. */ def fullXform[T](v: T)(f: T => A => T): T = v /** - * An Either that is a Left with the given argument - * left if this is empty, or a Right if this - * Full with the Box's value. + * An `Either` that is a `Left` with the given argument `left` if this is + * empty, or a `Right` with the boxed value if this is `Full`. */ def toRight[B](left: => B): Either[B, A] = Left(left) /** - * An Either that is a Right with the given - * argument - * right if this is empty, or a Left if this is - * Fill with the Box's value + * An `Either` that is a `Right` with the given argument `right` if this is + * empty, or a `Left` with the boxed value if this is `Full`. */ def toLeft[B](right: => B): Either[A, B] = Right(right) - /** - * If the partial function is defined at the current Box's value - * apply the partial function. + * If the partial function is defined at the current Box's value, apply the + * partial function. */ final def collect[B](pf: PartialFunction[A, B]): Box[B] = { flatMap(value => @@ -534,46 +752,25 @@ sealed abstract class Box[+A] extends Product with Serializable{ else Empty) } + /** + * An alias for `collect`. + * + * Although this function is different for true collections, because `Box` is + * really a collection of 1, the two functions are identical. + */ + final def collectFirst[B](pf: PartialFunction[A, B]): Box[B] = { + collect(pf) + } } /** - * Full is a Box containing a value. + * `Full` is a `[[Box]]` that contains a value. */ -final case class Full[+A](value: A) extends Box[A]{ - +final case class Full[+A](value: A) extends Box[A] { def isEmpty: Boolean = false - - - /** - * If you grew up on Java, you're used to Exceptions as part of your program logic. - * The Scala philosophy and the Lift philosophy is that exceptions are for exceptional - * conditions such as failure of an external resource (e.g., your database goes offline) - * rather than simply indicating that a parameter wasn't supplied or couldn't be parsed. - * - * Lift's Box and Scala's Option provide a mechanism for being explicit about a value - * existing or not existing rather than relying on a reference being not-null. However, - * extracting a value from a Box should be done correctly. Correctly can be (in order of use - * in David Pollak's code): a for comprehension; using map, flatMap or foreach; or using pattern matching. - * - * The only times when you should be using this method are: the value is guaranteed to be available based - * on a guard outside of the method using the Box or in tests. For example, - * User.currentUser.openOrThrowException("This snippet is used on pages where the user is logged in") - * - * A valid justification for using this method should not be "I want my code to fail fast when I call it." - * Using exceptions in the core logic of your application should be strongly discouraged. - * - * This method replaces open_! because people used open_! and generally ignored the reason for the "!", - * so we're making it more explicit that this method should not commonly be used and should be justified - * when used. - * - * @param justification Justify why calling this method is okay and why it will not result in an Exception - * - * @return The contents of the Box if it has one or an exception if not - */ def openOrThrowException(justification: String): A = value - override def openOr[B >: A](default: => B): B = value override def or[B >: A](alternative: => Box[B]): Box[B] = this @@ -598,37 +795,17 @@ final case class Full[+A](value: A) extends Box[A]{ override def run[T](in: => T)(f: (T, A) => T) = f(in, value) - /** - * If the Box is Full, apply the transform function on the - * value, otherwise just return the value untransformed - * - * @param v the value - * @param f the transformation function - * @tparam T the type of the value - * @return the value or the transformed value is the Box is Full - */ override def fullXform[T](v: T)(f: T => A => T): T = f(v)(value) - /** - * An Either that is a Left with the given argument - * left if this is empty, or a Right if this - * Full with the Box's value. - */ override def toRight[B](left: => B): Either[B, A] = Right(value) - /** - * An Either that is a Right with the given - * argument - * right if this is empty, or a Left if this is - * Fill with the Box's value - */ override def toLeft[B](right: => B): Either[A, B] = Left(value) override def isA[B](clsOrg: Class[B]): Box[B] = value match { case value: AnyRef => - val cls = Box.primativeMap.get(clsOrg) match { + val cls = Box.primitiveMap.get(clsOrg) match { case Some(c) => c case _ => clsOrg } @@ -646,49 +823,22 @@ final case class Full[+A](value: A) extends Box[A]{ } /** - * Singleton object representing an Empty Box + * Singleton object representing a completely empty `Box` with no value or + * failure information. */ case object Empty extends EmptyBox /** - * The EmptyBox is a Box containing no value. + * An `EmptyBox` is a `Box` containing no value. It can sometimes carry + * additional failure information, as in `[[Failure]]` and `[[ParamFailure]]`. */ sealed abstract class EmptyBox extends Box[Nothing] with Serializable { def isEmpty: Boolean = true - - /** - * If you grew up on Java, you're used to Exceptions as part of your program logic. - * The Scala philosophy and the Lift philosophy is that exceptions are for exceptional - * conditions such as failure of an external resource (e.g., your database goes offline) - * rather than simply indicating that a parameter wasn't supplied or couldn't be parsed. - * - * Lift's Box and Scala's Option provide a mechanism for being explicit about a value - * existing or not existing rather than relying on a reference being not-null. However, - * extracting a value from a Box should be done correctly. Correctly can be (in order of use - * in David Pollak's code): a for comprehension; using map, flatMap or foreach; or using pattern matching. - * - * The only times when you should be using this method are: the value is guaranteed to be available based - * on a guard outside of the method using the Box or in tests. For example, - * User.currentUser.openOrThrowException("This snippet is used on pages where the user is logged in") - * - * A valid justification for using this method should not be "I want my code to fail fast when I call it." - * Using exceptions in the core logic of your application should be strongly discouraged. - * - * This method replaces open_! because people used open_! and generally ignored the reason for the "!", - * so we're making it more explicit that this method should not commonly be used and should be justified - * when used. - * - * @param justification Justify why calling this method is okay and why it will not result in an Exception - * - * @return The contents of the Box if it has one or an exception if not - */ def openOrThrowException(justification: String) = throw new NullPointerException("An Empty Box was opened. The justification for allowing the openOrThrowException was "+justification) - - override def openOr[B >: Nothing](default: => B): B = default override def or[B >: Nothing](alternative: => Box[B]): Box[B] = alternative @@ -699,51 +849,26 @@ sealed abstract class EmptyBox extends Box[Nothing] with Serializable { override def ?~!(msg: => String): Failure = Failure(msg, Empty, Empty) - override def ~>[T](errorCode: => T): ParamFailure[T] = - ParamFailure("", Empty, Empty, errorCode) + override def ~>[T](errorCode: => T): ParamFailure[T] = ParamFailure("", Empty, Empty, errorCode) } /** - * Companion object used to simplify the creation of a simple Failure. + * Companion object used to simplify the creation of a simple `Failure` with + * just a message. */ object Failure { def apply(msg: String) = new Failure(msg, Empty, Empty) } /** - * A Failure is an EmptyBox with an additional failure message explaining the reason for its being empty. - * It can also optionally provide an exception or a chain of causes represented as a list of other Failure objects + * A `Failure` is an `[[EmptyBox]]` with an additional failure message + * explaining the reason for its being empty. It can also optionally provide an + * exception and/or a chain of previous `Failure`s that may have caused this + * one. */ -sealed case class Failure(msg: String, exception: Box[Throwable], chain: Box[Failure]) extends EmptyBox{ +sealed case class Failure(msg: String, exception: Box[Throwable], chain: Box[Failure]) extends EmptyBox { type A = Nothing - - /** - * If you grew up on Java, you're used to Exceptions as part of your program logic. - * The Scala philosophy and the Lift philosophy is that exceptions are for exceptional - * conditions such as failure of an external resource (e.g., your database goes offline) - * rather than simply indicating that a parameter wasn't supplied or couldn't be parsed. - * - * Lift's Box and Scala's Option provide a mechanism for being explicit about a value - * existing or not existing rather than relying on a reference being not-null. However, - * extracting a value from a Box should be done correctly. Correctly can be (in order of use - * in David Pollak's code): a for comprehension; using map, flatMap or foreach; or using pattern matching. - * - * The only times when you should be using this method are: the value is guaranteed to be available based - * on a guard outside of the method using the Box or in tests. For example, - * User.currentUser.openOrThrowException("This snippet is used on pages where the user is logged in") - * - * A valid justification for using this method should not be "I want my code to fail fast when I call it." - * Using exceptions in the core logic of your application should be strongly discouraged. - * - * This method replaces open_! because people used open_! and generally ignored the reason for the "!", - * so we're making it more explicit that this method should not commonly be used and should be justified - * when used. - * - * @param justification Justify why calling this method is okay and why it will not result in an Exception - * - * @return The contents of the Box if it has one or an exception if not - */ override def openOrThrowException(justification: String) = throw new NullPointerException("An Failure Box was opened. Failure Message: "+msg+ ". The justification for allowing the openOrThrowException was "+justification) { @@ -764,8 +889,13 @@ sealed case class Failure(msg: String, exception: Box[Throwable], chain: Box[Fai } /** - * Get the exception chain along with the exception chain of any - * chained failures + * Return a list of the exceptions that led to this `Failure`. First, unflattens + * the list of causes of this `Failure`'s `exception`. Then, if this `Failure` + * has a `chain`, walks down it and concatenates their `exceptionChain` to the + * end of this one's. + * + * @return A single list of `Throwable`s from the most direct cause to the + * least direct cause of this `Failure`. */ def exceptionChain: List[Throwable] = { import scala.collection.mutable.ListBuffer @@ -782,19 +912,34 @@ sealed case class Failure(msg: String, exception: Box[Throwable], chain: Box[Fai } /** - * Gets the deepest exception cause + * Gets the deepest exception cause, if any, which is ostensibly the root + * cause of this `Failure`. */ def rootExceptionCause: Box[Throwable] = { exceptionChain.lastOption } /** - * Flatten the Failure chain to a List where this - * Failure is at the head + * Flatten the `Failure` chain to a List where this Failure is at the head. */ def failureChain: List[Failure] = this :: chain.toList.flatMap(_.failureChain) + /** + * Reduce this `Failure`'s message and the messages of all chained failures a + * to a single `String`. The resulting string links each step in the failure + * chain with <-, and this `Failure`'s message is last. + * + * For example: + * {{{ + * scala> Failure("It's all gone wrong.") ?~! "Something's gone wrong." ?~! "It's all sideways" + * res0: net.liftweb.common.Failure = Failure(It's all sideways,Empty, + * Full(Failure(Something's gone wrong.,Empty, + * Full(Failure(It's all gone wrong.,Empty,Empty))))) + * scala> res0.messageChain + * res1: String = It's all sideways <- Something's gone wrong. <- It's all gone wrong. + * }}} + */ def messageChain: String = (this :: chainList).map(_.msg).mkString(" <- ") override def equals(other: Any): Boolean = (this, other) match { @@ -811,8 +956,48 @@ sealed case class Failure(msg: String, exception: Box[Throwable], chain: Box[Fai } /** - * A ParamFailure is a Failure with an additional typesafe parameter that can - * allow an application to store other information related to the failure. + * Companion object used to simplify the creation of simple `ParamFailure`s, as + * well as allow pattern-matching on the `ParamFailure`. + */ +object ParamFailure { + def apply[T](msg: String, exception: Box[Throwable], chain: Box[Failure], param: T) = + new ParamFailure(msg, exception, chain, param) + + def apply[T](msg: String, param: T) = new ParamFailure(msg, Empty, Empty, param) + + def unapply(in: Box[_]): Option[(String, Box[Throwable], Box[Failure], Any)] = in match { + case pf: ParamFailure[_] => Some((pf.msg, pf.exception, pf.chain, pf.param)) + case _ => None + } +} + +/** + * A `ParamFailure` is a `Failure` with an additional type-safe parameter that + * can allow an application to store other information related to the failure. + * + * For example: + * {{{ + * val loggedInUser = + * for { + * username ?~ "Missing username" ~> "error.missingUser" + * password ?~! "Missing password" ~> "error.missingPassword" + * user <- User.find("username" -> username) + * if User.checkPassword(password, user.password) + * } yield { + * user + * } + * + * loggedInUser match { + * case ParamFailure(message, _, _, i18nKey: String) => + * tellUser(i18n(i18nKey)) + * case Failure(message, _, _) => + * tellUser(failureMessage) + * case Empty => + * tellUser("Unknown login failure.") + * case _ => + * tellUser("You're in!") + * } + * }}} */ final class ParamFailure[T](override val msg: String, override val exception: Box[Throwable], @@ -834,71 +1019,48 @@ final class ParamFailure[T](override val msg: String, } /** - * A trait that a class can mix into itself to convert itself into a Box + * A trait that a class can mix into itself to indicate that it can convert + * itself into a `Box`. */ trait Boxable[T] { def asBox: Box[T] } -object ParamFailure { - def apply[T](msg: String, exception: Box[Throwable], chain: Box[Failure], param: T) = - new ParamFailure(msg, exception, chain, param) - - def apply[T](msg: String, param: T) = new ParamFailure(msg, Empty, Empty, param) - - def unapply(in: Box[_]): Option[(String, Box[Throwable], Box[Failure], Any)] = in match { - case pf: ParamFailure[_] => Some((pf.msg, pf.exception, pf.chain, pf.param)) - case _ => None - } -} - /** - * Sometimes it's convenient to access either a Box[T] - * or a T. If you specify BoxOrRaw[T], the - * either a T or a Box[T] can be passed and the "right thing" - * will happen + * Sometimes it's convenient to access either a Box[T] or a T. If you specify + * BoxOrRaw[T], the either a T or a Box[T] can be passed and the "right thing" + * will happen, including nulls being treated as `Empty`. */ sealed trait BoxOrRaw[T] { def box: Box[T] } /** - * The companion object that has helpful conversions + * Companion object with implicit conversions to allow `BoxOrRaw[T]` to + * masquerade as the appropriate types. */ object BoxOrRaw { - /** - * Convert a T to a BoxOrRaw[T] - */ implicit def rawToBoxOrRaw[T, Q <: T](r: Q): BoxOrRaw[T] = RawBoxOrRaw(r: T) - /** - * Convert a Box[T] to a BoxOrRaw[T] - */ implicit def boxToBoxOrRaw[T, Q <% T](r: Box[Q]): BoxOrRaw[T] = { BoxedBoxOrRaw(r.map(v => v: T)) } - /** - * Convert an Option[T] to a BoxOrRaw[T] - */ implicit def optionToBoxOrRaw[T, Q <% T](r: Option[Q]): BoxOrRaw[T] = { BoxedBoxOrRaw(r.map(v => v: T)) } - /** - * Convert a BoxOrRaw[T] to a Box[T] - */ implicit def borToBox[T](in: BoxOrRaw[T]): Box[T] = in.box } /** - * The Boxed up BoxOrRaw + * The BoxOrRaw that represents a boxed value. */ final case class BoxedBoxOrRaw[T](box: Box[T]) extends BoxOrRaw[T] /** - * The raw version of BoxOrRaw + * The BoxOrRaw that represents a raw value. */ final case class RawBoxOrRaw[T](raw: T) extends BoxOrRaw[T] { def box: Box[T] = diff --git a/core/common/src/main/scala/net/liftweb/common/CombinableBox.scala b/core/common/src/main/scala/net/liftweb/common/CombinableBox.scala index a763b2d0fb..38e80c4a6b 100644 --- a/core/common/src/main/scala/net/liftweb/common/CombinableBox.scala +++ b/core/common/src/main/scala/net/liftweb/common/CombinableBox.scala @@ -20,12 +20,8 @@ package common import scala.language.implicitConversions /** - *

        - * Via an HList containing a Collection of Box[things], either generate an - * HList of the things or a List[Failure] - *

        - * - * + * Via an `[[HLists.HList HList]]` containing a collection of `[[Box]]`, either generates an + * `HList` of the things (unboxed) or a `List[Failure]`. */ object CombinableBox { import HLists._ @@ -59,14 +55,13 @@ object CombinableBox { } /** - * If the Failure is going to be condensed, generate a FailureList + * If the `[[Failure]]` is going to be condensed, a `FailureList` is generated + * to allow type-safe pattern matches without worrying about erasure. */ final case class FailureList(failures: List[Failure]) - - /** - * The place where the results are accumulated + * The place where the results are accumulated. */ final case class CombinableBox[B, C <: HList](rhs: Result[B :+: C]) { def :&: [A](lhs: Boxable[A]): Result[A :+: B :+: C] = this.:&:(lhs.asBox) diff --git a/core/common/src/main/scala/net/liftweb/common/Conversions.scala b/core/common/src/main/scala/net/liftweb/common/Conversions.scala index 2046d4bb48..f6a308dbbd 100644 --- a/core/common/src/main/scala/net/liftweb/common/Conversions.scala +++ b/core/common/src/main/scala/net/liftweb/common/Conversions.scala @@ -26,17 +26,26 @@ import scala.xml.NodeSeq */ /** - * A helpful trait that will accept either a String or a NodeSeq via - * an implicit conversion. So, all you need to do is put in a String or - * a NodeSeq and the right thing will happen. + * This trait is used to unify `String`s and `[[scala.xml.NodeSeq NodeSeq]]`s + * into one type. It is used in conjuction with the implicit conversions defined + * in its companion object. */ sealed trait StringOrNodeSeq { def nodeSeq: scala.xml.NodeSeq } /** - * The companion object that has helpful - * implicit conversions from String and NodeSeq + * Provides implicit conversions to the `StringOrNodeSeq` trait, which can in + * turn be implicitly converted to `[[scala.xml.NodeSeq NodeSeq]]`. This allows + * using a `String` as a natural part of `NodeSeq` APIs without having to + * explicitly wrap it in `scala.xml.Text` or having to write overloads for all + * methods that should accept both. + * + * This is used in certain Lift APIs, for example, to accept either a `String` + * or more complex content. For example, a `button` can have either a simple + * label or complex HTML content. HTML APIs that can do this can accept a + * parameter of type `StringOrNodeSeq` to allow the user to pass either in as + * their needs dictate. */ object StringOrNodeSeq { import scala.xml._ @@ -50,7 +59,8 @@ object StringOrNodeSeq { } /** - * Convert a NodeSeq (well, a Seq[Node]) to a StringOrNodeSeq + * This is written in terms of a `Seq[Node]` to make sure Scala converts + * everything it should to a `StringOrNodeSeq`. `NodeSeq` is a `Seq[Node]`.` */ implicit def nsTo(ns: Seq[Node]): StringOrNodeSeq = new StringOrNodeSeq { @@ -64,18 +74,24 @@ object StringOrNodeSeq { } /** - * Sometimes you want a function that returns a String as a parameter, - * but many times, you'll just want to pass a String constant. In - * those cases, this trait and it's implicit conversions come in really - * handy. Basically, a String constant or a String function can be passed and - * either will be implicitly converted into a StringFunc. + * This trait is used to unify `()=>String` and `String` into one type. It is + * used in conjunction with the implicit conversions defined in its companion + * object. */ sealed trait StringFunc { def func: () => String } /** - * The companion object to StringFunc with helpful implicit conversions + * Provides implicit conversions to the `StringFunc` trait. This allows using a + * `String` as a natural part of APIs that want to allow the flexibility of a + * `()=>String` without having to write overloads for all methods that should + * accept both. + * + * Lift's Menu API, for example, allows CSS classes to be defined either as + * a `String` or a `()=>String`. The latter could use the current request and + * session state to do more interesting things than a hard-coded `String` would, + * while the former is simpler to use. */ object StringFunc { /** @@ -106,18 +122,20 @@ final case class ConstStringFunc(str: String) extends StringFunc { } /** - * Sometimes you want a function that returns a NodeSeq as a parameter, - * but many times, you'll just want to pass a NodeSeq constant. In - * those cases, this trait and it's implicit conversions come in really - * handy. Basically, a NodeSeq constant or a NodeSeq function can be passed and - * either will be implicitly converted into a NodeSeqFunc. + * This trait is used to unify `()=>[[scala.xml.NodeSeq NodeSeq]]` and + * `[[scala.xml.NodeSeq NodeSeq]]` into one type. It is used in conjunction + * with the implicit conversions defined in its [[NodeSeqFunc$ companion + * object]]. */ sealed trait NodeSeqFunc { def func: () => NodeSeq } /** - * The companion object to NodeSeqFunc with helpful implicit conversions + * Provides implicit conversions to the `NodeSeqFunc` trait. This allows using a + * `[[scala.xml.NodeSeq NodeSeq]]` as a natural part of APIs that want to allow + * the flexibility of a `()=>[[scala.xml.NodeSeq NodeSeq]]` without having to + * write overloads for all methods that should accept both. */ object NodeSeqFunc { /** @@ -136,12 +154,12 @@ object NodeSeqFunc { } /** - * The case class that holds a NodeSeq function. + * The case class that holds a `[[scala.xml.NodeSeq NodeSeq]]` function. */ final case class RealNodeSeqFunc(func: () => NodeSeq) extends NodeSeqFunc /** - * The case class that holds the NodeSeq constant. + * The case class that holds the `[[scala.xml.NodeSeq NodeSeq]]` constant. */ final case class ConstNodeSeqFunc(ns: NodeSeq) extends NodeSeqFunc { lazy val func = () => ns diff --git a/core/common/src/main/scala/net/liftweb/common/FuncJBridge.scala b/core/common/src/main/scala/net/liftweb/common/FuncJBridge.scala index 23790371ef..ba9d21354c 100644 --- a/core/common/src/main/scala/net/liftweb/common/FuncJBridge.scala +++ b/core/common/src/main/scala/net/liftweb/common/FuncJBridge.scala @@ -22,7 +22,11 @@ import scala.language.implicitConversions object FuncJBridge extends FuncJBridge /** - * Bridge from Java functions to Scala functions + * Bridges from Java functions to Scala functions. + * + * The implicits defined here allow Scala code to interact seamlessly between + * the Java function-like interfaces and the Scala function interfaces for + * various function arities. */ class FuncJBridge { /** diff --git a/core/common/src/main/scala/net/liftweb/common/HList.scala b/core/common/src/main/scala/net/liftweb/common/HList.scala index 15825c67c9..a199577c4d 100644 --- a/core/common/src/main/scala/net/liftweb/common/HList.scala +++ b/core/common/src/main/scala/net/liftweb/common/HList.scala @@ -18,13 +18,56 @@ package net.liftweb package common /** - * Support for heterogenious lists, aka HLists + * Basic support for heterogeneous lists, aka + * [[http://apocalisp.wordpress.com/2010/07/06/type-level-programming-in-scala-part-6a-heterogeneous-list%C2%A0basics/ HLists]]. * + * An `HList` can be constructed like so: + * + * {{{ + * import net.liftweb.common.HLists._ + * + * trait Base + * case class Type1(value: String) extends Base + * case class Type2(otherValue: String) extends Base + * + * val myHList = Type1("Value") :+: Type2("Other Value") :+: HNil + * myHList match { + * case firstThing :+: secondThing :+: HNil => + * println(firstThing.value) + * println(secondThing.otherValue) + * } + * }}} + * + * Above, we see that the `HList` preserved the value of the types of its + * members, otherwise we wouldn't have been able to fetch `value` and + * `otherValue`, respectively. + * + * Trying the same thing with a list won't work: + * + * {{{ + * val myList = Type1("Value") :: Type2("Other Value") :: Nil + * myList match { + * case firstThing :: secondThing :: Nil => + * // error: value value is not a member of Product with Serializable with Base + * println(firstThing.value) + * } + * }}} + * + * This is because `value` is not defined in `Base`. The inferred type of the + * `List` has to be a common ancestor class or trait of `Type1` and `Type2`, and + * no such type has a `value` method. */ object HLists { /** - * The trait that defines HLists + * The base trait for `HList`s. Functions that take `HList`s will need a type + * parameter subtype of `HList`: + * + * {{{ + * def myHListFunction[T <: HList](list: HList) = { + * println(s"This HList has \${list.length} items!") + * } + * }}} */ sealed trait HList { type Head @@ -37,14 +80,22 @@ object HLists { } /** - * The last element of an HList + * The last element of an `HList`. This is the starting point for an `HList`, + * and carries a `:+:` method to start one: + * + * {{{ + * scala> Type1("Value") :+: HNil + * res0: net.liftweb.common.HLists.HCons[Type1,net.liftweb.common.HLists.HNil] = Type1(Value) :+: HNil + * }}} */ final class HNil extends HList { type Head = Nothing type Tail = HNil /** - * Create a new HList based on this node + * Chains the given value to the front of an `HList`. + * + * Produces a `T :+: HNil`. */ def :+:[T](v: T) = HCons(v, this) @@ -62,7 +113,23 @@ object HLists { val HNil = new HNil() /** - * The HList cons cell + * The `HList` cons cell, which represents one part of an `HList` in linked + * list style. + * + * Carries the information about the type of this element, plus the `HList` + * type of the rest of the list. + * + * You can use `:+:` to make this `HList` longer: + * + * {{{ + * scala> val first = Type1("Value") :+: HNil + * first: net.liftweb.common.HLists.HCons[Type1,net.liftweb.common.HLists.HNil] = Type1(Value) :+: HNil + * scala> Type2("Other Value") :+: first + * res0: net.liftweb.common.HLists.HCons[Type2, + * net.liftweb.common.HLists.HCons[Type1, + * net.liftweb.common.HLists.HNil]] = + * Type2(Other Value) :+: Type1(Value) :+: HNil + * }}} */ final case class HCons[H, T <: HList](head: H, tail: T) extends HList { type This = HCons[H, T] @@ -70,7 +137,7 @@ object HLists { type Tail = T /** - * Create a new HList based on this node + * Chains the given value to the front of this `HList`. */ def :+:[T](v: T) = HCons(v, this) @@ -84,21 +151,25 @@ object HLists { type :+:[H, T <: HList] = HCons[H, T] + /** + * Provides the support needed to be able to pattern-match an `HList`. + */ object :+: { def unapply[H, T <: HList](in: HCons[H, T]): Option[(H, T)] = Some(in.head, in.tail) } } -// Some useful type system stuff from Miles Sabin /** - * Encoding for "A is not a subtype of B" + * Encoding for "A is not a subtype of B". */ sealed trait ExcludeThisType[A, B] /** - * The companion object to <:!<. This allows one of specify - * that a type is not a subtype of another type + * The companion object to `ExcludeThisType`. This allows one of specify that a + * type is not a subtype of another type. + * + * Based on work by Miles Sabin. */ object ExcludeThisType { def unexpected: Nothing = sys.error("Unexpected invocation") diff --git a/core/common/src/main/scala/net/liftweb/common/LRU.scala b/core/common/src/main/scala/net/liftweb/common/LRU.scala index b4da7bedde..06bcd64a2a 100644 --- a/core/common/src/main/scala/net/liftweb/common/LRU.scala +++ b/core/common/src/main/scala/net/liftweb/common/LRU.scala @@ -47,7 +47,18 @@ private[common] trait LinkedListElem[T1, T2] { /** - * Implements an LRU Hashmap + * Implements an LRU Hashmap. Given a size, this map will evict the least + * recently used item(s) when new items are added. + * + * Note that `LRUMap` is *not* thread-safe. + * + * @param initmaxSize The initial max size. This can be updated using + * `[[updateMaxSize]]`. + * @param loadFactor If non-`Empty`, specifies the load factor for the + * backing `java.util.HashMap`. + * @param expiredFunc When a key-value pair is removed, the last thing that + * happens is that these functions are invoked. Note that this happens + * after `[[expired]]` is invoked. */ class LRUMap[K, V](initMaxSize: Int, loadFactor: Box[Float], expiredFunc: ((K, V) => Unit)*) extends LinkedListElem[K, V] { import java.util.HashMap @@ -58,6 +69,10 @@ class LRUMap[K, V](initMaxSize: Int, loadFactor: Box[Float], expiredFunc: ((K, V def maxSize = _maxSize + /** + * Updates the `LRUMap`'s current max size to `newMaxSize`, evicting the + * oldest entries if the size has shrunk. + */ def updateMaxSize(newMaxSize: Int) { val oldMaxSize = _maxSize _maxSize = newMaxSize @@ -75,6 +90,13 @@ class LRUMap[K, V](initMaxSize: Int, loadFactor: Box[Float], expiredFunc: ((K, V private[this] val localMap = new HashMap[K, LinkedListElem[K, V]](maxSize / 4, loadFactor openOr 0.75f) + /** + * Fetches the given key, returning `[[Empty]]` if the key does not exist in + * the map. A key may not be in the map either if it was never added or if it + * has been expired. + * + * Accessing a key this way will mark its value as most-recently-used. + */ def get(key: K): Box[V] = localMap.get(key) match { case null => Empty case v => @@ -83,12 +105,29 @@ class LRUMap[K, V](initMaxSize: Int, loadFactor: Box[Float], expiredFunc: ((K, V Full(v.value2) } + /** + * Unsafe version of `[[get]]`. + * + * @throws NullPointerException If the key does not exist in the map. Use `get` + * instead to get a safe `[[Box]]` result that can be checked for + * existence, or use `[[contains]]` before calling this. + */ def apply(key: K) = get(key).openOrThrowException("Simulating what happens with a regular Map, use contains(key) to check if it is present or not.") + /** + * Check if the given `key` exists in the map. A key may not be in the map + * either if it was never added or if it has been expired. + */ def contains(key: K): Boolean = localMap.containsKey(key) + /** + * Remove the given `key` and its associated value from the map. + */ def -(key: K) = remove(key) + /** + * Alias for `[[-]]`. + */ def remove(key: K) { localMap.get(key) match { case null => @@ -98,6 +137,13 @@ class LRUMap[K, V](initMaxSize: Int, loadFactor: Box[Float], expiredFunc: ((K, V } } + /** + * Set the `value` for the given `key` in the map. + * + * Marks the given `value` as the most recently used, and, if this `key` is + * new in the map and the map has grown beyond the specifiex `[[maxSize]]`, + * evicts the least-recently-used entries. + */ def update(key: K, value: V) { localMap.get(key) match { case null => @@ -117,16 +163,15 @@ class LRUMap[K, V](initMaxSize: Int, loadFactor: Box[Float], expiredFunc: ((K, V /** * Override this method to implement a test to see if a particular - * element can be expired from the cache + * element can be expired from the cache. */ protected def canExpire(k: K, v: V): Boolean = { true } /** - * A mechanism for expiring elements from cache. This method - * can devolve into O(n ^ 2) if lots of elements can't be - * expired + * A mechanism for expiring elements from cache. This method can devolve into + * O(n ^ 2) if lots of elements can't be expired. */ private def doRemoveIfTooMany() { while (localMap.size > maxSize) { @@ -143,7 +188,9 @@ class LRUMap[K, V](initMaxSize: Int, loadFactor: Box[Float], expiredFunc: ((K, V } /** - * Called when a key/value pair is removed + * Called when a key/value pair is removed, before the `expiredFunc`. + * + * Does nothing by default, override for custom functionality. */ protected def expired(key: K, value: V) { @@ -163,6 +210,5 @@ class LRUMap[K, V](initMaxSize: Int, loadFactor: Box[Float], expiredFunc: ((K, V } def size: Int = localMap.size - } diff --git a/core/common/src/main/scala/net/liftweb/common/LoanWrapper.scala b/core/common/src/main/scala/net/liftweb/common/LoanWrapper.scala index 110f57795b..7f5e421445 100644 --- a/core/common/src/main/scala/net/liftweb/common/LoanWrapper.scala +++ b/core/common/src/main/scala/net/liftweb/common/LoanWrapper.scala @@ -18,15 +18,21 @@ package net.liftweb package common /** - * This trait defines the principle contract for function objects that - * wrap the processing of HTTP requests by Lift while utilizing the preestablished - * request-local scope. + * A component that takes action around some other functionality. It may choose + * to execute or not execute that functionality, but should not interpret or + * change the returned value; instead, it should perform orthogonal actions that + * need to occur around the given functionality. A canonical example is wrapping + * an SQL transaction around some piece of code. + * + * As an example, this trait defines the principal contract for function objects + * that wrap the processing of HTTP requests in Lift. */ trait CommonLoanWrapper { /** * Implementations of this method may either call f to continue processing * the wrapped call as normal, or may ignore f to entirely replace the - * wrapped call with a custom implementation + * wrapped call with a custom implementation. + * * @param f the delegate which provides processing by the underlying framework */ def apply[T](f: => T): T @@ -34,7 +40,21 @@ trait CommonLoanWrapper { object CommonLoanWrapper { /** - * If you have a List of LoanWrappers, apply them and then the functions + * If you have a List of LoanWrappers, apply them and then the functions. For + * example: + * + * {{{ + * val firstWrapper = new TimerWrapper() + * val secondWrapper = new TransactionWrapper() + * + * CommonLoanWrapper(firstWrapper :: secondWrapper :: Nil) { + * // do some things + * }) + * }}} + * + * The inner code will be wrapped first in the timer and then in the + * transaction, so that the timer will time the results of running the code + * inside a transaction. */ def apply[T, LWT <: CommonLoanWrapper](lst: List[LWT])(f: => T): T = lst match { case Nil => f diff --git a/core/common/src/main/scala/net/liftweb/common/Logging.scala b/core/common/src/main/scala/net/liftweb/common/Logging.scala index 546b0f63bb..4e5e778d4a 100644 --- a/core/common/src/main/scala/net/liftweb/common/Logging.scala +++ b/core/common/src/main/scala/net/liftweb/common/Logging.scala @@ -19,6 +19,38 @@ package common import org.slf4j.{MDC => SLF4JMDC, Marker, Logger => SLF4JLogger, LoggerFactory} +/** + * Provides some helpers to easily create `[[Logger]]` instances. + * + * For example: + * {{{ + * class MyClass { + * val logger = Logger(classOf[MyClass]) + * } + * }}} + * + * It can also be used to provide global setup for loggers created this way: + * + * {{{ + * Logger.setup = Full(Logback.withFile(new URL("file:///path/to/config.xml"))) + * + * class MyClass { + * val logger = Loger(classOf[MyClass]) + * + * logger.debug("Hello") // uses the above configuration + * } + * }}} + * + * Last but not least, you can wrap chunks of code with particular + * [[http://www.slf4j.org/manual.html#mdc Mapped Diagnostic Context]] values: + * + * {{{ + * Logger.logWith("mykey" -> "value") { + * logger.debug("stuff") // has mykey set to value in the MDC + * } + * logger.debug("more stuff") // mykey is set to its previous value + * }}} + */ object Logger { private[common] lazy val checkConfig: Boolean = { setup.foreach {_()}; @@ -26,17 +58,21 @@ object Logger { } /** - * This function, if set, will be called before any loggers are created + * This function, if set, will be called before any loggers are created. * - * Useful for initializing the logging backend with a non-default configuration + * Useful for initializing the logging backend with a non-default configuration. * - * Helpers exists for log4j & logback: + * Helpers exists for [[Log4j log4j]] and [[Logback logback]]: * - * Logger.setup = Full(Log4j.withFile(url) + * {{{ + * Logger.setup = Full(Log4j.withFile(url) + * }}} * * or - * - * Logger.setup = Full(Logback.withFile(url)) + * + * {{{ + * Logger.setup = Full(Logback.withFile(url)) + * }}} * */ var setup: Box[() => Unit] = Empty @@ -53,11 +89,11 @@ object Logger { def apply(name: String): Logger = if (checkConfig) new WrappedLogger(LoggerFactory.getLogger(name)) else null /** - * Set the Mapped Diagnostic Context for the thread and execute - * the function f + * Set the [[http://www.slf4j.org/manual.html#mdc Mapped Diagnostic Context]] + * for the thread and execute the block `f`. * - * Upon return, the MDC is cleared of the values passed (any - * MDC values that existed prior to this call remains) + * Upon return, the MDC is cleared of the values passed (any MDC values that + * existed prior to this call remains). */ def logWith[F](mdcValues: (String,Any)*)(f: => F): F = { val old = SLF4JMDC.getCopyOfContextMap @@ -76,8 +112,8 @@ object Logger { /** * The Mapped Diagnostics Context can hold values per thread and output them * with each logged output. - * - * The logging backend needs to be configured to log these values + * + * The logging backend needs to be configured to log these values. */ object MDC { /** @@ -101,20 +137,20 @@ object MDC { } /** - * Logger is a thin wrapper on top of an SLF4J Logger + * `Logger` is a thin wrapper on top of an SLF4J Logger. * - * The main purpose is to utilize Scala features for logging + * The main purpose is to utilize Scala features for logging. * - * Note that the dynamic type of "this" is used when this trait is - * mixed in. + * Note that the dynamic type of "this" is used when this trait is mixed in. * - * This may not always be what you want. If you need the static type, you have to declare your - * own Logger: + * This may not always be what you want. If you need the static type, you have + * to declare your own `Logger`: * + * {{{ * class MyClass { * val logger = Logger(classOf[MyClass]) * } - * + * }}} */ trait Logger { private lazy val logger: SLF4JLogger = _logger // removed @transient 'cause there's no reason for transient on val @@ -132,9 +168,9 @@ trait Logger { } /** - * Trace a Failure. If the log level is trace and the Box is - * a Failure, trace the message concatenated with the Failure's message. - * If the Failure contains an Exception, trace that as well. + * Trace a `[[Failure]]`. If the log level is trace and the `[[Box]]` is a + * `Failure`, trace the message concatenated with the `Failure`'s message. If + * the `Failure` contains an `Exception`, trace that as well. */ def trace(msg: => AnyRef, box: Box[_]): Unit = { if (logger.isTraceEnabled) { @@ -155,9 +191,9 @@ trait Logger { def isTraceEnabled = logger.isTraceEnabled /** - * Debug a Failure. If the log level is debug and the Box is - * a Failure, debug the message concatenated with the Failure's message. - * If the Failure contains an Exception, debug that as well. + * Debug a `Failure`. If the log level is debug and the `Box` is a + * `Failure`, debug the message concatenated with the `Failure`'s message. If + * the `Failure` contains an `Exception`, debug that as well. */ def debug(msg: => AnyRef, box: Box[_]): Unit = { if (logger.isDebugEnabled) { @@ -178,9 +214,9 @@ trait Logger { def isDebugEnabled = logger.isDebugEnabled /** - * Info a Failure. If the log level is info and the Box is - * a Failure, info the message concatenated with the Failure's message. - * If the Failure contains an Exception, info that as well. + * Info a `Failure`. If the log level is info and the `Box` is a `Failure`, + * info the message concatenated with the `Failure`'s message. If the + * `Failure` contains an `Exception`, info that as well. */ def info(msg: => AnyRef, box: Box[_]): Unit = { if (logger.isInfoEnabled) { @@ -199,9 +235,9 @@ trait Logger { def isInfoEnabled = logger.isInfoEnabled /** - * Warn a Failure. If the log level is warn and the Box is - * a Failure, warn the message concatenated with the Failure's message. - * If the Failure contains an Exception, warn that as well. + * Warn a `Failure`. If the log level is warn and the `Box` is a `Failure`, + * warn the message concatenated with the `Failure`'s message. If the + * `Failure` contains an `Exception`, warn that as well. */ def warn(msg: => AnyRef, box: Box[_]): Unit = { if (logger.isWarnEnabled) { @@ -220,9 +256,9 @@ trait Logger { def isWarnEnabled = logger.isWarnEnabled /** - * Error a Failure. If the log level is error and the Box is - * a Failure, error the message concatenated with the Failure's message. - * If the Failure contains an Exception, error that as well. + * Error a `Failure`. If the log level is error and the `Box` is a `Failure`, + * error the message concatenated with the `Failure`'s message. If the + * `Failure` contains an `Exception`, error that as well. */ def error(msg: => AnyRef, box: Box[_]): Unit = { if (logger.isErrorEnabled) { @@ -243,33 +279,43 @@ trait Logger { } +/** + * Represents a `[[Logger]]` backed by an SLF4J `Logger`. + */ class WrappedLogger(l: SLF4JLogger) extends Logger { override def _logger = l } /** - * Mixin with a nested Logger + * If you mix this into your class, you will get a protected `logger` instance + * `val` that will be a `[[Logger]]` instance. */ trait Loggable { @transient protected val logger = Logger(this.getClass) } /** - * Mixin with a nested lazy Logger + * If you mix this into your class, you will get a protected `logger` instance + * `lazy val` that will be a `[[Logger]]` instance. * - * Useful for mixin to objects that are created before Lift has booted (and thus Logging is not yet configured) + * Useful for mixing into objects that are created before Lift has booted (and + * thus Logging is not yet configured). */ trait LazyLoggable { @transient protected lazy val logger = Logger(this.getClass) } /** - * Configuration helpers for the log4j logging backend + * Configuration helpers for the log4j logging backend. */ object Log4j { import org.apache.log4j.{LogManager,PropertyConfigurator} import org.apache.log4j.xml.DOMConfigurator + /** + * Default configuration for log4j backend. Appends to the console with a + * simple layout at `INFO` level. + */ val defaultProps = """ @@ -285,7 +331,8 @@ object Log4j { """ /** - * Configure with contents of the specified filed (either .xml or .properties) + * Configure with the contents of the file at the specified `url` (either + * `.xml` or `.properties`). */ def withFile(url: java.net.URL)() = { if (url.getPath.endsWith(".xml")) { @@ -295,7 +342,8 @@ object Log4j { PropertyConfigurator.configure(url) } /** - * Configure with the specified configuration. config must contain a valid XML document + * Configure with the specified configuration. `config` must contain a valid + * XML document. */ def withConfig(config: String)() = { val domConf = new DOMConfigurator @@ -304,13 +352,13 @@ object Log4j { } /** - * Configure with simple defaults + * Configure with simple defaults. See [[defaultProps]]. */ def withDefault() = withConfig(defaultProps) } /** - * Configuration helpers for the Logback logging backend + * Configuration helpers for the Logback logging backend. */ object Logback { import ch.qos.logback.classic.LoggerContext; @@ -318,7 +366,7 @@ object Logback { import ch.qos.logback.classic.joran.JoranConfigurator; /** - * Configure with contents of the specified XML filed + * Configure with the contents of the XML file at the specified `url`. */ def withFile(url: java.net.URL)() = { val lc = LoggerFactory.getILoggerFactory().asInstanceOf[LoggerContext]; diff --git a/core/common/src/main/scala/net/liftweb/common/SimpleActor.scala b/core/common/src/main/scala/net/liftweb/common/SimpleActor.scala index 09c387ddce..792194d0fe 100644 --- a/core/common/src/main/scala/net/liftweb/common/SimpleActor.scala +++ b/core/common/src/main/scala/net/liftweb/common/SimpleActor.scala @@ -18,8 +18,8 @@ package net.liftweb package common /** - * The simple definition of an actor. Something that - * can receive a message of type T. + * The simple definition of an actor. Something that can be sent a message of + * type `T`. */ trait SimpleActor[-T] { /** @@ -31,52 +31,62 @@ trait SimpleActor[-T] { } /** - * An Actor that can receive a message of any type + * An Actor that can receive a message of any type. */ trait SimplestActor extends SimpleActor[Any] /** - * An Actor that can receive messsages of type T and - * return responses of type R. + * An Actor that can receive messsages of type `T` and return responses of type + * `R`. */ trait TypedActor[-T, +R] extends SimpleActor[T] { - def !?(param: T): R + /** + * Compatible with Scala actors' `!?` method, sends the given `message` to + * this actor and waits infinitely for a reply. + */ + def !?(message: T): R /** - * Compatible with Scala Actors' !? method + * Compatible with Scala actors' `!?` method, sends the given `message` to + * this actor and waits up to `timeout` for a reply, returning `[[Empty]]` or + * `[[Failure]]` if no reply is received by then. */ def !?(timeout: Long, message: Any): Box[R] /** - * Asynchronous message send. Send-and-receive eventually. Waits on a Future for the reply message. - * If recevied within the Actor default timeout interval then it returns Some(result) and if a timeout - * has occured None. + * Asynchronous message send. Send-and-receive eventually. Waits on a Future + * for the reply message. If recevied within the Actor default timeout + * interval then it returns `Full(result)` and if a timeout has occured + * `[[Empty]]` or `[[Failure]]`. */ def !!(message: T): Box[R] /** - * Asynchronous message send. Send-and-receive eventually. Waits on a Future for the reply message. - * If recevied within timout interval that is specified then it returns Some(result) and if a timeout - * has occured None. + * Asynchronous message send. Send-and-receive eventually. Waits on a Future + * for the reply message. If recevied within timout interval that is + * specified then it returns `Full(result)` and if a timeout has occured + * `[[Empty]]` or `[[Failure]]`. */ def !!(message: T, timeout: Long): Box[R] - } /** - * Generic Actor interface. Can receive any type of message. - * Can return (via !! and !?) messages of type R. + * Generic Actor interface. Can receive any type of message. Can return (via + * `!!` and `!?`) messages of type `R`. */ trait GenericActor[+R] extends TypedActor[Any, R] /** * Generic Actor interface. Can receive any type of message. - * Can return (via !! and !?) messages of any type. + * Can return (via `!!` and `!?`) messages of any type. */ trait SimplestGenericActor extends GenericActor[Any] - +/** + * Interface for an actor that can internally forward received messages to other + * actors. + */ trait ForwardableActor[From, To] { self: TypedActor[From, To] => diff --git a/core/common/src/main/scala/net/liftweb/common/SimpleList.scala b/core/common/src/main/scala/net/liftweb/common/SimpleList.scala index fdc67372b9..d9536faeab 100644 --- a/core/common/src/main/scala/net/liftweb/common/SimpleList.scala +++ b/core/common/src/main/scala/net/liftweb/common/SimpleList.scala @@ -21,26 +21,32 @@ import java.util.{List => JavaList, Iterator => JavaIterator, ArrayList, ListIterator, Collection => JavaCollection} /** - * An immutable singly linked list that uses the Scala List class - * as backing store, but is Java-friendly. + * An immutable singly linked list that uses the Scala List class as backing + * store, but is Java-friendly as a `java.util.List`. Note however that since it + * is immutable, you have to capture the results of addition/removal operations. + * + * The typical mutating methods like `add`, `set`, `clear`, and `remove` are all + * unsupported, as are mutating methods on its iterators, since this collection + * is immutable. */ final case class SimpleList[T](underlying: List[T]) extends JavaList[T] { /** - * Construct an empty List + * Construct an empty list. */ def this() = this(Nil) def this(jl: JavaList[T]) = this(jl.toArray().toList.asInstanceOf[List[T]]) /** - * Append an item to this list. An O(n) operation where n is the - * number of items in the underlying List. + * Append an item to this list. This operation is O(n) where `n` is the number + * of items in the underlying `List`, and returns the updated list. */ def append(item: T): SimpleList[T] = SimpleList(underlying :+ item) /** - * Prepends an item to this list. O(1) + * Prepends an item to this list. This operation is O(1) and returns the + * updated list. */ def prepend(item: T): SimpleList[T] = SimpleList(item :: underlying) @@ -67,9 +73,9 @@ final case class SimpleList[T](underlying: List[T]) extends JavaList[T] { def isEmpty(): Boolean = underlying.isEmpty /** - * Does the List contain the element + * Returns true if this list contains the given `obj`. */ - def contains(x: Object): Boolean = underlying.contains(x) + def contains(obj: Object): Boolean = underlying.contains(obj) def iterator(): JavaIterator[T] = { val it = underlying.iterator @@ -170,26 +176,36 @@ final case class SimpleList[T](underlying: List[T]) extends JavaList[T] { } /** - * An immutable singly linked list that uses the Scala List class - * as backing store, but is Java-friendly. + * An immutable vector that uses the Scala `[[scala.collection.immutable.Vector Vector]]` + * class as backing store, but is Java-friendly as a `java.util.List`. Note however that + * since it is immutable, you have to capture the results of addition/removal + * operations. + * + * The typical mutating methods like `add`, `set`, `clear`, and `remove` are all + * unsupported, as are mutating methods on its iterators, since this collection + * is immutable. + * + * @see [[http://docs.scala-lang.org/overviews/collections/concrete-immutable-collection-classes.html#vectors "Scala's Collection Library overview"]] + * section on Vectors for more information. */ final case class SimpleVector[T](underlying: Vector[T]) extends JavaList[T] { /** - * Construct an empty List + * Construct an empty vector. */ def this() = this(Vector()) def this(jl: JavaList[T]) = this(Vector(jl.toArray().toList.asInstanceOf[List[T]] :_*)) /** - * Append an item to this list. An O(n) operation where n is the - * number of items in the underlying List. + * Append an item to this vector. This operation is effectively O(1) and + * returns the updated vector. */ def append(item: T): SimpleVector[T] = SimpleVector(underlying :+ item) /** - * Prepends an item to this list. O(1) + * Prepends an item to this vector. This operation is effectively O(1) and + * returns the updated vector. */ def prepend(item: T): SimpleVector[T] = SimpleVector(item +: underlying) @@ -216,9 +232,9 @@ final case class SimpleVector[T](underlying: Vector[T]) extends JavaList[T] { def isEmpty(): Boolean = underlying.isEmpty /** - * Does the List contain the element + * Returns `true` if this vector contains the given `obj`. */ - def contains(x: Object): Boolean = underlying.contains(x) + def contains(obj: Object): Boolean = underlying.contains(obj) def iterator(): JavaIterator[T] = { val it = underlying.iterator @@ -270,8 +286,7 @@ final case class SimpleVector[T](underlying: Vector[T]) extends JavaList[T] { } ret - } - + } def toArray[X](in: Array[X with Object]): Array[X with Object] = { val clz = in.getClass.getComponentType() From b017a28023443b7bc2e98978a08322257f86d8d5 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 14 Dec 2014 20:23:02 -0500 Subject: [PATCH 1078/1949] Move an import up to the head of Conversions.scala. --- .../common/src/main/scala/net/liftweb/common/Conversions.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Conversions.scala b/core/common/src/main/scala/net/liftweb/common/Conversions.scala index f6a308dbbd..cb27936513 100644 --- a/core/common/src/main/scala/net/liftweb/common/Conversions.scala +++ b/core/common/src/main/scala/net/liftweb/common/Conversions.scala @@ -18,7 +18,7 @@ package net.liftweb package common import scala.language.implicitConversions -import scala.xml.NodeSeq +import scala.xml._ /* * This file contains common conversions and other utilities to make @@ -48,7 +48,6 @@ sealed trait StringOrNodeSeq { * their needs dictate. */ object StringOrNodeSeq { - import scala.xml._ /** * Convert a String to a StringOrNodeSeq From 55fa831d9b848c5d37d376f56fa2a4ec82f2d8e7 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 14 Dec 2014 20:23:44 -0500 Subject: [PATCH 1079/1949] Clean out documentation that adds nothing. Some of the scaladocs were extremely redundant with respect to the functions they were describing. This commit drops those Scaladocs. --- .../net/liftweb/common/Conversions.scala | 21 ------------------- .../net/liftweb/common/FuncJBridge.scala | 21 ------------------- .../main/scala/net/liftweb/common/HList.scala | 6 ------ .../scala/net/liftweb/common/SimpleList.scala | 12 ----------- 4 files changed, 60 deletions(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Conversions.scala b/core/common/src/main/scala/net/liftweb/common/Conversions.scala index cb27936513..c1119312e0 100644 --- a/core/common/src/main/scala/net/liftweb/common/Conversions.scala +++ b/core/common/src/main/scala/net/liftweb/common/Conversions.scala @@ -48,10 +48,6 @@ sealed trait StringOrNodeSeq { * their needs dictate. */ object StringOrNodeSeq { - - /** - * Convert a String to a StringOrNodeSeq - */ implicit def strTo[T <% String](str: T): StringOrNodeSeq = new StringOrNodeSeq { def nodeSeq: NodeSeq = Text(str) @@ -66,9 +62,6 @@ object StringOrNodeSeq { def nodeSeq: NodeSeq = ns } - /** - * Convert a StringOrNodeSeq into a NodeSeq - */ implicit def toNodeSeq(sns: StringOrNodeSeq): NodeSeq = sns.nodeSeq } @@ -93,29 +86,15 @@ sealed trait StringFunc { * while the former is simpler to use. */ object StringFunc { - /** - * If you've got something that can be converted into a String (a constant) - * but want a StringFunc, this implicit will do the conversion. - */ implicit def strToStringFunc[T](str: T)(implicit f: T => String): StringFunc = ConstStringFunc(f(str)) - /** - * If you've got something that can be converted into a String Function - * but want a StringFunc, this implicit will do the conversion. - */ implicit def funcToStringFunc[T](func: () => T)(implicit f: T => String): StringFunc = RealStringFunc(() => f(func())) } -/** - * The case class that holds a String function. - */ final case class RealStringFunc(func: () => String) extends StringFunc -/** - * The case class that holds the String constant. - */ final case class ConstStringFunc(str: String) extends StringFunc { lazy val func = () => str } diff --git a/core/common/src/main/scala/net/liftweb/common/FuncJBridge.scala b/core/common/src/main/scala/net/liftweb/common/FuncJBridge.scala index ba9d21354c..975af6d082 100644 --- a/core/common/src/main/scala/net/liftweb/common/FuncJBridge.scala +++ b/core/common/src/main/scala/net/liftweb/common/FuncJBridge.scala @@ -29,51 +29,30 @@ object FuncJBridge extends FuncJBridge * various function arities. */ class FuncJBridge { - /** - * Lift the Java Func0 to a Scala Function0 - */ implicit def lift[Z](f: Func0[Z]): Function0[Z] = new Function0[Z] { def apply(): Z = f.apply() } - /** - * Drop from Scala function to Java function - */ implicit def drop[Z](f: Function0[Z]): Func0[Z] = new Func0[Z] { def apply(): Z = f.apply() } - /** - * Lift the Java Func1 to a Scala Function1 - */ implicit def lift[A, Z](f: Func1[A, Z]): Function1[A, Z] = new Function1[A, Z] { def apply(a: A): Z = f.apply(a) } - /** - * Lift the Java Func2 to a Scala Function2 - */ implicit def lift[A, B, Z](f: Func2[A, B, Z]): Function2[A, B, Z] = new Function2[A, B, Z] { def apply(a: A, b: B): Z = f.apply(a, b) } - /** - * Lift the Java Func3 to a Scala Function3 - */ implicit def lift[A, B, C, Z](f: Func3[A, B, C, Z]): Function3[A, B, C, Z] = new Function3[A, B, C, Z] { def apply(a: A, b: B, c: C): Z = f.apply(a, b, c) } - /** - * Lift the Java Func4 to a Scala Function4 - */ implicit def lift[A, B, C, D, Z](f: Func4[A, B, C, D, Z]): Function4[A, B, C, D, Z] = new Function4[A, B, C, D, Z] { def apply(a: A, b: B, c: C, d: D): Z = f.apply(a, b, c, d) } - /** - * Lift the Java Callable to a Scala Function0 - */ implicit def lift[Z](f: java.util.concurrent.Callable[Z]): Function0[Z] = new Function0[Z] { def apply(): Z = f.call() } diff --git a/core/common/src/main/scala/net/liftweb/common/HList.scala b/core/common/src/main/scala/net/liftweb/common/HList.scala index a199577c4d..b7775305b7 100644 --- a/core/common/src/main/scala/net/liftweb/common/HList.scala +++ b/core/common/src/main/scala/net/liftweb/common/HList.scala @@ -101,9 +101,6 @@ object HLists { override def toString = "HNil" - /** - * The length of the HList - */ def length = 0 } @@ -143,9 +140,6 @@ object HLists { override def toString = head + " :+: " + tail - /** - * The length of the HList - */ def length = 1 + tail.length } diff --git a/core/common/src/main/scala/net/liftweb/common/SimpleList.scala b/core/common/src/main/scala/net/liftweb/common/SimpleList.scala index d9536faeab..76f599462f 100644 --- a/core/common/src/main/scala/net/liftweb/common/SimpleList.scala +++ b/core/common/src/main/scala/net/liftweb/common/SimpleList.scala @@ -62,14 +62,8 @@ final case class SimpleList[T](underlying: List[T]) extends JavaList[T] { def tail(): SimpleList[T] = SimpleList(underlying.tail) - /** - * The size of the List - */ def size(): Int = underlying.length - /** - * Is the List Empty - */ def isEmpty(): Boolean = underlying.isEmpty /** @@ -221,14 +215,8 @@ final case class SimpleVector[T](underlying: Vector[T]) extends JavaList[T] { def tail(): SimpleVector[T] = SimpleVector(underlying.tail) - /** - * The size of the List - */ def size(): Int = underlying.length - /** - * Is the List Empty - */ def isEmpty(): Boolean = underlying.isEmpty /** From 0f8ce86d31753f260cdadeb1f5cfd4890fa6f747 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 14 Dec 2014 20:24:25 -0500 Subject: [PATCH 1080/1949] Deprecate NodeSeqFunc. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It doesn’t seem to be used anywhere and we generally lean on transformation functions rather than generation functions for NodeSeq. This is also something we want to continue to encourage, so making using NodeSeqs directly a little more painful is okay. --- .../src/main/scala/net/liftweb/common/Conversions.scala | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/core/common/src/main/scala/net/liftweb/common/Conversions.scala b/core/common/src/main/scala/net/liftweb/common/Conversions.scala index c1119312e0..1097ad52a3 100644 --- a/core/common/src/main/scala/net/liftweb/common/Conversions.scala +++ b/core/common/src/main/scala/net/liftweb/common/Conversions.scala @@ -105,6 +105,8 @@ final case class ConstStringFunc(str: String) extends StringFunc { * with the implicit conversions defined in its [[NodeSeqFunc$ companion * object]]. */ +@deprecated("""Lift now mostly uses NodeSeq=>NodeSeq transformations rather than +NodeSeq constants; consider doing the same.""","3.0") sealed trait NodeSeqFunc { def func: () => NodeSeq } @@ -115,6 +117,8 @@ sealed trait NodeSeqFunc { * the flexibility of a `()=>[[scala.xml.NodeSeq NodeSeq]]` without having to * write overloads for all methods that should accept both. */ +@deprecated("""Lift now mostly uses NodeSeq=>NodeSeq transformations rather than +NodeSeq constants; consider doing the same.""","3.0") object NodeSeqFunc { /** * If you've got something that can be converted into a NodeSeq (a constant) @@ -134,11 +138,15 @@ object NodeSeqFunc { /** * The case class that holds a `[[scala.xml.NodeSeq NodeSeq]]` function. */ +@deprecated("""Lift now mostly uses NodeSeq=>NodeSeq transformations rather than +NodeSeq constants; consider doing the same.""","3.0") final case class RealNodeSeqFunc(func: () => NodeSeq) extends NodeSeqFunc /** * The case class that holds the `[[scala.xml.NodeSeq NodeSeq]]` constant. */ +@deprecated("""Lift now mostly uses NodeSeq=>NodeSeq transformations rather than +NodeSeq constants; consider doing the same.""","3.0") final case class ConstNodeSeqFunc(ns: NodeSeq) extends NodeSeqFunc { lazy val func = () => ns } From 22ee9e4fef4fa070186ef01181dfbd957bc74755 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 14 Dec 2014 20:24:52 -0500 Subject: [PATCH 1081/1949] Rename a variable in Logger. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit checkConfig was used to make sure setup was being run. It’s now named ranSetup, since that’s more reflective of what it means. --- .../main/scala/net/liftweb/common/Logging.scala | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Logging.scala b/core/common/src/main/scala/net/liftweb/common/Logging.scala index 4e5e778d4a..19a2b91e8d 100644 --- a/core/common/src/main/scala/net/liftweb/common/Logging.scala +++ b/core/common/src/main/scala/net/liftweb/common/Logging.scala @@ -52,8 +52,8 @@ import org.slf4j.{MDC => SLF4JMDC, Marker, Logger => SLF4JLogger, LoggerFactory} * }}} */ object Logger { - private[common] lazy val checkConfig: Boolean = { - setup.foreach {_()}; + private[common] lazy val ranSetup: Boolean = { + setup.foreach { _() } true } @@ -85,8 +85,8 @@ object Logger { className } - def apply(cls: Class[_]): Logger = if (checkConfig) new WrappedLogger(LoggerFactory.getLogger(loggerNameFor(cls))) else null - def apply(name: String): Logger = if (checkConfig) new WrappedLogger(LoggerFactory.getLogger(name)) else null + def apply(cls: Class[_]): Logger = if (ranSetup) new WrappedLogger(LoggerFactory.getLogger(loggerNameFor(cls))) else null + def apply(name: String): Logger = if (ranSetup) new WrappedLogger(LoggerFactory.getLogger(name)) else null /** * Set the [[http://www.slf4j.org/manual.html#mdc Mapped Diagnostic Context]] @@ -153,9 +153,9 @@ object MDC { * }}} */ trait Logger { - private lazy val logger: SLF4JLogger = _logger // removed @transient 'cause there's no reason for transient on val - // changed to lazy val so it only gets initialized on use rather than on instantiation - protected def _logger = if (Logger.checkConfig) LoggerFactory.getLogger(Logger.loggerNameFor(this.getClass)) else null + private lazy val logger: SLF4JLogger = _logger + + protected def _logger = if (Logger.ranSetup) LoggerFactory.getLogger(Logger.loggerNameFor(this.getClass)) else null def assertLog(assertion: Boolean, msg: => String) = if (assertion) info(msg) From 6b2917bb64c1dff37346450e7f5dee12d7fc76ff Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 14 Dec 2014 20:25:57 -0500 Subject: [PATCH 1082/1949] Use UnsupportedOperationException where reasonable. SimpleList and SimpleVector have a bunch of unsupported operations with respect to regular Java collections; these were through Exception instances. We switch them to throw UnsupportedOperationException as per the Java collection contract. --- .../scala/net/liftweb/common/SimpleList.scala | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/core/common/src/main/scala/net/liftweb/common/SimpleList.scala b/core/common/src/main/scala/net/liftweb/common/SimpleList.scala index 76f599462f..4063d5806a 100644 --- a/core/common/src/main/scala/net/liftweb/common/SimpleList.scala +++ b/core/common/src/main/scala/net/liftweb/common/SimpleList.scala @@ -76,7 +76,7 @@ final case class SimpleList[T](underlying: List[T]) extends JavaList[T] { new JavaIterator[T] { def hasNext() = it.hasNext def next(): T = it.next() - def remove() = throw new Exception("Does not support") + def remove() = throw new UnsupportedOperationException() } } @@ -95,17 +95,17 @@ final case class SimpleList[T](underlying: List[T]) extends JavaList[T] { def lastIndexOf(obj: Object): Int = underlying.lastIndexOf(obj) - def add(x: T): Boolean = throw new Exception("Does not support") + def add(x: T): Boolean = throw new UnsupportedOperationException() - def add(after: Int, x: T): Unit = throw new Exception("Does not support") + def add(after: Int, x: T): Unit = throw new UnsupportedOperationException() - def set(after: Int, x: T): T = throw new Exception("Does not support") + def set(after: Int, x: T): T = throw new UnsupportedOperationException() - def clear(): Unit = throw new Exception("Does not support") + def clear(): Unit = throw new UnsupportedOperationException() - def remove(pos: Int): T = throw new Exception("Does not support") + def remove(pos: Int): T = throw new UnsupportedOperationException() - def remove(obj: Object): Boolean = throw new Exception("Does not support") + def remove(obj: Object): Boolean = throw new UnsupportedOperationException() def get(pos: Int): T = underlying(pos) @@ -142,13 +142,13 @@ final case class SimpleList[T](underlying: List[T]) extends JavaList[T] { ret } - def retainAll(jc: JavaCollection[_]): Boolean = throw new Exception("Does not support") + def retainAll(jc: JavaCollection[_]): Boolean = throw new UnsupportedOperationException() - def removeAll(jc: JavaCollection[_]): Boolean = throw new Exception("Does not support") + def removeAll(jc: JavaCollection[_]): Boolean = throw new UnsupportedOperationException() - def addAll(jc: JavaCollection[_ <: T]): Boolean = throw new Exception("Does not support") + def addAll(jc: JavaCollection[_ <: T]): Boolean = throw new UnsupportedOperationException() - def addAll(index: Int, jc: JavaCollection[_ <: T]): Boolean = throw new Exception("Does not support") + def addAll(index: Int, jc: JavaCollection[_ <: T]): Boolean = throw new UnsupportedOperationException() def containsAll(jc: JavaCollection[_]): Boolean = { val it = jc.iterator() @@ -229,7 +229,7 @@ final case class SimpleVector[T](underlying: Vector[T]) extends JavaList[T] { new JavaIterator[T] { def hasNext() = it.hasNext def next(): T = it.next() - def remove() = throw new Exception("Does not support") + def remove() = throw new UnsupportedOperationException() } } @@ -248,17 +248,17 @@ final case class SimpleVector[T](underlying: Vector[T]) extends JavaList[T] { def lastIndexOf(obj: Object): Int = underlying.lastIndexOf(obj) - def add(x: T): Boolean = throw new Exception("Does not support") + def add(x: T): Boolean = throw new UnsupportedOperationException() - def add(after: Int, x: T): Unit = throw new Exception("Does not support") + def add(after: Int, x: T): Unit = throw new UnsupportedOperationException() - def set(after: Int, x: T): T = throw new Exception("Does not support") + def set(after: Int, x: T): T = throw new UnsupportedOperationException() - def clear(): Unit = throw new Exception("Does not support") + def clear(): Unit = throw new UnsupportedOperationException() - def remove(pos: Int): T = throw new Exception("Does not support") + def remove(pos: Int): T = throw new UnsupportedOperationException() - def remove(obj: Object): Boolean = throw new Exception("Does not support") + def remove(obj: Object): Boolean = throw new UnsupportedOperationException() def get(pos: Int): T = underlying(pos) @@ -290,13 +290,13 @@ final case class SimpleVector[T](underlying: Vector[T]) extends JavaList[T] { ret } - def retainAll(jc: JavaCollection[_]): Boolean = throw new Exception("Does not support") + def retainAll(jc: JavaCollection[_]): Boolean = throw new UnsupportedOperationException() - def removeAll(jc: JavaCollection[_]): Boolean = throw new Exception("Does not support") + def removeAll(jc: JavaCollection[_]): Boolean = throw new UnsupportedOperationException() - def addAll(jc: JavaCollection[_ <: T]): Boolean = throw new Exception("Does not support") + def addAll(jc: JavaCollection[_ <: T]): Boolean = throw new UnsupportedOperationException() - def addAll(index: Int, jc: JavaCollection[_ <: T]): Boolean = throw new Exception("Does not support") + def addAll(index: Int, jc: JavaCollection[_ <: T]): Boolean = throw new UnsupportedOperationException() def containsAll(jc: JavaCollection[_]): Boolean = { val it = jc.iterator() From 00ac6d2ee4ae1dca70b66b4b08e4d7f7539351f2 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 21 Dec 2014 16:48:17 -0500 Subject: [PATCH 1083/1949] Fix incorrect emphasis scaladoc markers. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apparently * doesn’t emphasize, ''' does. Seems legit… --- core/common/src/main/scala/net/liftweb/common/Box.scala | 4 ++-- core/common/src/main/scala/net/liftweb/common/LRU.scala | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index d85c565999..f844f75adc 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -407,7 +407,7 @@ sealed abstract class Box[+A] extends Product with Serializable{ * Exists to avoid the implicit conversion from `Box` to `Option`. Opening a * `Box` unsafely should be done using `openOrThrowException`. * - * This method *always* throws an exception. + * This method '''always''' throws an exception. */ final def get: DoNotCallThisMethod = { throw new Exception("Attempted to open a Box incorrectly. Please use openOrThrowException.") @@ -480,7 +480,7 @@ sealed abstract class Box[+A] extends Product with Serializable{ /** * - * If this `Box` contains a value and it does *not* satisfy the specified + * If this `Box` contains a value and it does '''not''' satisfy the specified * `predicate`, return the `Box` unchanged. Otherwise, return an `Empty`. */ def filterNot(f: A => Boolean): Box[A] = filter(a => !f(a)) diff --git a/core/common/src/main/scala/net/liftweb/common/LRU.scala b/core/common/src/main/scala/net/liftweb/common/LRU.scala index 06bcd64a2a..b619c5d369 100644 --- a/core/common/src/main/scala/net/liftweb/common/LRU.scala +++ b/core/common/src/main/scala/net/liftweb/common/LRU.scala @@ -50,7 +50,7 @@ private[common] trait LinkedListElem[T1, T2] { * Implements an LRU Hashmap. Given a size, this map will evict the least * recently used item(s) when new items are added. * - * Note that `LRUMap` is *not* thread-safe. + * Note that `LRUMap` is '''not''' thread-safe. * * @param initmaxSize The initial max size. This can be updated using * `[[updateMaxSize]]`. From 082536357ae7262584c7e195aeadad939cb0aade Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 21 Dec 2014 18:02:25 -0500 Subject: [PATCH 1084/1949] Fix a typo in the Logger docs. --- core/common/src/main/scala/net/liftweb/common/Logging.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Logging.scala b/core/common/src/main/scala/net/liftweb/common/Logging.scala index 19a2b91e8d..5f60b7f405 100644 --- a/core/common/src/main/scala/net/liftweb/common/Logging.scala +++ b/core/common/src/main/scala/net/liftweb/common/Logging.scala @@ -35,7 +35,7 @@ import org.slf4j.{MDC => SLF4JMDC, Marker, Logger => SLF4JLogger, LoggerFactory} * Logger.setup = Full(Logback.withFile(new URL("file:///path/to/config.xml"))) * * class MyClass { - * val logger = Loger(classOf[MyClass]) + * val logger = Logger(classOf[MyClass]) * * logger.debug("Hello") // uses the above configuration * } From 15b7e816076de5da2f40fee6caccd90c8d0ba394 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 21 Dec 2014 18:03:55 -0500 Subject: [PATCH 1085/1949] Clarify and improve various scaladocs. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mostly adding `` around classes and parameters in docs, with a couple of tweaks to docs that weren’t clear enough. --- .../main/scala/net/liftweb/common/Box.scala | 103 ++++++++++-------- .../net/liftweb/common/Conversions.scala | 36 +++--- .../net/liftweb/common/LoanWrapper.scala | 8 +- 3 files changed, 80 insertions(+), 67 deletions(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index f844f75adc..cef6b27aea 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -92,7 +92,7 @@ object Box extends BoxTrait { } /** - * Implementation for the `[[Box$]]` singleton. + * Implementation for the `[[Box$ Box]]` singleton. */ sealed trait BoxTrait { val primitiveMap: Map[Class[_], Class[_]] = Map( @@ -122,7 +122,8 @@ sealed trait BoxTrait { /** * Create a `Box` from the specified `Box`, checking for `null`. * - * @return `Full(in)` if `in` is non-null, `Empty` otherwise. + * @return `Full(in)` if `in` is a `Full` box and its value is non-null, + * `Empty` otherwise. */ def apply[T](in: Box[T]) = in match { case Full(x) => legacyNullTest(x) @@ -131,9 +132,8 @@ sealed trait BoxTrait { } /** - * Transform a `List` with zero or one elements to a `Box`. - * - * Note that any elements past the head of the list are lost! + * Transform a `List` with zero or more elements to a `Box`, losing all but + * the first element if there are more than one. * * @return `Full(x)` with the head of the list if it contains at least one * element and `Empty` otherwise. @@ -272,10 +272,11 @@ final class DoNotCallThisMethod /** * The `Box` class is a container which is able to declare if it is `Full` - * (containing a single non-null value) or `EmptyBox`. An EmptyBox, or empty, - * can be the `Empty` singleton, `Failure` or `ParamFailure`. `Failure` and - * `ParamFailure` contain information about why the `Box` is empty including - * exception information, possibly chained `Failures` and a `String` message. + * (containing a single non-null value) or `[[EmptyBox]]`. An `EmptyBox`, + * or empty, can be the `[[Empty]]` singleton, `[[Failure]]` or + * `[[ParamFailure]]`. `Failure` and `ParamFailure` contain information about + * why the `Box` is empty including exception information, possibly chained + * `Failure`s and a `String` message. * * This serves a similar purpose to the `[[scala.Option Option]]` class from * Scala standard library but adds several features: @@ -283,8 +284,8 @@ final class DoNotCallThisMethod * `[[?~]]` or `[[failMsg]]` method). * - You can chain failure messages on `Failure`s (with the `?~!` or * `[[compoundFailMsg]]` method). - * - You "run" a function on a `Box`, with a default to return if the box is - * `Empty`: + * - You can "run" a function on a `Box`, with a default to return if the box + * is `Empty`: * {{{ * val littleTeddyBears: Box[Int] = Full(10) * littleTeddyBears.run("and then there were none") { (default: String, teddyBears: Int) => @@ -308,7 +309,9 @@ final class DoNotCallThisMethod * ) // doSomething gets a Box[Int] as well * }}} * - * If you grew up on Java, you're used to `Exceptions` as part of your program + * === Exceptions and Empty Box Handling === + * + * If you grew up on Java, you're used to `Exception`s as part of your program * logic. The Scala philosophy and the Lift philosophy is that exceptions are * for exceptional conditions such as failure of an external resource (e.g., * your database goes offline) rather than simply indicating that a parameter @@ -383,8 +386,8 @@ sealed abstract class Box[+A] extends Product with Serializable{ * The only time when you should be using this method is if the value is * guaranteed to be available based on a guard outside of the method. In these * cases, please provide that information in the justification `String`. - * For example, User.currentUser.openOrThrowException("This snippet is only - * used on pages where the user is logged in"). For tests, use `[[==]]` or + * For example, `User.currentUser.openOrThrowException("This snippet is only + * used on pages where the user is logged in")`. For tests, use `[[==]]` or * `[[===]]` instead. See the class documentation for more information. * * A valid justification for using this method should not be "I want my code @@ -432,9 +435,9 @@ sealed abstract class Box[+A] extends Product with Serializable{ /** * Apply a function returning a `Box` to the value contained in this `Box` if * it exists and return the resulting `Box`. If this `Box` is not already - * `Full`, return the unchanged box. + * `Full`, return this box unchanged. * - * @note This means that using `map` with a `Failure` will preserve the + * @note This means that using `flatMap` with a `Failure` will preserve the * `Failure.` */ def flatMap[B](f: A => Box[B]): Box[B] = Empty @@ -462,16 +465,17 @@ sealed abstract class Box[+A] extends Product with Serializable{ } /** - * If this `Box` contains a value and it satisfies the specified `predicate`, + * If this `Box` contains a value and it satisfies the specified `func`, * return `true`. Otherwise, return `false`. * - * @return true if this Box does contain a value and it satisfies the predicate + * @return `true` if this Box does contain a value and it satisfies the + * predicate. */ def exists(func: A => Boolean): Boolean = false /** * If this `Box` contains a value and it does not satisfy the specified - * `predicate`, return `false`. Otherwise, return `true`. + * `func`, return `false`. Otherwise, return `true`. * * @return true If the `Box` is empty, or if its value satisfies the * predicate. @@ -481,7 +485,7 @@ sealed abstract class Box[+A] extends Product with Serializable{ /** * * If this `Box` contains a value and it does '''not''' satisfy the specified - * `predicate`, return the `Box` unchanged. Otherwise, return an `Empty`. + * `f`, return the `Box` unchanged. Otherwise, return an `Empty`. */ def filterNot(f: A => Boolean): Box[A] = filter(a => !f(a)) @@ -492,8 +496,8 @@ sealed abstract class Box[+A] extends Product with Serializable{ def foreach[U](f: A => U): Unit = {} /** - * Create a `Full` box containing the specified value if `in` is an instance of - * the specified class `clz` and `Empty` otherwise. + * If this box is `Full` and contains an object of type `B`, returns a `Full` + * of type `Box[B]`. Otherwise, returns `Empty`. * * This is basically a Java-friendly version of `[[asA]]`, which you should * prefer when using Scala. @@ -510,8 +514,8 @@ sealed abstract class Box[+A] extends Product with Serializable{ def isA[B](cls: Class[B]): Box[B] = Empty /** - * Create a `Full` box containing the specified value if `in` is of type - * `B` and `Empty` otherwise. + * Create a `Full` box containing the specified value if this box's value is + * of type `B` and `Empty` otherwise. * * For example: * {{{ @@ -525,7 +529,8 @@ sealed abstract class Box[+A] extends Product with Serializable{ def asA[B](implicit m: Manifest[B]): Box[B] = Empty /** - * Return this Box if `Full`, or the specified alternative if it is empty. + * Return this Box if `Full`, or the specified alternative if it is + * empty. Equivalent to `Option`'s `[[scala.Option.orElse orElse]]`. */ def or[B >: A](alternative: => Box[B]): Box[B] = alternative @@ -559,20 +564,19 @@ sealed abstract class Box[+A] extends Product with Serializable{ def toList: List[A] = Nil /** - * Returns the contents of this box wrapped in `Some` if this is Full, or - * `None` if this is a empty (meaning an `Empty`, `Failure` or - * `ParamFailure`). + * Returns the contents of this box wrapped in `Some` if this is `Full`, or + * `None` if this is empty (meaning an `Empty`, `Failure` or ParamFailure`). */ def toOption: Option[A] = None /** - * Transform an Empty to a Failure with the specified message. Otherwise - * leaves returns the same box. + * Transform an `Empty` to a `Failure` with the specified message. Otherwise + * returns the same box. * * @note This means a `Failure` will also remain unchanged; see `?~!` to * change these. * - * @return A Failure with the message if this `Box` is `Empty`, this box + * @return A `Failure` with the message if this `Box` is `Empty`, this box * otherwise. */ def ?~(msg: => String): Box[A] = this @@ -603,9 +607,9 @@ sealed abstract class Box[+A] extends Product with Serializable{ * * As with `[[?~]]`, if this is a `Full`, we return it unchanged. * - * @return A Failure with the message if this Box is an Empty Box. Chain this - * box to the new `Failure` if this is a `Failure`. The unchanged box - * if it is a `Full`. + * @return A `Failure` with the message if this `Box` is an `Empty` box. Chain + * this box to the new `Failure` if this is a `Failure`. The unchanged + * box if it is a `Full`. */ def ?~!(msg: => String): Box[A] = ?~(msg) @@ -636,8 +640,8 @@ sealed abstract class Box[+A] extends Product with Serializable{ def run[T](in: => T)(f: (T, A) => T) = in /** - * Perform a side effect by passing this Box to the specified function and - * return this Box unmodified. Similar to `foreach`, except that `foreach` + * Perform a side effect by passing this `Box` to the specified function and + * return this `Box` unmodified. Similar to `foreach`, except that `foreach` * returns `Unit`, while this method allows chained use of the `Box`. * * @return This box. @@ -700,7 +704,7 @@ sealed abstract class Box[+A] extends Product with Serializable{ def ===[B >: A](to: B): Boolean = false /** - * Equivalent to `map(f).openOr(Full(dflt))`. + * Equivalent to `map(f).openOr(dflt)`. */ def dmap[B](dflt: => B)(f: A => B): B = dflt @@ -731,14 +735,16 @@ sealed abstract class Box[+A] extends Product with Serializable{ def fullXform[T](v: T)(f: T => A => T): T = v /** - * An `Either` that is a `Left` with the given argument `left` if this is - * empty, or a `Right` with the boxed value if this is `Full`. + * An `[[scala.util.Either Either]]` that is a `Left` with the given argument + * `left` if this is empty, or a `Right` with the boxed value if this is + * `Full`. */ def toRight[B](left: => B): Either[B, A] = Left(left) /** - * An `Either` that is a `Right` with the given argument `right` if this is - * empty, or a `Left` with the boxed value if this is `Full`. + * An `[[scala.util.Either Either]]` that is a `Right` with the given argument + * `right` if this is empty, or a `Left` with the boxed value if this is + * `Full`. */ def toLeft[B](right: => B): Either[A, B] = Right(right) @@ -972,8 +978,9 @@ object ParamFailure { } /** - * A `ParamFailure` is a `Failure` with an additional type-safe parameter that - * can allow an application to store other information related to the failure. + * A `ParamFailure` is a `[[Failure]]` with an additional type-safe parameter + * that can allow an application to store other information related to the + * failure. * * For example: * {{{ @@ -1027,9 +1034,9 @@ trait Boxable[T] { } /** - * Sometimes it's convenient to access either a Box[T] or a T. If you specify - * BoxOrRaw[T], the either a T or a Box[T] can be passed and the "right thing" - * will happen, including nulls being treated as `Empty`. + * Sometimes it's convenient to access either a `[[Box]][T]` or a `T`. If you + * specify `BoxOrRaw[T]`, either a `T` or a `Box[T]` can be passed and the + * "right thing" will happen, including `null`s being treated as `Empty`. */ sealed trait BoxOrRaw[T] { def box: Box[T] @@ -1055,12 +1062,12 @@ object BoxOrRaw { } /** - * The BoxOrRaw that represents a boxed value. + * The `[[BoxOrRaw]]` that represents a boxed value. */ final case class BoxedBoxOrRaw[T](box: Box[T]) extends BoxOrRaw[T] /** - * The BoxOrRaw that represents a raw value. + * The `[[BoxOrRaw]]` that represents a raw value. */ final case class RawBoxOrRaw[T](raw: T) extends BoxOrRaw[T] { def box: Box[T] = diff --git a/core/common/src/main/scala/net/liftweb/common/Conversions.scala b/core/common/src/main/scala/net/liftweb/common/Conversions.scala index 1097ad52a3..bf156e4fe3 100644 --- a/core/common/src/main/scala/net/liftweb/common/Conversions.scala +++ b/core/common/src/main/scala/net/liftweb/common/Conversions.scala @@ -28,7 +28,7 @@ import scala.xml._ /** * This trait is used to unify `String`s and `[[scala.xml.NodeSeq NodeSeq]]`s * into one type. It is used in conjuction with the implicit conversions defined - * in its companion object. + * in its [[StringOrNodeSeq$ companion object]]. */ sealed trait StringOrNodeSeq { def nodeSeq: scala.xml.NodeSeq @@ -67,8 +67,8 @@ object StringOrNodeSeq { /** * This trait is used to unify `()=>String` and `String` into one type. It is - * used in conjunction with the implicit conversions defined in its companion - * object. + * used in conjunction with the implicit conversions defined in its [[StringFunc$ + * companion object]]. */ sealed trait StringFunc { def func: () => String @@ -93,8 +93,14 @@ object StringFunc { RealStringFunc(() => f(func())) } +/** + * See `[[StringFunc]]`. + */ final case class RealStringFunc(func: () => String) extends StringFunc +/** + * See `[[StringFunc]]`. + */ final case class ConstStringFunc(str: String) extends StringFunc { lazy val func = () => str } @@ -105,8 +111,8 @@ final case class ConstStringFunc(str: String) extends StringFunc { * with the implicit conversions defined in its [[NodeSeqFunc$ companion * object]]. */ -@deprecated("""Lift now mostly uses NodeSeq=>NodeSeq transformations rather than -NodeSeq constants; consider doing the same.""","3.0") +@deprecated("""Lift now mostly uses `NodeSeq=>NodeSeq` transformations rather +than `NodeSeq` constants; consider doing the same.""","3.0") sealed trait NodeSeqFunc { def func: () => NodeSeq } @@ -117,19 +123,19 @@ sealed trait NodeSeqFunc { * the flexibility of a `()=>[[scala.xml.NodeSeq NodeSeq]]` without having to * write overloads for all methods that should accept both. */ -@deprecated("""Lift now mostly uses NodeSeq=>NodeSeq transformations rather than -NodeSeq constants; consider doing the same.""","3.0") +@deprecated("""Lift now mostly uses `NodeSeq=>NodeSeq` transformations rather +than `NodeSeq` constants; consider doing the same.""","3.0") object NodeSeqFunc { /** - * If you've got something that can be converted into a NodeSeq (a constant) - * but want a NodeSeqFunc, this implicit will do the conversion. + * If you've got something that can be converted into a `NodeSeq` (a constant) + * but want a `NodeSeqFunc`, this implicit will do the conversion. */ implicit def nsToNodeSeqFunc[T](ns: T)(implicit f: T => NodeSeq): NodeSeqFunc = ConstNodeSeqFunc(f(ns)) /** - * If you've got something that can be converted into a String Function - * but want a StringFunc, this implicit will do the conversion. + * If you've got something that can be converted into a `NodeSeq` function but + * want a `NodeSeqFunc`, this implicit will do the conversion. */ implicit def funcToNodeSeqFunc[T](func: () => T)(implicit f: T => NodeSeq): NodeSeqFunc = RealNodeSeqFunc(() => f(func())) @@ -138,15 +144,15 @@ object NodeSeqFunc { /** * The case class that holds a `[[scala.xml.NodeSeq NodeSeq]]` function. */ -@deprecated("""Lift now mostly uses NodeSeq=>NodeSeq transformations rather than -NodeSeq constants; consider doing the same.""","3.0") +@deprecated("""Lift now mostly uses `NodeSeq=>NodeSeq` transformations rather +than `NodeSeq` constants; consider doing the same.""","3.0") final case class RealNodeSeqFunc(func: () => NodeSeq) extends NodeSeqFunc /** * The case class that holds the `[[scala.xml.NodeSeq NodeSeq]]` constant. */ -@deprecated("""Lift now mostly uses NodeSeq=>NodeSeq transformations rather than -NodeSeq constants; consider doing the same.""","3.0") +@deprecated("""Lift now mostly uses `NodeSeq=>NodeSeq` transformations rather +than `NodeSeq` constants; consider doing the same.""","3.0") final case class ConstNodeSeqFunc(ns: NodeSeq) extends NodeSeqFunc { lazy val func = () => ns } diff --git a/core/common/src/main/scala/net/liftweb/common/LoanWrapper.scala b/core/common/src/main/scala/net/liftweb/common/LoanWrapper.scala index 7f5e421445..7388162659 100644 --- a/core/common/src/main/scala/net/liftweb/common/LoanWrapper.scala +++ b/core/common/src/main/scala/net/liftweb/common/LoanWrapper.scala @@ -29,8 +29,8 @@ package common */ trait CommonLoanWrapper { /** - * Implementations of this method may either call f to continue processing - * the wrapped call as normal, or may ignore f to entirely replace the + * Implementations of this method may either call `f` to continue processing + * the wrapped call as normal, or may ignore `f` to entirely replace the * wrapped call with a custom implementation. * * @param f the delegate which provides processing by the underlying framework @@ -40,8 +40,8 @@ trait CommonLoanWrapper { object CommonLoanWrapper { /** - * If you have a List of LoanWrappers, apply them and then the functions. For - * example: + * If you have a `List` of `LoanWrapper`s, apply them and then the + * functions. For example: * * {{{ * val firstWrapper = new TimerWrapper() From 2b1d749b0155635a07c5daf6d41090d0710defb2 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 21 Dec 2014 18:04:15 -0500 Subject: [PATCH 1086/1949] Bump to Scala 2.11.3. The specific motivation is that scaladoc in 2.11.3 fixes an issue with picking up indentation in code blocks properly. --- build.sbt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index b664165368..f907f03950 100644 --- a/build.sbt +++ b/build.sbt @@ -12,9 +12,9 @@ startYear in ThisBuild := Some(2006) organizationName in ThisBuild := "WorldWide Conferencing, LLC" -scalaVersion in ThisBuild := "2.11.2" +scalaVersion in ThisBuild := "2.11.3" -crossScalaVersions in ThisBuild := Seq("2.11.2") +crossScalaVersions in ThisBuild := Seq("2.11.3") libraryDependencies in ThisBuild <++= scalaVersion {sv => Seq(specs2, scalacheck, scalatest) } From a9fcf2d62961f7da6daa120ab435fbd006a20941 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 21 Dec 2014 18:07:10 -0500 Subject: [PATCH 1087/1949] Restore some implicit conversion scaladocs. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These are implicits that take implicits as parameters, and the docs make it a little clearer what that’s for. --- .../scala/net/liftweb/common/Conversions.scala | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/core/common/src/main/scala/net/liftweb/common/Conversions.scala b/core/common/src/main/scala/net/liftweb/common/Conversions.scala index bf156e4fe3..56240d7e0c 100644 --- a/core/common/src/main/scala/net/liftweb/common/Conversions.scala +++ b/core/common/src/main/scala/net/liftweb/common/Conversions.scala @@ -86,9 +86,23 @@ sealed trait StringFunc { * while the former is simpler to use. */ object StringFunc { + /** + * Implicit conversion from any type that in turn has an implicit conversion + * to a `String`, to a `StringFunc`. In particular, this means that if a given + * method takes a `StringFunc` as a parameter, it can accept either a `String` + * and any type that has an implicit conversion to `String` in scope. + */ implicit def strToStringFunc[T](str: T)(implicit f: T => String): StringFunc = ConstStringFunc(f(str)) + /** + * Implicit conversion from any function that produces a type that in turn has + * an implicit conversion to a `String`, to a `StringFunc`. In particular, + * this means that if a given method takes a `StringFunc` as a parameter, it + * can accept either a function that returns a `String` and a function that + * returns any other type that has an implicit conversion to `String` in + * scope. + */ implicit def funcToStringFunc[T](func: () => T)(implicit f: T => String): StringFunc = RealStringFunc(() => f(func())) } From 028830061af2d0f4d2464bda9c1b9ace4a5f78bc Mon Sep 17 00:00:00 2001 From: Joe Barnes Date: Mon, 22 Dec 2014 21:04:56 -0600 Subject: [PATCH 1088/1949] ContentParser is now a thing --- .../src/main/scala/net/liftweb/util/MarkdownParser.scala | 6 +++++- .../src/main/scala/net/liftweb/http/LiftRules.scala | 2 ++ .../src/main/scala/net/liftweb/http/Templates.scala | 8 ++++---- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/MarkdownParser.scala b/core/util/src/main/scala/net/liftweb/util/MarkdownParser.scala index 4320f7ee40..dfe61e935d 100644 --- a/core/util/src/main/scala/net/liftweb/util/MarkdownParser.scala +++ b/core/util/src/main/scala/net/liftweb/util/MarkdownParser.scala @@ -4,7 +4,11 @@ import xml.{Elem, NodeSeq} import net.liftweb.common.Box import net.liftweb.markdown.ThreadLocalTransformer -object MarkdownParser { +trait ContentParser { + def parse(in: String): Box[NodeSeq] +} + +object MarkdownParser extends ContentParser { lazy val matchMetadata = """(?m)\A(:?[ \t]*\n)?(?:-{3,}+\n)?(^([a-zA-Z0-9 _\-]+)[=:]([^\n]*)\n+(:?[ \t]*\n)?)+(:?-{3,}+\n)?""".r lazy val topMetadata = """(?m)^([^:]+):[ \t]*(.*)$""".r diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 6b4dc4083f..4b9071928b 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -1320,6 +1320,8 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { */ val snippets = RulesSeq[SnippetPF] + var contentParsers:Map[String, ContentParser] = Map("md" -> MarkdownParser) + /** * Execute certain functions early in a Stateful Request * This is called early in a stateful request (one that's not serviced by a stateless REST request and diff --git a/web/webkit/src/main/scala/net/liftweb/http/Templates.scala b/web/webkit/src/main/scala/net/liftweb/http/Templates.scala index d8e7a2e7fe..816ad64c3a 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Templates.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Templates.scala @@ -134,10 +134,10 @@ object Templates { }.headOption getOrElse in } - private def parseMarkdown(is: InputStream, needAutoSurround: Boolean): Box[NodeSeq] = + private def parseContent(is: InputStream, parser:ContentParser, needAutoSurround: Boolean): Box[NodeSeq] = for { bytes <- Helpers.tryo(Helpers.readWholeStream(is)) - elems <- MarkdownParser.parse(new String(bytes, "UTF-8")) + elems <- parser.parse(new String(bytes, "UTF-8")) } yield { if (needAutoSurround) {elems} @@ -210,8 +210,8 @@ object Templates { import scala.xml.dtd.ValidationException val xmlb = try { LiftRules.doWithResource(name) { is => - if (s == "md") {parseMarkdown(is, needAutoSurround)} else - parserFunction(is) } match { + LiftRules.contentParsers.get(s).map(parseContent(is, _, needAutoSurround)).getOrElse(parserFunction(is)) + } match { case Full(seq) => seq case _ => Empty } From 22d260ac7f268096d9d46a0766040a4d84232937 Mon Sep 17 00:00:00 2001 From: Joe Barnes Date: Mon, 22 Dec 2014 21:46:52 -0600 Subject: [PATCH 1089/1949] addContentParser is needed since we have to also update templateSuffixes --- .../src/main/scala/net/liftweb/http/LiftRules.scala | 9 +++++++-- .../src/main/scala/net/liftweb/http/Templates.scala | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 4b9071928b..5e8eaa420a 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -1320,7 +1320,12 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { */ val snippets = RulesSeq[SnippetPF] - var contentParsers:Map[String, ContentParser] = Map("md" -> MarkdownParser) + private var contentParsers:Map[String, ContentParser] = Map("md" -> MarkdownParser) + def addContentParser(suffix:String, parser:ContentParser) = { + templateSuffixes = templateSuffixes :+ suffix + contentParsers += suffix -> parser + } + def getContentParsers = contentParsers /** * Execute certain functions early in a Stateful Request @@ -1701,7 +1706,7 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { /** * The list of suffixes that Lift looks for in templates. - * By default, html, xhtml, and htm + * By default, html, xhtml, htm, and md */ @volatile var templateSuffixes: List[String] = List("html", "xhtml", "htm", "md") diff --git a/web/webkit/src/main/scala/net/liftweb/http/Templates.scala b/web/webkit/src/main/scala/net/liftweb/http/Templates.scala index 816ad64c3a..dc4f8cc486 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Templates.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Templates.scala @@ -210,7 +210,7 @@ object Templates { import scala.xml.dtd.ValidationException val xmlb = try { LiftRules.doWithResource(name) { is => - LiftRules.contentParsers.get(s).map(parseContent(is, _, needAutoSurround)).getOrElse(parserFunction(is)) + LiftRules.getContentParsers.get(s).map(parseContent(is, _, needAutoSurround)).getOrElse(parserFunction(is)) } match { case Full(seq) => seq case _ => Empty From e1d5adf1d2cf7100fe4104c82391f29a977990df Mon Sep 17 00:00:00 2001 From: Joe Barnes Date: Tue, 23 Dec 2014 06:36:46 -0600 Subject: [PATCH 1090/1949] Changed ContentParser to a mere function --- .../src/main/scala/net/liftweb/util/MarkdownParser.scala | 6 +----- web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala | 4 +++- web/webkit/src/main/scala/net/liftweb/http/Templates.scala | 4 ++-- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/MarkdownParser.scala b/core/util/src/main/scala/net/liftweb/util/MarkdownParser.scala index dfe61e935d..4320f7ee40 100644 --- a/core/util/src/main/scala/net/liftweb/util/MarkdownParser.scala +++ b/core/util/src/main/scala/net/liftweb/util/MarkdownParser.scala @@ -4,11 +4,7 @@ import xml.{Elem, NodeSeq} import net.liftweb.common.Box import net.liftweb.markdown.ThreadLocalTransformer -trait ContentParser { - def parse(in: String): Box[NodeSeq] -} - -object MarkdownParser extends ContentParser { +object MarkdownParser { lazy val matchMetadata = """(?m)\A(:?[ \t]*\n)?(?:-{3,}+\n)?(^([a-zA-Z0-9 _\-]+)[=:]([^\n]*)\n+(:?[ \t]*\n)?)+(:?-{3,}+\n)?""".r lazy val topMetadata = """(?m)^([^:]+):[ \t]*(.*)$""".r diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 5e8eaa420a..971ffca92e 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -166,6 +166,8 @@ object LiftRules extends LiftRulesMocker { */ type LiftRequestPF = PartialFunction[Req, Boolean] + type ContentParser = String => Box[NodeSeq] + /* private[this] var _doneBoot = false private[http] def doneBoot = _doneBoot @@ -1320,7 +1322,7 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { */ val snippets = RulesSeq[SnippetPF] - private var contentParsers:Map[String, ContentParser] = Map("md" -> MarkdownParser) + private var contentParsers:Map[String, ContentParser] = Map("md" -> MarkdownParser.parse) def addContentParser(suffix:String, parser:ContentParser) = { templateSuffixes = templateSuffixes :+ suffix contentParsers += suffix -> parser diff --git a/web/webkit/src/main/scala/net/liftweb/http/Templates.scala b/web/webkit/src/main/scala/net/liftweb/http/Templates.scala index dc4f8cc486..5b007a4e12 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Templates.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Templates.scala @@ -134,10 +134,10 @@ object Templates { }.headOption getOrElse in } - private def parseContent(is: InputStream, parser:ContentParser, needAutoSurround: Boolean): Box[NodeSeq] = + private def parseContent(is: InputStream, parser:LiftRules.ContentParser, needAutoSurround: Boolean): Box[NodeSeq] = for { bytes <- Helpers.tryo(Helpers.readWholeStream(is)) - elems <- parser.parse(new String(bytes, "UTF-8")) + elems <- parser(new String(bytes, "UTF-8")) } yield { if (needAutoSurround) {elems} From 83036ba31e34fbafa8bd83c58835aea8231133f7 Mon Sep 17 00:00:00 2001 From: Joe Barnes Date: Tue, 23 Dec 2014 15:48:55 -0600 Subject: [PATCH 1091/1949] Defined LiftRules.contentParsers and dontAutoSurround --- .../scala/net/liftweb/http/LiftRules.scala | 30 +++++++++++-------- .../scala/net/liftweb/http/Templates.scala | 29 ++++++------------ 2 files changed, 26 insertions(+), 33 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 971ffca92e..5452995e56 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -166,7 +166,15 @@ object LiftRules extends LiftRulesMocker { */ type LiftRequestPF = PartialFunction[Req, Boolean] - type ContentParser = String => Box[NodeSeq] + type ContentParser = InputStream => Box[NodeSeq] + + def toContentParser(f:String => Box[NodeSeq]):ContentParser = { + is:InputStream => + for { + bytes <- Helpers.tryo(Helpers.readWholeStream(is)) + elems <- f(new String(bytes, "UTF-8")) + } yield { elems } + } /* private[this] var _doneBoot = false @@ -1322,12 +1330,14 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { */ val snippets = RulesSeq[SnippetPF] - private var contentParsers:Map[String, ContentParser] = Map("md" -> MarkdownParser.parse) - def addContentParser(suffix:String, parser:ContentParser) = { - templateSuffixes = templateSuffixes :+ suffix - contentParsers += suffix -> parser - } - def getContentParsers = contentParsers + @volatile var contentParsers:Map[String, () => ContentParser] = Map( + "html" -> (() => S.htmlProperties.htmlParser), + "xhtml" -> (() => S.htmlProperties.htmlParser), + "htm" -> (() => S.htmlProperties.htmlParser), + "md" -> (() => toContentParser(MarkdownParser.parse)) + ) + + @volatile var dontAutoSurround = Seq("html", "xhtml", "htm") /** * Execute certain functions early in a Stateful Request @@ -1706,12 +1716,6 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { } - /** - * The list of suffixes that Lift looks for in templates. - * By default, html, xhtml, htm, and md - */ - @volatile var templateSuffixes: List[String] = List("html", "xhtml", "htm", "md") - /** * When a request is parsed into a Req object, certain suffixes are explicitly split from * the last part of the request URI. If the suffix is contained in this list, it is explicitly split. diff --git a/web/webkit/src/main/scala/net/liftweb/http/Templates.scala b/web/webkit/src/main/scala/net/liftweb/http/Templates.scala index 5b007a4e12..e799b37fec 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Templates.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Templates.scala @@ -28,7 +28,7 @@ import java.io.InputStream * Contains functions for obtaining templates */ object Templates { - private val suffixes = LiftRules.templateSuffixes + private val parsers = LiftRules.contentParsers private def checkForLiftView(part: List[String], last: String, what: LiftRules.ViewDispatchPF): Box[NodeSeq] = { if (what.isDefinedAt(part)) { @@ -134,17 +134,6 @@ object Templates { }.headOption getOrElse in } - private def parseContent(is: InputStream, parser:LiftRules.ContentParser, needAutoSurround: Boolean): Box[NodeSeq] = - for { - bytes <- Helpers.tryo(Helpers.readWholeStream(is)) - elems <- parser(new String(bytes, "UTF-8")) - } yield { - if (needAutoSurround) - {elems} - else - elems - } - /** * Given a list of paths (e.g. List("foo", "index")), * find the template. @@ -176,10 +165,6 @@ object Templates { val lrCache = LiftRules.templateCache val cache = if (lrCache.isDefined) lrCache.openOrThrowException("passes isDefined") else NoCache - val parserFunction: InputStream => Box[NodeSeq] = - S.htmlProperties.htmlParser - - val tr = cache.get(key) if (tr.isDefined) tr @@ -195,22 +180,26 @@ object Templates { case _ => val pls = places.mkString("/", "/", "") - val se = suffixes.iterator + val se = parsers.iterator val sl = List("_" + locale.toString, "_" + locale.getLanguage, "") var found = false var ret: NodeSeq = null while (!found && se.hasNext) { - val s = se.next + val (suffix, parserFactory) = se.next + val parse = parserFactory() val le = sl.iterator while (!found && le.hasNext) { val p = le.next - val name = pls + p + (if (s.length > 0) "." + s else "") + val name = pls + p + (if (suffix.length > 0) "." + suffix else "") import scala.xml.dtd.ValidationException val xmlb = try { LiftRules.doWithResource(name) { is => - LiftRules.getContentParsers.get(s).map(parseContent(is, _, needAutoSurround)).getOrElse(parserFunction(is)) + parse(is).map { elems => + if (!needAutoSurround || LiftRules.dontAutoSurround.contains(suffix)) elems + else {elems} + } } match { case Full(seq) => seq case _ => Empty From dd822a48cfe6f30c2c668c6d02b55367a2ac3e20 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Tue, 23 Dec 2014 21:19:29 -0500 Subject: [PATCH 1092/1949] Bump to Scala 2.11.4 because Scala. Evidently 2.11.3 had binary compatibility issues, so 2.11.4 it is! --- build.sbt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index f907f03950..a8f90023b9 100644 --- a/build.sbt +++ b/build.sbt @@ -12,9 +12,9 @@ startYear in ThisBuild := Some(2006) organizationName in ThisBuild := "WorldWide Conferencing, LLC" -scalaVersion in ThisBuild := "2.11.3" +scalaVersion in ThisBuild := "2.11.4" -crossScalaVersions in ThisBuild := Seq("2.11.3") +crossScalaVersions in ThisBuild := Seq("2.11.4") libraryDependencies in ThisBuild <++= scalaVersion {sv => Seq(specs2, scalacheck, scalatest) } From cc705610baa8f37854c0decf131fc38ea7a8c8b1 Mon Sep 17 00:00:00 2001 From: Joe Barnes Date: Tue, 23 Dec 2014 21:16:55 -0600 Subject: [PATCH 1093/1949] This version of unified parsers incorporates some good community feedback --- .../net/liftweb/http/ContentParser.scala | 82 +++++++++++++++++++ .../scala/net/liftweb/http/LiftRules.scala | 27 +++--- .../scala/net/liftweb/http/Templates.scala | 19 ++--- 3 files changed, 101 insertions(+), 27 deletions(-) create mode 100644 web/webkit/src/main/scala/net/liftweb/http/ContentParser.scala diff --git a/web/webkit/src/main/scala/net/liftweb/http/ContentParser.scala b/web/webkit/src/main/scala/net/liftweb/http/ContentParser.scala new file mode 100644 index 0000000000..d0ffcaf320 --- /dev/null +++ b/web/webkit/src/main/scala/net/liftweb/http/ContentParser.scala @@ -0,0 +1,82 @@ +/* + * Copyright 2007-2015 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 java.io.InputStream + +import net.liftweb.common.Box +import net.liftweb.util.Helpers + +import scala.xml.NodeSeq + +/** Objects which can parse content should implement this trait. See LiftRules.contentParsers */ +trait ContentParser { + /** The template filename suffixes this parser is for, e.g. "html", "md", "adoc", etc. */ + def templateSuffixes: Seq[String] + + /** Given an InputStream to a resource with a matching suffix, this function should provide the corresponding + * NodeSeq ready to serve + * @return + */ + def parse(content:InputStream): Box[NodeSeq] + + /** Called to auto-surround the content when needed, i.e. when this content is a top-level template */ + def surround(content:NodeSeq): NodeSeq +} + +object ContentParser { + /** Convenience function to convert a simple String => Box[NodeSeq] content parser function into a + * InputStream => Box[NodeSeq] function for satisfying the ContentParser contract. + * @param f your String => Box[NodeSeq] content parser + * @return your parser wrapped up to handle an InputStream + */ + def toInputStreamParser(f:String => Box[NodeSeq]):InputStream => Box[NodeSeq] = { + is:InputStream => + for { + bytes <- Helpers.tryo(Helpers.readWholeStream(is)) + elems <- f(new String(bytes, "UTF-8")) + } yield { elems } + } + + val defaultSurround:NodeSeq => NodeSeq = + { elems => {elems} } + + /** A basic ContentParser which handles one template filename suffix, operates on a string, and surrounds the + * top-level templates with the default surround + */ + def basic(templateSuffix:String, + parseF:String => Box[NodeSeq], + surroundF:NodeSeq => NodeSeq = defaultSurround):ContentParser = + new ContentParser { + override def templateSuffixes: Seq[String] = Seq(templateSuffix) + override def parse(content:InputStream): Box[NodeSeq] = toInputStreamParser(parseF)(content) + override def surround(content:NodeSeq):NodeSeq = surroundF(content) + } + + /** A full ContentParser which handles multiple filename suffixes, operates on an InputStream, and surrounds the + * top-level templates with the default surround + */ + def full(templateSuffixesSeq:Seq[String], + parseF:InputStream => Box[NodeSeq], + surroundF:NodeSeq => NodeSeq = defaultSurround):ContentParser = + new ContentParser { + override def templateSuffixes: Seq[String] = templateSuffixesSeq + override def parse(content:InputStream): Box[NodeSeq] = parseF(content) + override def surround(content:NodeSeq):NodeSeq = surroundF(content) + } +} \ No newline at end of file diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 5452995e56..b8cb58f969 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -166,16 +166,6 @@ object LiftRules extends LiftRulesMocker { */ type LiftRequestPF = PartialFunction[Req, Boolean] - type ContentParser = InputStream => Box[NodeSeq] - - def toContentParser(f:String => Box[NodeSeq]):ContentParser = { - is:InputStream => - for { - bytes <- Helpers.tryo(Helpers.readWholeStream(is)) - elems <- f(new String(bytes, "UTF-8")) - } yield { elems } - } - /* private[this] var _doneBoot = false private[http] def doneBoot = _doneBoot @@ -1330,15 +1320,18 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { */ val snippets = RulesSeq[SnippetPF] - @volatile var contentParsers:Map[String, () => ContentParser] = Map( - "html" -> (() => S.htmlProperties.htmlParser), - "xhtml" -> (() => S.htmlProperties.htmlParser), - "htm" -> (() => S.htmlProperties.htmlParser), - "md" -> (() => toContentParser(MarkdownParser.parse)) + /** + * Handles the parsing of template content into NodeSeqs. + */ + @volatile var contentParsers:Seq[ContentParser] = Seq( + new ContentParser { + override def templateSuffixes = Seq("html", "xhtml", "htm") + override def parse(content:InputStream): Box[NodeSeq] = S.htmlProperties.htmlParser(content) + override def surround(content:NodeSeq):NodeSeq = content + }, + ContentParser.basic("md", MarkdownParser.parse) ) - @volatile var dontAutoSurround = Seq("html", "xhtml", "htm") - /** * Execute certain functions early in a Stateful Request * This is called early in a stateful request (one that's not serviced by a stateless REST request and diff --git a/web/webkit/src/main/scala/net/liftweb/http/Templates.scala b/web/webkit/src/main/scala/net/liftweb/http/Templates.scala index e799b37fec..db1d016ea6 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Templates.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Templates.scala @@ -28,7 +28,10 @@ import java.io.InputStream * Contains functions for obtaining templates */ object Templates { - private val parsers = LiftRules.contentParsers + // Making this lazy to ensure it doesn't accidentally init before Boot completes in case someone touches this class. + private lazy val parsers = LiftRules.contentParsers.flatMap(parser => + parser.templateSuffixes.map( _ -> parser ) + ).toMap private def checkForLiftView(part: List[String], last: String, what: LiftRules.ViewDispatchPF): Box[NodeSeq] = { if (what.isDefinedAt(part)) { @@ -187,20 +190,14 @@ object Templates { var ret: NodeSeq = null while (!found && se.hasNext) { - val (suffix, parserFactory) = se.next - val parse = parserFactory() + val (suffix, parser) = se.next val le = sl.iterator while (!found && le.hasNext) { val p = le.next val name = pls + p + (if (suffix.length > 0) "." + suffix else "") import scala.xml.dtd.ValidationException val xmlb = try { - LiftRules.doWithResource(name) { is => - parse(is).map { elems => - if (!needAutoSurround || LiftRules.dontAutoSurround.contains(suffix)) elems - else {elems} - } - } match { + LiftRules.doWithResource(name) { parser.parse } match { case Full(seq) => seq case _ => Empty } @@ -217,7 +214,9 @@ object Templates { } if (xmlb.isDefined) { found = true - ret = (cache(key) = xmlb.openOrThrowException("passes isDefined")) + val rawElems = xmlb.openOrThrowException("passes isDefined") + val possiblySurrounded = if(needAutoSurround) parser.surround(rawElems) else rawElems + ret = (cache(key) = possiblySurrounded) } else if (xmlb.isInstanceOf[Failure] && (Props.devMode | Props.testMode)) { val msg = xmlb.asInstanceOf[Failure].msg From 831063065c2a6a7ed8be6165ab720819ce496252 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 3 Jan 2015 11:34:42 -0500 Subject: [PATCH 1094/1949] Clarify that all PR criteria must be met in contribution guidelines. --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 59f3c96c79..b7a1cb49a0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,7 +17,7 @@ The policy on issues is: available to a broader audience. We will accept pull requests into the Lift codebase if the pull requests meet -the following criteria: +all of the following criteria: * The request handles an issue that has been discussed on the Lift mailing list and whose solution has been requested (and in general adheres to the spirit of the issue guidelines above). From d57119b117d8a8b00017d9a4d445ee7b2737190e Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 21 Dec 2014 18:10:01 -0500 Subject: [PATCH 1095/1949] Take a stab at clarifying the FuncJBridge class comment. --- .../src/main/scala/net/liftweb/common/FuncJBridge.scala | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/common/src/main/scala/net/liftweb/common/FuncJBridge.scala b/core/common/src/main/scala/net/liftweb/common/FuncJBridge.scala index 975af6d082..4943b59c32 100644 --- a/core/common/src/main/scala/net/liftweb/common/FuncJBridge.scala +++ b/core/common/src/main/scala/net/liftweb/common/FuncJBridge.scala @@ -27,6 +27,11 @@ object FuncJBridge extends FuncJBridge * The implicits defined here allow Scala code to interact seamlessly between * the Java function-like interfaces and the Scala function interfaces for * various function arities. + * + * In particular, there is a pair of implicits for each arity of function from 0 + * to 4. There is one implicit (called `lift`) from the Java function type to + * the corresponding Scala function type and one (called `drop`) from the Scala + * function type to the corresponding Java function type. */ class FuncJBridge { implicit def lift[Z](f: Func0[Z]): Function0[Z] = new Function0[Z] { From e42fbed50c00deeebfd2cfc09f91f6ecb51a75a7 Mon Sep 17 00:00:00 2001 From: Joe Barnes Date: Sun, 4 Jan 2015 12:06:25 -0600 Subject: [PATCH 1096/1949] Incorporating feedback regarding style, naming, scaladocs, etc --- .../net/liftweb/http/ContentParser.scala | 84 ++++++++++++------- .../scala/net/liftweb/http/LiftRules.scala | 12 +-- .../scala/net/liftweb/http/Templates.scala | 2 +- 3 files changed, 59 insertions(+), 39 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/ContentParser.scala b/web/webkit/src/main/scala/net/liftweb/http/ContentParser.scala index d0ffcaf320..10258366a4 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/ContentParser.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/ContentParser.scala @@ -24,59 +24,79 @@ import net.liftweb.util.Helpers import scala.xml.NodeSeq -/** Objects which can parse content should implement this trait. See LiftRules.contentParsers */ +/** + * Objects which can parse content should implement this trait. See [[LiftRules.contentParsers]] + */ trait ContentParser { - /** The template filename suffixes this parser is for, e.g. "html", "md", "adoc", etc. */ + /** + * The template filename suffixes this parser is for, e.g. "html", "md", "adoc", etc. + */ def templateSuffixes: Seq[String] - /** Given an InputStream to a resource with a matching suffix, this function should provide the corresponding - * NodeSeq ready to serve - * @return - */ + /** + * Given an InputStream to a resource with a matching suffix, this function should provide the corresponding + * NodeSeq ready to serve + * @return + */ def parse(content:InputStream): Box[NodeSeq] - /** Called to auto-surround the content when needed, i.e. when this content is a top-level template */ + /** + * Called to auto-surround the content when needed, i.e. when this content is a top-level template + */ def surround(content:NodeSeq): NodeSeq } object ContentParser { - /** Convenience function to convert a simple String => Box[NodeSeq] content parser function into a - * InputStream => Box[NodeSeq] function for satisfying the ContentParser contract. - * @param f your String => Box[NodeSeq] content parser - * @return your parser wrapped up to handle an InputStream - */ - def toInputStreamParser(f:String => Box[NodeSeq]):InputStream => Box[NodeSeq] = { + /** + * Convenience function to convert a simple `String => Box[NodeSeq]` content parser function into a + * `InputStream => Box[NodeSeq]` function for satisfying the `ContentParser` contract. + * @param simpleParser your `String => Box[NodeSeq]` content parser + * @return your parser wrapped up to handle an `InputStream` + */ + def toInputStreamParser(simpleParser: String=>Box[NodeSeq]): InputStream=>Box[NodeSeq] = { is:InputStream => for { bytes <- Helpers.tryo(Helpers.readWholeStream(is)) - elems <- f(new String(bytes, "UTF-8")) - } yield { elems } + elems <- simpleParser(new String(bytes, "UTF-8")) + } yield { + elems + } } - val defaultSurround:NodeSeq => NodeSeq = + /** + * Default surround function used by `ContentParser.basic` and the built-it markdown parser which results in the + * template being surrounded by `default.html` with the content located at `id=content`. + */ + val defaultSurround: NodeSeq=>NodeSeq = { elems => {elems} } - /** A basic ContentParser which handles one template filename suffix, operates on a string, and surrounds the - * top-level templates with the default surround - */ - def basic(templateSuffix:String, - parseF:String => Box[NodeSeq], - surroundF:NodeSeq => NodeSeq = defaultSurround):ContentParser = + /** + * A basic `ContentParser` which handles one template filename suffix, operates on a string, and surrounds the + * top-level templates with the default surround. + * @param templateSuffix the template filename suffix for which this parser will be utilized. + * @param parseFunction the parse function for converting the template as a string into a `NodeSeq`. + * @param surroundFunction the function for surrounding the content returned by the `parseFunction`. + * See [[defaultSurround]]. + */ + def basic(templateSuffix: String, + parseFunction: String=>Box[NodeSeq], + surroundFunction: NodeSeq=>NodeSeq = defaultSurround):ContentParser = new ContentParser { override def templateSuffixes: Seq[String] = Seq(templateSuffix) - override def parse(content:InputStream): Box[NodeSeq] = toInputStreamParser(parseF)(content) - override def surround(content:NodeSeq):NodeSeq = surroundF(content) + override def parse(content: InputStream): Box[NodeSeq] = toInputStreamParser(parseFunction)(content) + override def surround(content: NodeSeq): NodeSeq = surroundFunction(content) } - /** A full ContentParser which handles multiple filename suffixes, operates on an InputStream, and surrounds the - * top-level templates with the default surround - */ - def full(templateSuffixesSeq:Seq[String], - parseF:InputStream => Box[NodeSeq], - surroundF:NodeSeq => NodeSeq = defaultSurround):ContentParser = + /** + * A full `ContentParser` which handles multiple filename suffixes, operates on an `InputStream`, and surrounds the + * top-level templates with the default surround + */ + def full(templateSuffixesSeq: Seq[String], + parseF: InputStream=>Box[NodeSeq], + surroundF: NodeSeq=>NodeSeq = defaultSurround):ContentParser = new ContentParser { override def templateSuffixes: Seq[String] = templateSuffixesSeq - override def parse(content:InputStream): Box[NodeSeq] = parseF(content) - override def surround(content:NodeSeq):NodeSeq = surroundF(content) + override def parse(content: InputStream): Box[NodeSeq] = parseF(content) + override def surround(content: NodeSeq): NodeSeq = surroundF(content) } } \ No newline at end of file diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index b8cb58f969..9f9b27c1f8 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -1323,12 +1323,12 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { /** * Handles the parsing of template content into NodeSeqs. */ - @volatile var contentParsers:Seq[ContentParser] = Seq( - new ContentParser { - override def templateSuffixes = Seq("html", "xhtml", "htm") - override def parse(content:InputStream): Box[NodeSeq] = S.htmlProperties.htmlParser(content) - override def surround(content:NodeSeq):NodeSeq = content - }, + @volatile var contentParsers: Seq[ContentParser] = Seq( + ContentParser.full( + Seq("html", "xhtml", "htm"), + S.htmlProperties.htmlParser, + content => content // These templates are not surrounded by default + ), ContentParser.basic("md", MarkdownParser.parse) ) diff --git a/web/webkit/src/main/scala/net/liftweb/http/Templates.scala b/web/webkit/src/main/scala/net/liftweb/http/Templates.scala index db1d016ea6..2eda6e91d1 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Templates.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Templates.scala @@ -197,7 +197,7 @@ object Templates { val name = pls + p + (if (suffix.length > 0) "." + suffix else "") import scala.xml.dtd.ValidationException val xmlb = try { - LiftRules.doWithResource(name) { parser.parse } match { + LiftRules.doWithResource(name)(parser.parse) match { case Full(seq) => seq case _ => Empty } From 74ce47c97546c73a16c739c0c1bc51e1b92ffc72 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 4 Jan 2015 19:59:19 -0500 Subject: [PATCH 1097/1949] Fix two issues with LiftMerge id extraction. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When we were detecting the id for use by event handlers, we were doing two things wrong: - We were failing to pass on any existing event attributes that we’d extracted along the way, so if there were any they were lost. - We were failing to append the fixed attributes to the current attribute, so we were losing the id attribute altogether. This meant that if an element had an id, we always lost that id in the process of trying to reuse it for event attributes. We also only kept the event attributes for attributes before the id, as the ones after were lost while we processed the id. --- web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala index 49fca842f2..2e0bacb26b 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala @@ -138,9 +138,9 @@ private[http] trait LiftMerge { fixAttrs(original, toFix, attrs.next, fixURL, EventAttribute(u.key.substring(2), u.value.text) :: eventAttributes) case u: UnprefixedAttribute if u.key == "id" => - val (_, remainingAttributes, updatedEventAttributes) = fixAttrs(original, toFix, attrs.next, fixURL) + val (_, fixedAttributes, updatedEventAttributes) = fixAttrs(original, toFix, attrs.next, fixURL, eventAttributes) - (Option(u.value.text).filter(_.nonEmpty), remainingAttributes, updatedEventAttributes) + (Option(u.value.text).filter(_.nonEmpty), attrs.copy(fixedAttributes), updatedEventAttributes) case _ => val (id, remainingAttributes, updatedEventAttributes) = fixAttrs(original, toFix, attrs.next, fixURL, eventAttributes) From af6587324d350d6838e7545b88b8f00435edebfe Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 4 Jan 2015 20:00:53 -0500 Subject: [PATCH 1098/1949] Support extracting javascript: actions and hrefs. javascript: and javascript:// are used in a couple of places in Lift, and can be used in client code. We look for these in action and href elements and extract them into proper event handlers with return false; appended (since return false is implied in these situations). We also clarify some variable names and comments along the way. --- .../scala/net/liftweb/http/LiftMerge.scala | 62 +++++++++++++++++-- 1 file changed, 56 insertions(+), 6 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala index 2e0bacb26b..86f448a4d6 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala @@ -33,6 +33,25 @@ import Helpers._ * the JS that should run when that event is triggered as a String. */ private case class EventAttribute(eventName: String, jsString: String) +private object EventAttribute { + /** + * A map from attribute name to event name for attributes that support being + * set to javascript:(//)-style values in order to invoke JS. For example, you + * can (and Lift does) set a form's action attribute to javascript://(some JS) + * instead of setting onsubmit to (someJS); return false. + */ + val eventsByAttributeName = + Map( + "action" -> "submit", + "href" -> "click" + ) + + object EventForAttribute { + def unapply(attributeName: String): Option[String] = { + eventsByAttributeName.get(attributeName) + } + } +} private[http] trait LiftMerge { self: LiftSession => @@ -125,14 +144,43 @@ private[http] trait LiftMerge { val contextPath: String = S.contextPath // Fix URLs using Req.fixHref and extract JS event attributes for putting - // into page JS. + // into page JS. Returns a triple of: + // - The optional id that was found in this set of attributes. + // - The fixed metadata. + // - A list of extracted `EventAttribute`s. def fixAttrs(original: MetaData, toFix: String, attrs: MetaData, fixURL: Boolean, eventAttributes: List[EventAttribute] = Nil): (Option[String], MetaData, List[EventAttribute]) = { attrs match { case Null => (None, Null, eventAttributes) + + case attribute @ UnprefixedAttribute( + EventAttribute.EventForAttribute(eventName), + attributeValue, + remainingAttributes + ) if attributeValue.text.startsWith("javascript:") => + val attributeJavaScript = { + // Could be javascript: or javascript://. + val base = attributeValue.text.substring(11) + val strippedJs = + if (base.startsWith("//")) + base.substring(2) + else + base + + if (strippedJs.trim.isEmpty) { + Nil + } else { + // When using javascript:-style URIs, return false is implied. + List(strippedJs + "; return false;") + } + } + + val updatedEventAttributes = attributeJavaScript.map(EventAttribute(eventName, _)) ::: eventAttributes + fixAttrs(original, toFix, remainingAttributes, fixURL, updatedEventAttributes) + case u: UnprefixedAttribute if u.key == toFix => - val (id, remainingAttributes, updatedEventAttributes) = fixAttrs(original, toFix, attrs.next, fixURL) + val (id, fixedAttributes, updatedEventAttributes) = fixAttrs(original, toFix, attrs.next, fixURL) - (id, new UnprefixedAttribute(toFix, fixHref(contextPath, attrs.value, fixURL, rewrite), remainingAttributes), updatedEventAttributes) + (id, new UnprefixedAttribute(toFix, fixHref(contextPath, attrs.value, fixURL, rewrite), fixedAttributes), updatedEventAttributes) case u: UnprefixedAttribute if u.key.startsWith("on") => fixAttrs(original, toFix, attrs.next, fixURL, EventAttribute(u.key.substring(2), u.value.text) :: eventAttributes) @@ -143,15 +191,17 @@ private[http] trait LiftMerge { (Option(u.value.text).filter(_.nonEmpty), attrs.copy(fixedAttributes), updatedEventAttributes) case _ => - val (id, remainingAttributes, updatedEventAttributes) = fixAttrs(original, toFix, attrs.next, fixURL, eventAttributes) + val (id, fixedAttributes, updatedEventAttributes) = fixAttrs(original, toFix, attrs.next, fixURL, eventAttributes) - (id, attrs.copy(remainingAttributes), updatedEventAttributes) + (id, attrs.copy(fixedAttributes), updatedEventAttributes) } } // Fix the element's `attributeToFix` using `fixAttrs` and extract JS event // attributes for putting into page JS. Return a fixed version of this - // element with fixed children. + // element with fixed children. Adds a lift-generated id if the given + // element needs to have event handlers attached but doesn't already have an + // id. def fixElementAndAttributes(element: Elem, attributeToFix: String, fixURL: Boolean, fixedChildren: NodeSeq) = { val (id, fixedAttributes, eventAttributes) = fixAttrs(element.attributes, attributeToFix, element.attributes, fixURL) From 722c0935d9ac62036c3d43ca00738ae6420b5dbe Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 4 Jan 2015 20:08:38 -0500 Subject: [PATCH 1099/1949] Support using event in extracted handlers. The wrapping anonymous functions now take an event parameters (as is ideal). --- web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala index 86f448a4d6..b5d810c901 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala @@ -67,7 +67,7 @@ private[http] trait LiftMerge { (elementId, eventAttributes) <- eventAttributesByElementId EventAttribute(eventName, jsString) <- eventAttributes } yield { - Call("lift.onEvent", elementId, eventName, AnonFunc(JsRaw(jsString).cmd)).cmd + Call("lift.onEvent", elementId, eventName, AnonFunc("event", JsRaw(jsString).cmd)).cmd } val allJs = From 0b7a827c44417a85eac1535b690aeb1082d09935 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 4 Jan 2015 20:09:08 -0500 Subject: [PATCH 1100/1949] preventDefault on javascript: hrefs. This instead of return false, which can also interfere with the event bubble. --- web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala index b5d810c901..bda758f482 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala @@ -170,7 +170,7 @@ private[http] trait LiftMerge { Nil } else { // When using javascript:-style URIs, return false is implied. - List(strippedJs + "; return false;") + List(strippedJs + "; event.preventDefault();") } } From 25c1b250491cbb7af71ad5f0bbff5a6e130129b1 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 5 Jan 2015 17:52:01 -0500 Subject: [PATCH 1101/1949] Make HLists covariant in their type parameters. Also add a spec for HList length. --- .../main/scala/net/liftweb/common/HList.scala | 66 +++++++------------ .../scala/net/liftweb/common/HListSpec.scala | 10 +++ 2 files changed, 34 insertions(+), 42 deletions(-) diff --git a/core/common/src/main/scala/net/liftweb/common/HList.scala b/core/common/src/main/scala/net/liftweb/common/HList.scala index b7775305b7..90a094264f 100644 --- a/core/common/src/main/scala/net/liftweb/common/HList.scala +++ b/core/common/src/main/scala/net/liftweb/common/HList.scala @@ -69,19 +69,11 @@ object HLists { * } * }}} */ - sealed trait HList { - type Head - type Tail <: HList - - /** - * The length of the HList - */ - def length: Int - } + sealed trait HList /** * The last element of an `HList`. This is the starting point for an `HList`, - * and carries a `:+:` method to start one: + * and you can use `[[HListMethods.:+: :+:]]` to start one based on it: * * {{{ * scala> Type1("Value") :+: HNil @@ -89,23 +81,11 @@ object HLists { * }}} */ final class HNil extends HList { - type Head = Nothing - type Tail = HNil - - /** - * Chains the given value to the front of an `HList`. - * - * Produces a `T :+: HNil`. - */ - def :+:[T](v: T) = HCons(v, this) - override def toString = "HNil" - - def length = 0 } /** - * The HNil singleton + * The HNil singleton. */ val HNil = new HNil() @@ -116,7 +96,7 @@ object HLists { * Carries the information about the type of this element, plus the `HList` * type of the rest of the list. * - * You can use `:+:` to make this `HList` longer: + * You can use `[[HListMethods.:+: :+:]]` to make this `HList` longer: * * {{{ * scala> val first = Type1("Value") :+: HNil @@ -128,30 +108,32 @@ object HLists { * Type2(Other Value) :+: Type1(Value) :+: HNil * }}} */ - final case class HCons[H, T <: HList](head: H, tail: T) extends HList { - type This = HCons[H, T] - type Head = H - type Tail = T - - /** - * Chains the given value to the front of this `HList`. - */ - def :+:[T](v: T) = HCons(v, this) - + final case class :+:[+H, +T <: HList](head: H, tail: T) extends HList { override def toString = head + " :+: " + tail - - def length = 1 + tail.length } - type :+:[H, T <: HList] = HCons[H, T] - /** - * Provides the support needed to be able to pattern-match an `HList`. + * Provides the methods that can be used on an `HList`. These are set apart + * here due to certain issues we can experience otherwise with the type variance + * on the `:+:` class. */ - object :+: { - def unapply[H, T <: HList](in: HCons[H, T]): Option[(H, T)] = Some(in.head, in.tail) - } + implicit final class HListMethods[ListSoFar <: HList](hlist: ListSoFar) extends AnyRef { + def :+:[T](v: T): :+:[T, ListSoFar] = { + HLists.:+:(v, hlist) + } + /** + * The length of this HList; note that this is O(n) in the list of elements. + */ + def length: Int = { + hlist match { + case HNil => + 0 + case head :+: rest => + 1 + rest.length + } + } + } } /** diff --git a/core/common/src/test/scala/net/liftweb/common/HListSpec.scala b/core/common/src/test/scala/net/liftweb/common/HListSpec.scala index dc2676328e..dae1638cb7 100644 --- a/core/common/src/test/scala/net/liftweb/common/HListSpec.scala +++ b/core/common/src/test/scala/net/liftweb/common/HListSpec.scala @@ -38,6 +38,16 @@ object HListSpec extends Specification { x.head must_== 1 x.tail.head must_== "Foo" } + + "properly report its length" in { + import HLists._ + + val x = 1 :+: "Foo" :+: HNil + + HNil.length must_== 0 + x.length must_== 2 + ("Bam" :+: x).length must_== 3 + } } "A combinable box" should { From b994663712b0af141fa88fe081b621f7f804a292 Mon Sep 17 00:00:00 2001 From: Arek Burdach Date: Sun, 4 Jan 2015 17:32:51 +0100 Subject: [PATCH 1102/1949] Fixes LAFuture.collect/collectAll behaviour for empty list parameter For empty list parameter, were returned future which never been satisfied. Now is returned future that is immediately satisfied by empty list. --- .../scala/net/liftweb/actor/LAFuture.scala | 82 ++++++++++--------- .../net/liftweb/actor/LAFutureSpec.scala | 37 +++++++++ 2 files changed, 82 insertions(+), 37 deletions(-) create mode 100644 core/actor/src/test/scala/net/liftweb/actor/LAFutureSpec.scala diff --git a/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala b/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala index 397357dfb5..33b4a4c43a 100644 --- a/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala +++ b/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala @@ -330,25 +330,29 @@ object LAFuture { * collected futures are satisfied */ def collect[T](future: LAFuture[T]*): LAFuture[List[T]] = { - val sync = new Object - val len = future.length - val vals = new collection.mutable.ArrayBuffer[Box[T]](len) - // pad array so inserts at random places are possible - for (i <- 0 to len) { vals.insert(i, Empty) } - var gotCnt = 0 val ret = new LAFuture[List[T]] - - future.toList.zipWithIndex.foreach { - case (f, idx) => - f.foreach { - v => sync.synchronized { - vals.insert(idx, Full(v)) - gotCnt += 1 - if (gotCnt >= len) { - ret.satisfy(vals.toList.flatten) + if (future.isEmpty) { + ret.satisfy(Nil) + } else { + val sync = new Object + val len = future.length + val vals = new collection.mutable.ArrayBuffer[Box[T]](len) + // pad array so inserts at random places are possible + for (i <- 0 to len) { vals.insert(i, Empty) } + var gotCnt = 0 + + future.toList.zipWithIndex.foreach { + case (f, idx) => + f.foreach { + v => sync.synchronized { + vals.insert(idx, Full(v)) + gotCnt += 1 + if (gotCnt >= len) { + ret.satisfy(vals.toList.flatten) + } } } - } + } } ret @@ -362,33 +366,37 @@ object LAFuture { * returned future with an Empty */ def collectAll[T](future: LAFuture[Box[T]]*): LAFuture[Box[List[T]]] = { - val sync = new Object - val len = future.length - val vals = new collection.mutable.ArrayBuffer[Box[T]](len) - // pad array so inserts at random places are possible - for (i <- 0 to len) { vals.insert(i, Empty) } - var gotCnt = 0 val ret = new LAFuture[Box[List[T]]] + if (future.isEmpty) { + ret.satisfy(Full(Nil)) + } else { + val sync = new Object + val len = future.length + val vals = new collection.mutable.ArrayBuffer[Box[T]](len) + // pad array so inserts at random places are possible + for (i <- 0 to len) { vals.insert(i, Empty) } + var gotCnt = 0 + + future.toList.zipWithIndex.foreach { + case (f, idx) => + f.foreach { + vb => sync.synchronized { + vb match { + case Full(v) => { + vals.insert(idx, Full(v)) + gotCnt += 1 + if (gotCnt >= len) { + ret.satisfy(Full(vals.toList.flatten)) + } + } - future.toList.zipWithIndex.foreach { - case (f, idx) => - f.foreach { - vb => sync.synchronized { - vb match { - case Full(v) => { - vals.insert(idx, Full(v)) - gotCnt += 1 - if (gotCnt >= len) { - ret.satisfy(Full(vals.toList.flatten)) + case eb: EmptyBox => { + ret.satisfy(eb) } } - - case eb: EmptyBox => { - ret.satisfy(eb) - } } } - } + } } ret diff --git a/core/actor/src/test/scala/net/liftweb/actor/LAFutureSpec.scala b/core/actor/src/test/scala/net/liftweb/actor/LAFutureSpec.scala new file mode 100644 index 0000000000..ed5cc5e62d --- /dev/null +++ b/core/actor/src/test/scala/net/liftweb/actor/LAFutureSpec.scala @@ -0,0 +1,37 @@ +package net.liftweb.actor + +import org.specs2.mutable.Specification + +class LAFutureSpec extends Specification { + val timeout = 1000L + + "LAFuture" should { + + "collect one future result" in { + val givenOneResult = 123 + val one = LAFuture(() => givenOneResult) + LAFuture.collect(one).get(timeout) shouldEqual List(givenOneResult) + } + + "collect more future results in correct order" in { + val givenOneResult = 123 + val givenTwoResult = 234 + val one = LAFuture(() => givenOneResult) + val two = LAFuture(() => givenTwoResult) + LAFuture.collect(one, two).get shouldEqual List(givenOneResult, givenTwoResult) + } + + "collect empty list immediately" in { + val collectResult = LAFuture.collect(Nil: _*) + collectResult.isSatisfied shouldEqual true + collectResult.get shouldEqual Nil + } + + "collectAll empty list immediately" in { + val collectResult = LAFuture.collectAll(Nil : _*) + collectResult.isSatisfied shouldEqual true + collectResult.get shouldEqual Nil + } + } + +} From 98d8e65723fa5dd338b26f1c036201a4b36c9680 Mon Sep 17 00:00:00 2001 From: Arek Burdach Date: Wed, 7 Jan 2015 03:57:55 +0100 Subject: [PATCH 1103/1949] contributors.md update --- contributors.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/contributors.md b/contributors.md index 0760d36338..50930e40f9 100644 --- a/contributors.md +++ b/contributors.md @@ -221,3 +221,9 @@ Aleksey Izmailov ### Email: ### izmailoff at gmail dot com + +### Name: ### +Arek Burdach + +### Email: ### +arek.burdach at gmail dot com From dfea5705d3dedec475f436380afdd83af49735e3 Mon Sep 17 00:00:00 2001 From: Arek Burdach Date: Sun, 4 Jan 2015 20:44:07 +0100 Subject: [PATCH 1104/1949] Fix CombParserHelperSpec for digits with newlines. Probed strings may include new line indicators. However CombParserHelperSpec checks string matching using standard pattern matching and dots don't match \n. Now it uses DOTALL pattern matching mode --- .../test/scala/net/liftweb/util/CombParserHelpersSpec.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/util/src/test/scala/net/liftweb/util/CombParserHelpersSpec.scala b/core/util/src/test/scala/net/liftweb/util/CombParserHelpersSpec.scala index 25ebdb10b4..beb0b0f208 100644 --- a/core/util/src/test/scala/net/liftweb/util/CombParserHelpersSpec.scala +++ b/core/util/src/test/scala/net/liftweb/util/CombParserHelpersSpec.scala @@ -75,7 +75,7 @@ object CombParserHelpersSpec extends Specification with ScalaCheck { "provide a digit parser - returning a String" in { val isDigit: String => Boolean = (s: String) => digit(s) match { - case Success(x, y) => s must beMatching ("\\p{Nd}.*") + case Success(x, y) => s must beMatching ("(?s)\\p{Nd}.*") case _ => true } check(forAll(isDigit)) @@ -84,7 +84,7 @@ object CombParserHelpersSpec extends Specification with ScalaCheck { val number: String => Boolean = (s: String) => { aNumber(s) match { - case Success(x, y) => s must beMatching ("\\p{Nd}+.*") + case Success(x, y) => s must beMatching ("(?s)\\p{Nd}+.*") case _ => true } } From bf25bf6887f7c6b10dadd84849c17454e9d0b6a4 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 7 Jan 2015 10:25:38 -0500 Subject: [PATCH 1105/1949] Revert "Merge branch 'arkadius-clean-comb-parser-helpers-spec-fix'" This reverts commit f12a1d905b6aa79cfe7de6d46648c56861d667ba, reversing changes made to c1f31626435f04c22573313266efe54e20e1391b. The original merge was done off a branch that was not yet ready to be merged, so we'll cherry-pick the spec fix onto the non-merged master branch instead. --- .../liftweb/util/CombParserHelpersSpec.scala | 4 +- web/webkit/src/main/resources/toserve/lift.js | 44 ++--- .../scala/net/liftweb/http/LiftMerge.scala | 169 ++---------------- 3 files changed, 27 insertions(+), 190 deletions(-) diff --git a/core/util/src/test/scala/net/liftweb/util/CombParserHelpersSpec.scala b/core/util/src/test/scala/net/liftweb/util/CombParserHelpersSpec.scala index beb0b0f208..25ebdb10b4 100644 --- a/core/util/src/test/scala/net/liftweb/util/CombParserHelpersSpec.scala +++ b/core/util/src/test/scala/net/liftweb/util/CombParserHelpersSpec.scala @@ -75,7 +75,7 @@ object CombParserHelpersSpec extends Specification with ScalaCheck { "provide a digit parser - returning a String" in { val isDigit: String => Boolean = (s: String) => digit(s) match { - case Success(x, y) => s must beMatching ("(?s)\\p{Nd}.*") + case Success(x, y) => s must beMatching ("\\p{Nd}.*") case _ => true } check(forAll(isDigit)) @@ -84,7 +84,7 @@ object CombParserHelpersSpec extends Specification with ScalaCheck { val number: String => Boolean = (s: String) => { aNumber(s) match { - case Success(x, y) => s must beMatching ("(?s)\\p{Nd}+.*") + case Success(x, y) => s must beMatching ("\\p{Nd}+.*") case _ => true } } diff --git a/web/webkit/src/main/resources/toserve/lift.js b/web/webkit/src/main/resources/toserve/lift.js index 0f88d1d0d1..23e0efa42f 100644 --- a/web/webkit/src/main/resources/toserve/lift.js +++ b/web/webkit/src/main/resources/toserve/lift.js @@ -65,11 +65,8 @@ ajaxGet: function() { consoleOrAlert("ajaxGet function must be defined in settings"); }, - onEvent: function(elementOrId, eventName, fn) { - consoleOrAlert("onEvent function must be defined in settings"); - }, onDocumentReady: function(fn) { - consoleOrAlert("onDocumentReady function must be defined in settings"); + consoleOrAlert("documentReady function must be defined in settings"); }, cometGetTimeout: 140000, cometFailureRetryTimeout: 10000, @@ -557,7 +554,7 @@ this.extend(settings, options); var lift = this; - settings.onDocumentReady(function() { + options.onDocumentReady(function() { var attributes = document.body.attributes, cometGuid, cometVersion, comets = {}; @@ -587,8 +584,7 @@ doCycleIn200(); }); }, - logError: function() { settings.logError.apply(this, arguments) }, - onEvent: function() { settings.onEvent.apply(this, arguments) }, + logError: settings.logError, ajax: appendToQueue, startGc: successRegisterGC, ajaxOnSessionLost: function() { @@ -642,12 +638,6 @@ })(); window.liftJQuery = { - onEvent: function(elementOrId, eventName, fn) { - if (typeof elementOrId == 'string') - elementOrId = '#' + elementOrId; - - jQuery(elementOrId).on(eventName, fn); - }, onDocumentReady: jQuery(document).ready, ajaxPost: function(url, data, dataType, onSuccess, onFailure) { var processData = true, @@ -689,26 +679,16 @@ }; window.liftVanilla = { - // This and onDocumentReady adapted from https://github.com/dperini/ContentLoaded/blob/master/src/contentloaded.js, - // as also used (with modifications) in jQuery. - onEvent: function(elementOrId, eventName, fn) { - var win = window, - doc = win.document, - add = doc.addEventListener ? 'addEventListener' : 'attachEvent', - pre = doc.addEventListener ? '' : 'on'; - - var element = elementOrId; - if (typeof elementOrId == 'string') { - element = document.getElementById(elementOrId); - } - - element[add](pre + eventName, fn, false); - }, onDocumentReady: function(fn) { + // Taken from https://github.com/dperini/ContentLoaded/blob/master/src/contentloaded.js, + // as also used (with modifications) in jQuery. var done = false, top = true, + win = window, doc = win.document, root = doc.documentElement, - pre = doc.addEventListener ? '' : 'on'; + + add = doc.addEventListener ? 'addEventListener' : 'attachEvent', rem = doc.addEventListener ? 'removeEventListener' : 'detachEvent', + pre = doc.addEventListener ? '' : 'on', init = function(e) { if (e.type == 'readystatechange' && doc.readyState != 'complete') return; @@ -728,9 +708,9 @@ try { top = !win.frameElement; } catch(e) { } if (top) poll(); } - liftVanilla.onEvent(doc, 'DOMContentLoaded', init); - liftVanilla.onEvent(doc, 'readystatechange', init); - liftVanilla.onEvent(win, 'load', init); + doc[add](pre + 'DOMContentLoaded', init, false); + doc[add](pre + 'readystatechange', init, false); + win[add](pre + 'load', init, false); } }, ajaxPost: function(url, data, dataType, onSuccess, onFailure, onUploadProgress) { diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala index bda758f482..ddd032eb75 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala @@ -17,42 +17,13 @@ package net.liftweb package http -import scala.collection.Map import scala.collection.mutable.{HashMap, ArrayBuffer, ListBuffer} import scala.xml._ - import net.liftweb.util._ import net.liftweb.common._ import net.liftweb.http.js._ - import JsCmds.Noop - import JE.{AnonFunc,Call,JsRaw} import Helpers._ -/** - * Represents an HTML attribute for an event handler. Carries the event name and - * the JS that should run when that event is triggered as a String. - */ -private case class EventAttribute(eventName: String, jsString: String) -private object EventAttribute { - /** - * A map from attribute name to event name for attributes that support being - * set to javascript:(//)-style values in order to invoke JS. For example, you - * can (and Lift does) set a form's action attribute to javascript://(some JS) - * instead of setting onsubmit to (someJS); return false. - */ - val eventsByAttributeName = - Map( - "action" -> "submit", - "href" -> "click" - ) - - object EventForAttribute { - def unapply(attributeName: String): Option[String] = { - eventsByAttributeName.get(attributeName) - } - } -} - private[http] trait LiftMerge { self: LiftSession => @@ -61,21 +32,12 @@ private[http] trait LiftMerge { } // Gather all page-specific JS into one JsCmd. - private def assemblePageSpecificJavaScript(eventAttributesByElementId: Map[String,List[EventAttribute]]): JsCmd = { - val eventJs = - for { - (elementId, eventAttributes) <- eventAttributesByElementId - EventAttribute(eventName, jsString) <- eventAttributes - } yield { - Call("lift.onEvent", elementId, eventName, AnonFunc("event", JsRaw(jsString).cmd)).cmd - } - + private def assemblePageSpecificJavaScript: JsCmd = { val allJs = LiftRules.javaScriptSettings.vend().map { settingsFn => LiftJavaScript.initCmd(settingsFn(this)) }.toList ++ - S.jsToAppend ++ - eventJs + S.jsToAppend allJs.foldLeft(js.JsCmds.Noop)(_ & _) } @@ -137,97 +99,17 @@ private[http] trait LiftMerge { addlHead ++= S.forHead() val addlTail = new ListBuffer[Node] addlTail ++= S.atEndOfBody() - val eventAttributesByElementId = new HashMap[String,List[EventAttribute]] val rewrite = URLRewriter.rewriteFunc val fixHref = Req.fixHref val contextPath: String = S.contextPath - // Fix URLs using Req.fixHref and extract JS event attributes for putting - // into page JS. Returns a triple of: - // - The optional id that was found in this set of attributes. - // - The fixed metadata. - // - A list of extracted `EventAttribute`s. - def fixAttrs(original: MetaData, toFix: String, attrs: MetaData, fixURL: Boolean, eventAttributes: List[EventAttribute] = Nil): (Option[String], MetaData, List[EventAttribute]) = { - attrs match { - case Null => (None, Null, eventAttributes) - - case attribute @ UnprefixedAttribute( - EventAttribute.EventForAttribute(eventName), - attributeValue, - remainingAttributes - ) if attributeValue.text.startsWith("javascript:") => - val attributeJavaScript = { - // Could be javascript: or javascript://. - val base = attributeValue.text.substring(11) - val strippedJs = - if (base.startsWith("//")) - base.substring(2) - else - base - - if (strippedJs.trim.isEmpty) { - Nil - } else { - // When using javascript:-style URIs, return false is implied. - List(strippedJs + "; event.preventDefault();") - } - } - - val updatedEventAttributes = attributeJavaScript.map(EventAttribute(eventName, _)) ::: eventAttributes - fixAttrs(original, toFix, remainingAttributes, fixURL, updatedEventAttributes) + def fixAttrs(original: MetaData, toFix: String, attrs: MetaData, fixURL: Boolean): MetaData = attrs match { + case Null => Null + 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)) - case u: UnprefixedAttribute if u.key == toFix => - val (id, fixedAttributes, updatedEventAttributes) = fixAttrs(original, toFix, attrs.next, fixURL) - - (id, new UnprefixedAttribute(toFix, fixHref(contextPath, attrs.value, fixURL, rewrite), fixedAttributes), updatedEventAttributes) - - case u: UnprefixedAttribute if u.key.startsWith("on") => - fixAttrs(original, toFix, attrs.next, fixURL, EventAttribute(u.key.substring(2), u.value.text) :: eventAttributes) - - case u: UnprefixedAttribute if u.key == "id" => - val (_, fixedAttributes, updatedEventAttributes) = fixAttrs(original, toFix, attrs.next, fixURL, eventAttributes) - - (Option(u.value.text).filter(_.nonEmpty), attrs.copy(fixedAttributes), updatedEventAttributes) - - case _ => - val (id, fixedAttributes, updatedEventAttributes) = fixAttrs(original, toFix, attrs.next, fixURL, eventAttributes) - - (id, attrs.copy(fixedAttributes), updatedEventAttributes) - } - } - - // Fix the element's `attributeToFix` using `fixAttrs` and extract JS event - // attributes for putting into page JS. Return a fixed version of this - // element with fixed children. Adds a lift-generated id if the given - // element needs to have event handlers attached but doesn't already have an - // id. - def fixElementAndAttributes(element: Elem, attributeToFix: String, fixURL: Boolean, fixedChildren: NodeSeq) = { - val (id, fixedAttributes, eventAttributes) = fixAttrs(element.attributes, attributeToFix, element.attributes, fixURL) - - id.map { foundId => - eventAttributesByElementId += (foundId -> eventAttributes) - - element.copy( - attributes = fixedAttributes, - child = fixedChildren - ) - } getOrElse { - if (eventAttributes.nonEmpty) { - val generatedId = s"lift-event-js-$nextFuncName" - eventAttributesByElementId += (generatedId -> eventAttributes) - - element.copy( - attributes = new UnprefixedAttribute("id", generatedId, fixedAttributes), - child = fixedChildren - ) - } else { - element.copy( - attributes = fixedAttributes, - child = fixedChildren - ) - } - } } def _fixHtml(in: NodeSeq, _inHtml: Boolean, _inHead: Boolean, _justHead: Boolean, _inBody: Boolean, _justBody: Boolean, _bodyHead: Boolean, _bodyTail: Boolean, doMergy: Boolean): NodeSeq = { @@ -274,36 +156,11 @@ private[http] trait LiftMerge { node <- _fixHtml(nodes, inHtml, inHead, justHead, inBody, justBody, bodyHead, bodyTail, doMergy) } yield node - case e: Elem if e.label == "form" => - fixElementAndAttributes( - e, "action", fixURL = true, - _fixHtml(e.child, inHtml, inHead, justHead, inBody, justBody, bodyHead, bodyTail, doMergy) - ) - - case e: Elem if e.label == "script" => - fixElementAndAttributes( - e, "src", fixURL = false, - _fixHtml(e.child, inHtml, inHead, justHead, inBody, justBody, bodyHead, bodyTail, doMergy) - ) - - case e: Elem if e.label == "a" => - fixElementAndAttributes( - e, "href", fixURL = true, - _fixHtml(e.child, inHtml, inHead, justHead, inBody, justBody, bodyHead, bodyTail, doMergy) - ) - - case e: Elem if e.label == "link" => - fixElementAndAttributes( - e, "href", fixURL = false, - _fixHtml(e.child, inHtml, inHead, justHead, inBody, justBody, bodyHead, bodyTail, doMergy) - ) - - case e: Elem => - fixElementAndAttributes( - e, "src", fixURL = true, - _fixHtml(e.child, inHtml, inHead, justHead, inBody, justBody, bodyHead, bodyTail, doMergy) - ) - + case e: Elem if e.label == "form" => Elem(v.prefix, v.label, fixAttrs(v.attributes, "action", v.attributes, true), v.scope, e.minimizeEmpty, _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, e.minimizeEmpty, _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, e.minimizeEmpty, _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, e.minimizeEmpty,_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, e.minimizeEmpty, _fixHtml(v.child, inHtml, inHead, justHead, inBody, justBody, bodyHead, bodyTail, doMergy): _*) case c: Comment if stripComments => NodeSeq.Empty case _ => v } @@ -346,7 +203,7 @@ private[http] trait LiftMerge { bodyChildren += nl } - val pageJs = assemblePageSpecificJavaScript(eventAttributesByElementId) + val pageJs = assemblePageSpecificJavaScript if (pageJs.toJsCmd.trim.nonEmpty) { addlTail += pageScopedScriptFileWith(pageJs) } From c5914612d831e571e3abb6775451299535b0eb4a Mon Sep 17 00:00:00 2001 From: Arek Burdach Date: Sun, 4 Jan 2015 20:44:07 +0100 Subject: [PATCH 1106/1949] Fix CombParserHelperSpec for digits with newlines. Probed strings may include new line indicators. However CombParserHelperSpec checks string matching using standard pattern matching and dots don't match \n. Now it uses DOTALL pattern matching mode --- .../test/scala/net/liftweb/util/CombParserHelpersSpec.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/util/src/test/scala/net/liftweb/util/CombParserHelpersSpec.scala b/core/util/src/test/scala/net/liftweb/util/CombParserHelpersSpec.scala index 25ebdb10b4..beb0b0f208 100644 --- a/core/util/src/test/scala/net/liftweb/util/CombParserHelpersSpec.scala +++ b/core/util/src/test/scala/net/liftweb/util/CombParserHelpersSpec.scala @@ -75,7 +75,7 @@ object CombParserHelpersSpec extends Specification with ScalaCheck { "provide a digit parser - returning a String" in { val isDigit: String => Boolean = (s: String) => digit(s) match { - case Success(x, y) => s must beMatching ("\\p{Nd}.*") + case Success(x, y) => s must beMatching ("(?s)\\p{Nd}.*") case _ => true } check(forAll(isDigit)) @@ -84,7 +84,7 @@ object CombParserHelpersSpec extends Specification with ScalaCheck { val number: String => Boolean = (s: String) => { aNumber(s) match { - case Success(x, y) => s must beMatching ("\\p{Nd}+.*") + case Success(x, y) => s must beMatching ("(?s)\\p{Nd}+.*") case _ => true } } From 97ab445ecedaef5cec3916503046ea608160fbfd Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 7 Jan 2015 10:29:30 -0500 Subject: [PATCH 1107/1949] Revert "Revert "Merge branch 'arkadius-clean-comb-parser-helpers-spec-fix'"" This reverts commit bf25bf6887f7c6b10dadd84849c17454e9d0b6a4. The original revert was there because I cherry-picked a commit I wanted on master onto the a branch off of this one, and then merged it into master before this one was ready to go (shoulda opened a PR...). This commit reverts the revert on this branch, so that when we merge this branch to master its changes will take hold (otherwise the first revert would make the original changes be lost). Whew! --- web/webkit/src/main/resources/toserve/lift.js | 44 +++-- .../scala/net/liftweb/http/LiftMerge.scala | 169 ++++++++++++++++-- 2 files changed, 188 insertions(+), 25 deletions(-) diff --git a/web/webkit/src/main/resources/toserve/lift.js b/web/webkit/src/main/resources/toserve/lift.js index 23e0efa42f..0f88d1d0d1 100644 --- a/web/webkit/src/main/resources/toserve/lift.js +++ b/web/webkit/src/main/resources/toserve/lift.js @@ -65,8 +65,11 @@ ajaxGet: function() { consoleOrAlert("ajaxGet function must be defined in settings"); }, + onEvent: function(elementOrId, eventName, fn) { + consoleOrAlert("onEvent function must be defined in settings"); + }, onDocumentReady: function(fn) { - consoleOrAlert("documentReady function must be defined in settings"); + consoleOrAlert("onDocumentReady function must be defined in settings"); }, cometGetTimeout: 140000, cometFailureRetryTimeout: 10000, @@ -554,7 +557,7 @@ this.extend(settings, options); var lift = this; - options.onDocumentReady(function() { + settings.onDocumentReady(function() { var attributes = document.body.attributes, cometGuid, cometVersion, comets = {}; @@ -584,7 +587,8 @@ doCycleIn200(); }); }, - logError: settings.logError, + logError: function() { settings.logError.apply(this, arguments) }, + onEvent: function() { settings.onEvent.apply(this, arguments) }, ajax: appendToQueue, startGc: successRegisterGC, ajaxOnSessionLost: function() { @@ -638,6 +642,12 @@ })(); window.liftJQuery = { + onEvent: function(elementOrId, eventName, fn) { + if (typeof elementOrId == 'string') + elementOrId = '#' + elementOrId; + + jQuery(elementOrId).on(eventName, fn); + }, onDocumentReady: jQuery(document).ready, ajaxPost: function(url, data, dataType, onSuccess, onFailure) { var processData = true, @@ -679,16 +689,26 @@ }; window.liftVanilla = { + // This and onDocumentReady adapted from https://github.com/dperini/ContentLoaded/blob/master/src/contentloaded.js, + // as also used (with modifications) in jQuery. + onEvent: function(elementOrId, eventName, fn) { + var win = window, + doc = win.document, + add = doc.addEventListener ? 'addEventListener' : 'attachEvent', + pre = doc.addEventListener ? '' : 'on'; + + var element = elementOrId; + if (typeof elementOrId == 'string') { + element = document.getElementById(elementOrId); + } + + element[add](pre + eventName, fn, false); + }, onDocumentReady: function(fn) { - // Taken from https://github.com/dperini/ContentLoaded/blob/master/src/contentloaded.js, - // as also used (with modifications) in jQuery. var done = false, top = true, - win = window, doc = win.document, root = doc.documentElement, - - add = doc.addEventListener ? 'addEventListener' : 'attachEvent', + pre = doc.addEventListener ? '' : 'on'; rem = doc.addEventListener ? 'removeEventListener' : 'detachEvent', - pre = doc.addEventListener ? '' : 'on', init = function(e) { if (e.type == 'readystatechange' && doc.readyState != 'complete') return; @@ -708,9 +728,9 @@ try { top = !win.frameElement; } catch(e) { } if (top) poll(); } - doc[add](pre + 'DOMContentLoaded', init, false); - doc[add](pre + 'readystatechange', init, false); - win[add](pre + 'load', init, false); + liftVanilla.onEvent(doc, 'DOMContentLoaded', init); + liftVanilla.onEvent(doc, 'readystatechange', init); + liftVanilla.onEvent(win, 'load', init); } }, ajaxPost: function(url, data, dataType, onSuccess, onFailure, onUploadProgress) { diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala index ddd032eb75..bda758f482 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala @@ -17,13 +17,42 @@ package net.liftweb package http +import scala.collection.Map import scala.collection.mutable.{HashMap, ArrayBuffer, ListBuffer} import scala.xml._ + import net.liftweb.util._ import net.liftweb.common._ import net.liftweb.http.js._ + import JsCmds.Noop + import JE.{AnonFunc,Call,JsRaw} import Helpers._ +/** + * Represents an HTML attribute for an event handler. Carries the event name and + * the JS that should run when that event is triggered as a String. + */ +private case class EventAttribute(eventName: String, jsString: String) +private object EventAttribute { + /** + * A map from attribute name to event name for attributes that support being + * set to javascript:(//)-style values in order to invoke JS. For example, you + * can (and Lift does) set a form's action attribute to javascript://(some JS) + * instead of setting onsubmit to (someJS); return false. + */ + val eventsByAttributeName = + Map( + "action" -> "submit", + "href" -> "click" + ) + + object EventForAttribute { + def unapply(attributeName: String): Option[String] = { + eventsByAttributeName.get(attributeName) + } + } +} + private[http] trait LiftMerge { self: LiftSession => @@ -32,12 +61,21 @@ private[http] trait LiftMerge { } // Gather all page-specific JS into one JsCmd. - private def assemblePageSpecificJavaScript: JsCmd = { + private def assemblePageSpecificJavaScript(eventAttributesByElementId: Map[String,List[EventAttribute]]): JsCmd = { + val eventJs = + for { + (elementId, eventAttributes) <- eventAttributesByElementId + EventAttribute(eventName, jsString) <- eventAttributes + } yield { + Call("lift.onEvent", elementId, eventName, AnonFunc("event", JsRaw(jsString).cmd)).cmd + } + val allJs = LiftRules.javaScriptSettings.vend().map { settingsFn => LiftJavaScript.initCmd(settingsFn(this)) }.toList ++ - S.jsToAppend + S.jsToAppend ++ + eventJs allJs.foldLeft(js.JsCmds.Noop)(_ & _) } @@ -99,17 +137,97 @@ private[http] trait LiftMerge { addlHead ++= S.forHead() val addlTail = new ListBuffer[Node] addlTail ++= S.atEndOfBody() + val eventAttributesByElementId = new HashMap[String,List[EventAttribute]] val rewrite = URLRewriter.rewriteFunc val fixHref = Req.fixHref val contextPath: String = S.contextPath - def fixAttrs(original: MetaData, toFix: String, attrs: MetaData, fixURL: Boolean): MetaData = attrs match { - case Null => Null - 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)) + // Fix URLs using Req.fixHref and extract JS event attributes for putting + // into page JS. Returns a triple of: + // - The optional id that was found in this set of attributes. + // - The fixed metadata. + // - A list of extracted `EventAttribute`s. + def fixAttrs(original: MetaData, toFix: String, attrs: MetaData, fixURL: Boolean, eventAttributes: List[EventAttribute] = Nil): (Option[String], MetaData, List[EventAttribute]) = { + attrs match { + case Null => (None, Null, eventAttributes) + + case attribute @ UnprefixedAttribute( + EventAttribute.EventForAttribute(eventName), + attributeValue, + remainingAttributes + ) if attributeValue.text.startsWith("javascript:") => + val attributeJavaScript = { + // Could be javascript: or javascript://. + val base = attributeValue.text.substring(11) + val strippedJs = + if (base.startsWith("//")) + base.substring(2) + else + base + + if (strippedJs.trim.isEmpty) { + Nil + } else { + // When using javascript:-style URIs, return false is implied. + List(strippedJs + "; event.preventDefault();") + } + } + + val updatedEventAttributes = attributeJavaScript.map(EventAttribute(eventName, _)) ::: eventAttributes + fixAttrs(original, toFix, remainingAttributes, fixURL, updatedEventAttributes) + case u: UnprefixedAttribute if u.key == toFix => + val (id, fixedAttributes, updatedEventAttributes) = fixAttrs(original, toFix, attrs.next, fixURL) + + (id, new UnprefixedAttribute(toFix, fixHref(contextPath, attrs.value, fixURL, rewrite), fixedAttributes), updatedEventAttributes) + + case u: UnprefixedAttribute if u.key.startsWith("on") => + fixAttrs(original, toFix, attrs.next, fixURL, EventAttribute(u.key.substring(2), u.value.text) :: eventAttributes) + + case u: UnprefixedAttribute if u.key == "id" => + val (_, fixedAttributes, updatedEventAttributes) = fixAttrs(original, toFix, attrs.next, fixURL, eventAttributes) + + (Option(u.value.text).filter(_.nonEmpty), attrs.copy(fixedAttributes), updatedEventAttributes) + + case _ => + val (id, fixedAttributes, updatedEventAttributes) = fixAttrs(original, toFix, attrs.next, fixURL, eventAttributes) + + (id, attrs.copy(fixedAttributes), updatedEventAttributes) + } + } + + // Fix the element's `attributeToFix` using `fixAttrs` and extract JS event + // attributes for putting into page JS. Return a fixed version of this + // element with fixed children. Adds a lift-generated id if the given + // element needs to have event handlers attached but doesn't already have an + // id. + def fixElementAndAttributes(element: Elem, attributeToFix: String, fixURL: Boolean, fixedChildren: NodeSeq) = { + val (id, fixedAttributes, eventAttributes) = fixAttrs(element.attributes, attributeToFix, element.attributes, fixURL) + + id.map { foundId => + eventAttributesByElementId += (foundId -> eventAttributes) + + element.copy( + attributes = fixedAttributes, + child = fixedChildren + ) + } getOrElse { + if (eventAttributes.nonEmpty) { + val generatedId = s"lift-event-js-$nextFuncName" + eventAttributesByElementId += (generatedId -> eventAttributes) + + element.copy( + attributes = new UnprefixedAttribute("id", generatedId, fixedAttributes), + child = fixedChildren + ) + } else { + element.copy( + attributes = fixedAttributes, + child = fixedChildren + ) + } + } } def _fixHtml(in: NodeSeq, _inHtml: Boolean, _inHead: Boolean, _justHead: Boolean, _inBody: Boolean, _justBody: Boolean, _bodyHead: Boolean, _bodyTail: Boolean, doMergy: Boolean): NodeSeq = { @@ -156,11 +274,36 @@ private[http] trait LiftMerge { 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, e.minimizeEmpty, _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, e.minimizeEmpty, _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, e.minimizeEmpty, _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, e.minimizeEmpty,_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, e.minimizeEmpty, _fixHtml(v.child, inHtml, inHead, justHead, inBody, justBody, bodyHead, bodyTail, doMergy): _*) + case e: Elem if e.label == "form" => + fixElementAndAttributes( + e, "action", fixURL = true, + _fixHtml(e.child, inHtml, inHead, justHead, inBody, justBody, bodyHead, bodyTail, doMergy) + ) + + case e: Elem if e.label == "script" => + fixElementAndAttributes( + e, "src", fixURL = false, + _fixHtml(e.child, inHtml, inHead, justHead, inBody, justBody, bodyHead, bodyTail, doMergy) + ) + + case e: Elem if e.label == "a" => + fixElementAndAttributes( + e, "href", fixURL = true, + _fixHtml(e.child, inHtml, inHead, justHead, inBody, justBody, bodyHead, bodyTail, doMergy) + ) + + case e: Elem if e.label == "link" => + fixElementAndAttributes( + e, "href", fixURL = false, + _fixHtml(e.child, inHtml, inHead, justHead, inBody, justBody, bodyHead, bodyTail, doMergy) + ) + + case e: Elem => + fixElementAndAttributes( + e, "src", fixURL = true, + _fixHtml(e.child, inHtml, inHead, justHead, inBody, justBody, bodyHead, bodyTail, doMergy) + ) + case c: Comment if stripComments => NodeSeq.Empty case _ => v } @@ -203,7 +346,7 @@ private[http] trait LiftMerge { bodyChildren += nl } - val pageJs = assemblePageSpecificJavaScript + val pageJs = assemblePageSpecificJavaScript(eventAttributesByElementId) if (pageJs.toJsCmd.trim.nonEmpty) { addlTail += pageScopedScriptFileWith(pageJs) } From 67e924f615ff49d348e6b97241e40702318bd648 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 7 Jan 2015 17:09:34 -0500 Subject: [PATCH 1108/1949] Add some curly braces in some JS for safety. Friends don't let friends JavaScript braceless. --- web/webkit/src/main/resources/toserve/lift.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web/webkit/src/main/resources/toserve/lift.js b/web/webkit/src/main/resources/toserve/lift.js index 0f88d1d0d1..a37b6e0186 100644 --- a/web/webkit/src/main/resources/toserve/lift.js +++ b/web/webkit/src/main/resources/toserve/lift.js @@ -643,8 +643,9 @@ window.liftJQuery = { onEvent: function(elementOrId, eventName, fn) { - if (typeof elementOrId == 'string') + if (typeof elementOrId == 'string') { elementOrId = '#' + elementOrId; + } jQuery(elementOrId).on(eventName, fn); }, From 7feacef976528ec33db079615900cef964405fe8 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 7 Jan 2015 22:58:38 -0500 Subject: [PATCH 1109/1949] Switch find/isDefined to exists. --- .../scala/net/liftweb/webapptest/snippet/DeferredSnippet.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/webkit/src/test/scala/net/liftweb/webapptest/snippet/DeferredSnippet.scala b/web/webkit/src/test/scala/net/liftweb/webapptest/snippet/DeferredSnippet.scala index 27e01b2255..c30a76390d 100644 --- a/web/webkit/src/test/scala/net/liftweb/webapptest/snippet/DeferredSnippet.scala +++ b/web/webkit/src/test/scala/net/liftweb/webapptest/snippet/DeferredSnippet.scala @@ -40,7 +40,7 @@ class DeferredSnippet { } def stackWhack: NodeSeq = { - val inActor: Boolean = Thread.currentThread.getStackTrace.find(_.getClassName.contains("net.liftweb.actor.")).isDefined + val inActor: Boolean = Thread.currentThread.getStackTrace.exists(_.getClassName.contains("net.liftweb.actor.")) stackWhack } From 3ea320b82db2799814bbcb55698279d2e7434781 Mon Sep 17 00:00:00 2001 From: Arek Burdach Date: Tue, 6 Jan 2015 23:14:59 +0100 Subject: [PATCH 1110/1949] Fix failing LiftJavaScriptSpec in non-English environments. Generated Lift JS is localized based on the current environment. However, LiftJavaScriptSpec checks for English messages, so running it under a non-English environment causes it to fail due to the mismatch in expected message. Now settings are tested with english locale, internationalization is tested separately. --- .../liftweb/http/js/LiftJavaScriptSpec.scala | 40 +++++++++++++++---- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/web/webkit/src/test/scala/net/liftweb/http/js/LiftJavaScriptSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/js/LiftJavaScriptSpec.scala index 6f8d5391cc..794b9d6344 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/js/LiftJavaScriptSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/js/LiftJavaScriptSpec.scala @@ -18,7 +18,10 @@ package net.liftweb package http package js -import org.specs2.mutable.Specification +import java.util.Locale + +import org.specs2.execute.{Result, AsResult} +import org.specs2.mutable.{Around, Specification} import common._ import http.js._ @@ -37,34 +40,41 @@ object LiftJavaScriptSpec extends Specification { private def session = new LiftSession("", randomString(20), Empty) "LiftJavaScript" should { - "create default settings" in { + "create default settings" in withEnglishLocale { S.initIfUninitted(session) { val settings = LiftJavaScript.settings settings.toJsCmd must_== """{"liftPath": "/lift", "ajaxRetryCount": 3, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": null, "logError": function(msg) {}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}""" } } - "create custom static settings" in { + "create internationalized default settings" in withPolishLocale { + S.initIfUninitted(session) { + val settings = LiftJavaScript.settings + val internationalizedMessage = "Nie mo\\u017cna skontaktowa\\u0107 si\\u0119 z serwerem" + settings.toJsCmd must_== s"""{"liftPath": "/lift", "ajaxRetryCount": 3, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": null, "logError": function(msg) {}, "ajaxOnFailure": function() {alert("$internationalizedMessage");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}""" + } + } + "create custom static settings" in withEnglishLocale { S.initIfUninitted(session) { LiftRules.ajaxRetryCount = Full(4) val settings = LiftJavaScript.settings settings.toJsCmd must_== """{"liftPath": "/lift", "ajaxRetryCount": 4, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": null, "logError": function(msg) {}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}""" } } - "create custom dynamic settings" in { + "create custom dynamic settings" in withEnglishLocale { S.initIfUninitted(session) { LiftRules.cometServer = () => Some("srvr1") val settings = LiftJavaScript.settings settings.toJsCmd must_== """{"liftPath": "/lift", "ajaxRetryCount": 4, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "srvr1", "logError": function(msg) {}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}""" } } - "create custom function settings" in { + "create custom function settings" in withEnglishLocale { S.initIfUninitted(session) { LiftRules.jsLogFunc = Full(v => JE.Call("lift.logError", v)) val settings = LiftJavaScript.settings settings.toJsCmd must_== """{"liftPath": "/lift", "ajaxRetryCount": 4, "ajaxPostTimeout": 5000, "gcPollingInterval": 75000, "gcFailureRetryTimeout": 15000, "cometGetTimeout": 140000, "cometFailureRetryTimeout": 10000, "cometServer": "srvr1", "logError": function(msg) {lift.logError(msg);}, "ajaxOnFailure": function() {alert("The server cannot be contacted at this time");}, "ajaxOnStart": function() {}, "ajaxOnEnd": function() {}}""" } } - "create init command" in { + "create init command" in withEnglishLocale { S.initIfUninitted(session) { val init = LiftRules.javaScriptSettings.vend().map(_.apply(session)).map(LiftJavaScript.initCmd(_).toJsCmd) init must_== @@ -73,7 +83,7 @@ object LiftJavaScriptSpec extends Specification { |window.lift.init(lift_settings);""".stripMargin) } } - "create init command with custom setting" in { + "create init command with custom setting" in withEnglishLocale { S.initIfUninitted(session) { val settings = LiftJavaScript.settings.extend(JsObj("liftPath" -> "liftyStuff", "mysetting" -> 99)) val init = LiftJavaScript.initCmd(settings) @@ -85,4 +95,20 @@ object LiftJavaScriptSpec extends Specification { } } } + + object withEnglishLocale extends WithLocale(Locale.ENGLISH) + + object withPolishLocale extends WithLocale(Locale.forLanguageTag("pl-PL")) + + class WithLocale(locale: Locale) extends Around { + override def around[T: AsResult](test: => T): Result = { + val savedDefaultLocale = Locale.getDefault + Locale.setDefault(locale) + try { + AsResult(test) + } finally { + Locale.setDefault(savedDefaultLocale) + } + } + } } From 2292a0a2ef9cb5759e0e251e9a8190ae3979289e Mon Sep 17 00:00:00 2001 From: Joe Barnes Date: Sun, 11 Jan 2015 20:35:04 -0600 Subject: [PATCH 1111/1949] Making ContentParser ever so slightly more awesome with Antonio's suggestion to use 'apply' as the factory method name --- .../main/scala/net/liftweb/http/ContentParser.scala | 10 +++++----- .../src/main/scala/net/liftweb/http/LiftRules.scala | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/ContentParser.scala b/web/webkit/src/main/scala/net/liftweb/http/ContentParser.scala index 10258366a4..bb6bfae5dc 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/ContentParser.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/ContentParser.scala @@ -78,7 +78,7 @@ object ContentParser { * @param surroundFunction the function for surrounding the content returned by the `parseFunction`. * See [[defaultSurround]]. */ - def basic(templateSuffix: String, + def apply(templateSuffix: String, parseFunction: String=>Box[NodeSeq], surroundFunction: NodeSeq=>NodeSeq = defaultSurround):ContentParser = new ContentParser { @@ -88,12 +88,12 @@ object ContentParser { } /** - * A full `ContentParser` which handles multiple filename suffixes, operates on an `InputStream`, and surrounds the - * top-level templates with the default surround + * A fully-specified `ContentParser` which handles multiple filename suffixes, operates on an `InputStream`, and + * surrounds the top-level templates with the default surround */ - def full(templateSuffixesSeq: Seq[String], + def apply(templateSuffixesSeq: Seq[String], parseF: InputStream=>Box[NodeSeq], - surroundF: NodeSeq=>NodeSeq = defaultSurround):ContentParser = + surroundF: NodeSeq=>NodeSeq):ContentParser = new ContentParser { override def templateSuffixes: Seq[String] = templateSuffixesSeq override def parse(content: InputStream): Box[NodeSeq] = parseF(content) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 9f9b27c1f8..4b4d363599 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -1324,12 +1324,12 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { * Handles the parsing of template content into NodeSeqs. */ @volatile var contentParsers: Seq[ContentParser] = Seq( - ContentParser.full( + ContentParser( Seq("html", "xhtml", "htm"), - S.htmlProperties.htmlParser, - content => content // These templates are not surrounded by default + (content:InputStream) => S.htmlProperties.htmlParser(content), + identity[NodeSeq](_) // These templates are not surrounded by default ), - ContentParser.basic("md", MarkdownParser.parse) + ContentParser("md", MarkdownParser.parse) ) /** From afb8197cbe6927dd5281475f3e5cccd981d0d33b Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Tue, 13 Jan 2015 22:51:46 -0500 Subject: [PATCH 1112/1949] Slight style cleanup. --- web/webkit/src/main/scala/net/liftweb/http/S.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/S.scala b/web/webkit/src/main/scala/net/liftweb/http/S.scala index e1489a7494..8f7ffdfccf 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/S.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/S.scala @@ -950,7 +950,8 @@ trait S extends HasParams with Loggable with UserAgentCalculator { globalJs ::: { postPageJs ::: cometJs ::: _jsToAppend.is.toList match { - case Nil => Nil + case Nil => + Nil case loadJs => List(OnLoad(loadJs)) } From c99d8cc0e43f06a160a9834e88b8e17db95be91b Mon Sep 17 00:00:00 2001 From: Peter Brant Date: Wed, 14 Jan 2015 19:22:27 +0100 Subject: [PATCH 1113/1949] Improve public API --- .../scala/net/liftweb/http/CometActor.scala | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala b/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala index ccba48d906..d4de393d68 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/CometActor.scala @@ -445,10 +445,24 @@ abstract class CometActorJWithCometListener extends CometActorJ with CometListen override def lowPriority = _messageHandler } +trait CometActor extends BaseCometActor { + override final private[http] def partialUpdateStream_? = false +} + +trait MessageCometActor extends BaseCometActor { + override final private[http] def partialUpdateStream_? = true + + override final def render = NodeSeq.Empty + + protected def pushMessage(cmd: => JsCmd) { + partialUpdate(cmd) + } +} + /** * Takes care of the plumbing for building Comet-based Web Apps */ -trait CometActor extends LiftActor with LiftCometActor with CssBindImplicits { +trait BaseCometActor extends LiftActor with LiftCometActor with CssBindImplicits { private val logger = Logger(classOf[CometActor]) val uniqueId = Helpers.nextFuncName private var spanId = uniqueId @@ -493,7 +507,7 @@ trait CometActor extends LiftActor with LiftCometActor with CssBindImplicits { * push arbitrary JavaScript to the client without a distinct UI for the * comet actor itself. */ - protected def partialUpdateStream_? : Boolean = false + private[http] def partialUpdateStream_? : Boolean = false /** * The last rendering (cached or not) @@ -531,7 +545,7 @@ trait CometActor extends LiftActor with LiftCometActor with CssBindImplicits { private var jsonHandlerChain: PartialFunction[Any, JsCmd] = Map.empty private val notices = new ListBuffer[(NoticeType.Value, NodeSeq, Box[String])] - private var _deltaPruner: (CometActor, List[Delta]) => List[Delta] = + private var _deltaPruner: (BaseCometActor, List[Delta]) => List[Delta] = (actor, d) => { val m = Helpers.millis d.filter(d => (m - d.timestamp) < 120000L) @@ -694,7 +708,7 @@ trait CometActor extends LiftActor with LiftCometActor with CssBindImplicits { val what = composeFunction val myPf: PartialFunction[Any, Unit] = new PartialFunction[Any, Unit] { def apply(in: Any): Unit = - CurrentCometActor.doWith(CometActor.this) { + CurrentCometActor.doWith(BaseCometActor.this) { S.initIfUninitted(theSession) { RenderVersion.doWith(uniqueId) { S.functionLifespan(true) { @@ -715,7 +729,7 @@ trait CometActor extends LiftActor with LiftCometActor with CssBindImplicits { } def isDefinedAt(in: Any): Boolean = - CurrentCometActor.doWith(CometActor.this) { + CurrentCometActor.doWith(BaseCometActor.this) { S.initIfUninitted(theSession) { RenderVersion.doWith(uniqueId) { S.functionLifespan(true) { From c188e4f6600f64fa96cbf29451c38d175a4f577c Mon Sep 17 00:00:00 2001 From: Joe Barnes Date: Thu, 15 Jan 2015 17:16:13 -0600 Subject: [PATCH 1114/1949] Adding details regarding the behavior of LiftRules.contentParsers in scaladocs --- web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 4b4d363599..43a3fedaef 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -1321,7 +1321,9 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { val snippets = RulesSeq[SnippetPF] /** - * Handles the parsing of template content into NodeSeqs. + * Handles the parsing of template content into NodeSeqs. If multiple parsers are registered for the same + * template suffix, the first matching parser is used. This intended to be set in in `Boot` as it is read only + * once during the processing of the first template. */ @volatile var contentParsers: Seq[ContentParser] = Seq( ContentParser( From 6613d9304c8572885bbeb8a3ccd7a737961e3623 Mon Sep 17 00:00:00 2001 From: Joe Barnes Date: Thu, 15 Jan 2015 17:55:56 -0600 Subject: [PATCH 1115/1949] Changing LiftRules.contentParsers from a Seq to a List to encourage prepending/emphasize the first-one-wins behavior --- web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 43a3fedaef..0f66f3c62c 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -1325,7 +1325,7 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { * template suffix, the first matching parser is used. This intended to be set in in `Boot` as it is read only * once during the processing of the first template. */ - @volatile var contentParsers: Seq[ContentParser] = Seq( + @volatile var contentParsers: List[ContentParser] = List( ContentParser( Seq("html", "xhtml", "htm"), (content:InputStream) => S.htmlProperties.htmlParser(content), From ab71c7bc143d27bbd2e59bd8588977327d4237f4 Mon Sep 17 00:00:00 2001 From: Arek Burdach Date: Tue, 13 Jan 2015 00:11:37 +0100 Subject: [PATCH 1116/1949] TimeSpan represents duration in milliseconds. Before the change TimeSpan has many responsibilities - in equality behave as a joda Period (was checking duration field equality), can represent a Date (using toDate/toDateTime operations) or be used as a simple container for milliseconds. Now is responsible only for operations on milliseconds. Problematic methods have been deprecated (.ago/.later have been moved to implicit PeriodExtension). For backward compatibility there is introduced implicit conversion from Period to TimeSpan. Also added a variety of deprecations and cleaned up specs that are timezone-dependent to run in explicit conditions. --- .../scala/net/liftweb/util/TimeHelpers.scala | 176 +++++++++--------- .../net/liftweb/util/TimeHelpersSpec.scala | 144 ++++++++------ project/Dependencies.scala | 2 +- 3 files changed, 179 insertions(+), 143 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala b/core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala index daac48e580..c8547fc6fd 100644 --- a/core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala @@ -22,7 +22,7 @@ import java.util.{TimeZone, Calendar, Date, Locale} import scala.language.implicitConversions -import org.joda.time.{DateTime, Duration, Period, PeriodType} +import org.joda.time._ import common._ @@ -32,16 +32,13 @@ import common._ object TimeHelpers extends TimeHelpers with ControlHelpers with ClassHelpers /** - * The TimeHelpers trait provide functions to create TimeSpans (an object representing an amount of time), to manage date formats - * or general utility functions (get the date for today, get year/month/day number,...) + * The TimeHelpers trait provide functions to create TimeSpans (an object representing duration in milliseconds), + * to manage date formats or general utility functions (get the date for today, get year/month/day number,...) */ trait TimeHelpers { self: ControlHelpers => // Logger must be lazy, since we cannot instantiate until after boot is complete private lazy val logger = Logger(classOf[TimeHelpers]) - /** private variable allowing the access to all TimeHelpers functions from inside the TimeSpan class */ - private val outer = this - /** transforms a long to a TimeSpanBuilder object. Usage: 3L.seconds returns a TimeSpan of 3000L millis */ implicit def longToTimeSpanBuilder(in: Long): TimeSpanBuilder = TimeSpanBuilder(in) @@ -54,96 +51,86 @@ trait TimeHelpers { self: ControlHelpers => /** transforms an int to a TimeSpan object. Usage: 3000 returns a TimeSpan of 3000L millis */ implicit def intToTimeSpan(in: Int): TimeSpan = TimeSpan(in) - private implicit def durToPeriod(dur: Duration): Period = dur.toPeriod(PeriodType.standard()) - /** class building TimeSpans given an amount (len) and a method specify the time unit */ case class TimeSpanBuilder(val len: Long) { - def seconds = new TimeSpan(Right((new Period).plusSeconds(len.toInt))) + def seconds = new TimeSpan(Left(Duration.standardSeconds(len))) def second = seconds - def minutes = new TimeSpan(Right((new Period).plusMinutes(len.toInt))) + def minutes = new TimeSpan(Left(Duration.standardMinutes(len))) def minute = minutes - def hours = new TimeSpan(Right(Duration.standardHours(len): Period)) + def hours = new TimeSpan(Left(Duration.standardHours(len))) def hour = hours - def days = new TimeSpan(Right(Duration.standardDays(len): Period)) + def days = new TimeSpan(Left(Duration.standardDays(len))) def day = days - def weeks = new TimeSpan(Right(Duration.standardDays(len * 7L): Period)) + def weeks = new TimeSpan(Left(Duration.standardDays(len * 7L))) def week = weeks - def months = new TimeSpan(Right((new Period().plusMonths(len.toInt)))) + @deprecated("TimeSpan will not support operations on non-millis periods in future") + def months = new TimeSpan(Right(new Period().plusMonths(len.toInt))) + @deprecated("TimeSpan will not support operations on non-millis periods in future") def month = months - def years = new TimeSpan(Right((new Period().plusYears(len.toInt)))) + @deprecated("TimeSpan will not support operations on non-millis periods in future") + def years = new TimeSpan(Right(new Period().plusYears(len.toInt))) + @deprecated("TimeSpan will not support operations on non-millis periods in future") def year = years } - /* /** - * transforms a TimeSpan to a date by converting the TimeSpan expressed as millis and creating - * a Date lasting that number of millis from the Epoch time (see the documentation for java.util.Date) + * The TimeSpan class represents Duration of time in milliseconds. + * + * For deprecated years and month builders it handle an operations on duration field values. Then it could be + * used only in to-period implicit conversion. */ - implicit def timeSpanToDate(in: TimeSpan): Date = in.date - - /** transforms a TimeSpan to its long value as millis */ - implicit def timeSpanToLong(in: TimeSpan): Long = in.millis - */ + class TimeSpan(private val dt: Either[Duration, Period]) extends ConvertableToDate { - /** - * The TimeSpan class represents an amount of time. - * It can be translated to a date with the date method. In that case, the number of millis seconds will be used to create a Date - * object starting from the Epoch time (see the documentation for java.util.Date) - */ - class TimeSpan(private val dt: Either[DateTime, Period]) extends ConvertableToDate { - /** @return a Date as the amount of time represented by the TimeSpan after the Epoch date */ def this(ms: Long) = - this(if (ms < 52L * 7L * 24L * 60L * 60L * 1000L) Right(new Period(ms)) - else Left(new DateTime(ms))) + this(Left(new Duration(ms))) - def date: Date = dt match { - case Left(datetime) => new Date(datetime.getMillis()) - case _ => new Date(millis) - } + /** + * Convert to a Date. The number of millis seconds will be used to create a Date object starting from the Epoch time. + */ + @deprecated("TimeSpan to Date conversion will be removed for possibility of mistakes in on-duration operations") + def date: Date = new Date(millis) /** - * Convert to a Date + * Convert to a Date. The number of millis seconds will be used to create a Date object starting from the Epoch time. */ + @deprecated("TimeSpan to Date conversion will be removed for possibility of mistakes in on-duration operations") def toDate: Date = date /** - * Convert to a JodaTime DateTime + * Convert to a JodaTime DateTime. The number of millis seconds will be used to create a Date object starting from the Epoch time. */ - def toDateTime = dt match { - case Left(datetime) => datetime - case _ => new DateTime(millis) + @deprecated("TimeSpan to DateTime conversion will be removed for possibility of mistakes in on-duration operations") + def toDateTime = new DateTime(millis) + + @deprecated("TimeSpan will not support operations on non-millis periods in future") + private[util] def toPeriod: Period = dt match { // package protected because of view bound usage in tsToPeriod + case Left(duration) => duration.toPeriod + case Right(period) => period } + /** + * @return amount of milliseconds in duration + * @throws UnsupportedOperationException if was created by deprecated months/years builder + */ def toMillis = millis + /** + * @return amount of milliseconds in duration + * @throws UnsupportedOperationException if was created by deprecated months/years builder + */ def millis = dt match { - case Left(datetime) => datetime.getMillis() - case Right(duration) => duration.toStandardDuration.getMillis() - } - - - /** @return a Date as the amount of time represented by the TimeSpan after now */ - def later: TimeSpan = dt match { - case Right(duration) => new TimeSpan(Left(new DateTime(outer.millis).plus(duration))) - case _ => TimeSpan(millis + outer.millis) + case Left(duration) => duration.getMillis + case Right(period) => period.toStandardDuration.getMillis // will throw exception because it holds month or year } - /** @return a Date as the amount of time represented by the TimeSpan before now */ - def ago: TimeSpan = dt match { - case Right(duration) => new TimeSpan(Left(new DateTime(outer.millis).minus(duration))) - case _ => TimeSpan(outer.millis - millis) - } - - def noTime: Date = new DateExtension(this).noTime - /** @return a TimeSpan representing the addition of 2 TimeSpans */ def +[B](in: B)(implicit f: B => TimeSpan): TimeSpan = (this.dt, f(in).dt) match { - case (Right(p1), Right(p2)) => p1.plus(p2) - case (Left(date), Right(duration)) => date.plus(duration) - case (Right(duration), Left(date)) => date.plus(duration) - case _ => TimeSpan(this.millis + f(in).millis) + case (Right(p1), Right(p2)) => new TimeSpan(Right(p1.plus(p2))) + case (Left(duration), Right(period)) => new TimeSpan(Left(duration.plus(period.toStandardDuration))) + case (Right(period), Left(duration)) => new TimeSpan(Left(period.toStandardDuration.plus(duration))) + case (Left(d1), Left(d2)) => new TimeSpan(Left(d1.plus(d2))) } /** @return a TimeSpan representing the addition of 2 TimeSpans */ @@ -152,10 +139,10 @@ trait TimeHelpers { self: ControlHelpers => /** @return a TimeSpan representing the substraction of 2 TimeSpans */ def -[B](in: B)(implicit f: B => TimeSpan): TimeSpan = (this.dt, f(in).dt) match { - case (Right(p1), Right(p2)) => p1.minus(p2) - case (Left(date), Right(duration)) => date.minus(duration) - case (Right(duration), Left(date)) => date.minus(duration) - case _ => TimeSpan(this.millis - f(in).millis) + case (Right(p1), Right(p2)) => new TimeSpan(Right(p1.minus(p2))) + case (Left(duration), Right(period)) => new TimeSpan(Left(duration.minus(period.toStandardDuration))) + case (Right(period), Left(duration)) => new TimeSpan(Left(period.toStandardDuration.minus(duration))) + case (Left(d1), Left(d2)) => new TimeSpan(Left(d1.minus(d2))) } /** override the equals method so that TimeSpans can be compared to long, int and TimeSpan */ @@ -164,19 +151,14 @@ trait TimeHelpers { self: ControlHelpers => case lo: Long => lo == this.millis case i: Int => i == this.millis case ti: TimeSpan => ti.dt == this.dt - case d: Date => d.getTime() == this.millis - case dt: DateTime => Left(dt) == this.dt - case dur: Duration => Right(dur: Period) == this.dt - case dur: Period => Right(dur) == this.dt + case dur: Duration => Left(dur) == this.dt + case period: Period => Right(period) == this.dt case _ => false } } /** override the toString method to display a readable amount of time */ - override def toString = dt match { - case Left(date) => date.toString - case Right(dur) => TimeSpan.format(millis) - } + override def toString = TimeSpan.format(millis) } /** @@ -208,26 +190,36 @@ trait TimeHelpers { self: ControlHelpers => /** * Convert a Date to a TimeSpan */ + @deprecated("Date to TimeSpan conversion will be removed for possibility of mistakes in on-duration operations") implicit def dateToTS(in: Date): TimeSpan = - new TimeSpan(Left(new DateTime(in.getTime))) + new TimeSpan(Left(new Duration(in.getTime))) - /** - * Convert a DateTime to a TimeSpan - */ - implicit def dateTimeToTS(in: DateTime): TimeSpan = - new TimeSpan(Left(in)) /** * Convert a Duration to a TimeSpan */ implicit def durationToTS(in: Duration): TimeSpan = - new TimeSpan(Right(in: Period)) + new TimeSpan(Left(in)) /** * Convert a Period to a TimeSpan */ + @deprecated("Period to TimeSpan conversion will be removed for possibility of mistakes in on-duration operations") implicit def periodToTS(in: Period): TimeSpan = new TimeSpan(Right(in)) + + /** + * Convert a TimeSpan to a Period + */ + @deprecated("TimeSpan to Period conversion will be removed for possibility of mistakes in on-duration operations") + implicit def tsToPeriod[TS <% TimeSpan](in: TS): Period = in.toPeriod + + /** + * Convert a DateTime to a TimeSpan + */ + @deprecated("DateTime to TimeSpan conversion will be removed for possibility of mistakes in on-duration operations") + implicit def dateTimeToTS(in: DateTime): TimeSpan = + new TimeSpan(Left(new Duration(in.getMillis))) } /** @return the current System.nanoTime() */ @@ -268,6 +260,11 @@ trait TimeHelpers { self: ControlHelpers => } } + + implicit class DateTimeExtension(dateTime: DateTime) { + def noTime = dateTime.withTimeAtStartOfDay() + } + /** implicit def used to add the setXXX methods to the Calendar class */ implicit def toCalendarExtension(c: Calendar) = new CalendarExtension(c) @@ -427,18 +424,29 @@ trait TimeHelpers { self: ControlHelpers => case e: Exception => logger.debug("Error parsing date "+in, e); Failure("Bad date: "+in, Full(e), Empty) } } + + implicit class PeriodExtension[P <% Period](period: P) { + def later: DateTime = new DateTime(millis).plus(period) + + def ago: DateTime = new DateTime(millis).minus(period) + } + } +@deprecated("TimeSpan to Date/DateTime conversion will be removed for possibility of mistakes in on-duration operations") trait ConvertableToDate { + @deprecated("TimeSpan to Date conversion will be removed for possibility of mistakes in on-duration operations") def toDate: Date + @deprecated("TimeSpan to DateTime conversion will be removed for possibility of mistakes in on-duration operations") def toDateTime: DateTime def millis: Long } +@deprecated("TimeSpan to Date/DateTime conversion will be removed for possibility of mistakes in on-duration operations") object ConvertableToDate { + @deprecated("TimeSpan to Date conversion will be removed for possibility of mistakes in on-duration operations") implicit def toDate(in: ConvertableToDate): Date = in.toDate + @deprecated("TimeSpan to DateTime conversion will be removed for possibility of mistakes in on-duration operations") implicit def toDateTime(in: ConvertableToDate): DateTime = in.toDateTime implicit def toMillis(in: ConvertableToDate): Long = in.millis - -} - +} \ No newline at end of file diff --git a/core/util/src/test/scala/net/liftweb/util/TimeHelpersSpec.scala b/core/util/src/test/scala/net/liftweb/util/TimeHelpersSpec.scala index 96fa10dda0..afeeb8f388 100644 --- a/core/util/src/test/scala/net/liftweb/util/TimeHelpersSpec.scala +++ b/core/util/src/test/scala/net/liftweb/util/TimeHelpersSpec.scala @@ -17,18 +17,18 @@ package net.liftweb package util -import java.util.{Calendar, Date} +import java.util.{Calendar, Date, TimeZone} -import org.specs2.mutable.Specification -import org.specs2.ScalaCheck -import org.specs2.time.NoTimeConversions +import net.liftweb.common._ +import net.liftweb.util.TimeHelpers._ +import org.joda.time.{DateTimeZone, DateTime} import org.scalacheck.Gen._ import org.scalacheck.Prop._ - -import common._ -import TimeHelpers._ - - +import org.specs2.ScalaCheck +import org.specs2.execute.AsResult +import org.specs2.matcher.MatchersImplicits +import org.specs2.mutable.{Around, Specification} +import org.specs2.time.NoTimeConversions /** * Systems under specification for TimeHelpers. @@ -37,68 +37,74 @@ object TimeHelpersSpec extends Specification with ScalaCheck with TimeAmountsGen "TimeHelpers Specification".title "A TimeSpan" can { - "be created from a number of milliseconds" in { + "be created from a number of milliseconds" in forAllTimeZones { TimeSpan(3000) must_== TimeSpan(3 * 1000) } - "be created from a number of seconds" in { + "be created from a number of seconds" in forAllTimeZones { 3.seconds must_== TimeSpan(3 * 1000) } - "be created from a number of minutes" in { + "be created from a number of minutes" in forAllTimeZones { 3.minutes must_== TimeSpan(3 * 60 * 1000) } - "be created from a number of hours" in { + "be created from a number of hours" in forAllTimeZones { 3.hours must_== TimeSpan(3 * 60 * 60 * 1000) } - "be created from a number of days" in { + "be created from a number of days" in forAllTimeZones { 3.days must_== TimeSpan(3 * 24 * 60 * 60 * 1000) } - "be created from a number of weeks" in { + "be created from a number of weeks" in forAllTimeZones { 3.weeks must_== TimeSpan(3 * 7 * 24 * 60 * 60 * 1000) } - "be converted implicitly to a date starting from the epoch time" in { + "be created from a number of months" in forAllTimeZones { + 3.months must_== 3.months + } + "be created from a number of years" in forAllTimeZones { + 3.years must_== 3.years + } + "be converted implicitly to a date starting from the epoch time" in forAllTimeZones { 3.seconds.after(new Date(0)) must beTrue } - "be converted to a date starting from the epoch time, using the date method" in { + "be converted to a date starting from the epoch time, using the date method" in forAllTimeZones { 3.seconds.after(new Date(0)) must beTrue } - "be implicitly converted to a Long" in { + "be implicitly converted to a Long" in forAllTimeZones { (3.seconds == 3000L) must_== true } - "be compared to an int" in { + "be compared to an int" in forAllTimeZones { (3.seconds == 3000) must_== true (3.seconds != 2000) must_== true } - "be compared to a long" in { + "be compared to a long" in forAllTimeZones { (3.seconds == 3000L) must_== true (3.seconds != 2000L) must_== true } - "be compared to another TimeSpan" in { + "be compared to another TimeSpan" in forAllTimeZones { 3.seconds must_== 3.seconds 3.seconds must_!= 2.seconds } - "be compared to another object" in { + "be compared to another object" in forAllTimeZones { 3.seconds must_!= "string" } } "A TimeSpan" should { - "return a new TimeSpan representing the sum of the 2 times when added with another TimeSpan" in { + "return a new TimeSpan representing the sum of the 2 times when added with another TimeSpan" in forAllTimeZones { 3.seconds + 3.seconds must_== 6.seconds } - "return a new TimeSpan representing the difference of the 2 times when substracted with another TimeSpan" in { + "return a new TimeSpan representing the difference of the 2 times when substracted with another TimeSpan" in forAllTimeZones { 3.seconds - 4.seconds must_== (-1).seconds } - "have a later method returning a date relative to now plus the time span" in { + "have a later method returning a date relative to now plus the time span" in forAllTimeZones { val expectedTime = new Date().getTime + 3.seconds.millis - 3.seconds.later.getTime must beCloseTo(expectedTime, 1000L) + 3.seconds.later.getMillis must beCloseTo(expectedTime, 1000L) } - "have an ago method returning a date relative to now minus the time span" in { + "have an ago method returning a date relative to now minus the time span" in forAllTimeZones { val expectedTime = new Date().getTime - 3.seconds.millis - 3.seconds.ago.getTime must beCloseTo(expectedTime, 1000L) + 3.seconds.ago.getMillis must beCloseTo(expectedTime, 1000L) } - "have a toString method returning the relevant number of weeks, days, hours, minutes, seconds, millis" in { + "have a toString method returning the relevant number of weeks, days, hours, minutes, seconds, millis" in forAllTimeZones { val conversionIsOk = forAll(timeAmounts)((t: TimeAmounts) => { val (timeSpanToString, timeSpanAmounts) = t timeSpanAmounts forall { case (amount, unit) => amount >= 1 && @@ -116,66 +122,66 @@ object TimeHelpersSpec extends Specification with ScalaCheck with TimeAmountsGen } "the TimeHelpers" should { - "provide a 'seconds' function transforming a number of seconds into millis" in { + "provide a 'seconds' function transforming a number of seconds into millis" in forAllTimeZones { seconds(3) must_== 3 * 1000 } - "provide a 'minutes' function transforming a number of minutes into millis" in { + "provide a 'minutes' function transforming a number of minutes into millis" in forAllTimeZones { minutes(3) must_== 3 * 60 * 1000 } - "provide a 'hours' function transforming a number of hours into milliss" in { + "provide a 'hours' function transforming a number of hours into milliss" in forAllTimeZones { hours(3) must_== 3 * 60 * 60 * 1000 } - "provide a 'days' function transforming a number of days into millis" in { + "provide a 'days' function transforming a number of days into millis" in forAllTimeZones { days(3) must_== 3 * 24 * 60 * 60 * 1000 } - "provide a 'weeks' function transforming a number of weeks into millis" in { + "provide a 'weeks' function transforming a number of weeks into millis" in forAllTimeZones { weeks(3) must_== 3 * 7 * 24 * 60 * 60 * 1000 } - "provide a noTime function on Date objects to transform a date into a date at the same day but at 00:00" in { + "provide a noTime function on Date objects to transform a date into a date at the same day but at 00:00" in forAllTimeZones { hourFormat(now.noTime) must_== "00:00:00" } - "make sure noTime does not change the day" in { - dateFormatter.format(0.days.ago.noTime) must_== dateFormatter.format(new Date()) - dateFormatter.format(3.days.ago.noTime) must_== dateFormatter.format(new Date(millis - (3 * 24 * 60 * 60 * 1000))) + "make sure noTime does not change the day" in forAllTimeZones { + dateFormatter.format(0.days.ago.noTime.toDate) must_== dateFormatter.format(new DateTime().toDate) + dateFormatter.format(3.days.ago.noTime.toDate) must_== dateFormatter.format(new Date(millis - (3 * 24 * 60 * 60 * 1000))) } - "provide a day function returning the day of month corresponding to a given date (relative to UTC)" in { + "provide a day function returning the day of month corresponding to a given date (relative to UTC)" in forAllTimeZones { day(today.setTimezone(utc).setDay(3).getTime) must_== 3 } - "provide a month function returning the month corresponding to a given date" in { + "provide a month function returning the month corresponding to a given date" in forAllTimeZones { month(today.setTimezone(utc).setMonth(4).getTime) must_== 4 } - "provide a year function returning the year corresponding to a given date" in { + "provide a year function returning the year corresponding to a given date" in forAllTimeZones { year(today.setTimezone(utc).setYear(2008).getTime) must_== 2008 } - "provide a millisToDays function returning the number of days since the epoch time" in { + "provide a millisToDays function returning the number of days since the epoch time" in forAllTimeZones { millisToDays(new Date(0).getTime) must_== 0 millisToDays(today.setYear(1970).setMonth(0).setDay(1).getTime.getTime) must_== 0 // the epoch time // on the 3rd day after the epoch time, 2 days are passed millisToDays(today.setTimezone(utc).setYear(1970).setMonth(0).setDay(3).getTime.getTime) must_== 2 } - "provide a daysSinceEpoch function returning the number of days since the epoch time" in { + "provide a daysSinceEpoch function returning the number of days since the epoch time" in forAllTimeZones { daysSinceEpoch must_== millisToDays(now.getTime) } - "provide a time function creating a new Date object from a number of millis" in { + "provide a time function creating a new Date object from a number of millis" in forAllTimeZones { time(1000) must_== new Date(1000) } - "provide a calcTime function returning the time taken to evaluate a block in millis and the block's result" in { + "provide a calcTime function returning the time taken to evaluate a block in millis and the block's result" in forAllTimeZones { val (time, result) = calcTime((1 to 10).reduceLeft[Int](_ + _)) time.toInt must beCloseTo(0, 1000) // it should take less than 1 second! result must_== 55 } - "provide a hourFormat function to format the time of a date object" in { + "provide a hourFormat function to format the time of a date object" in forAllTimeZones { hourFormat(Calendar.getInstance(utc).noTime.getTime) must_== "00:00:00" } - "provide a formattedDateNow function to format todays date" in { + "provide a formattedDateNow function to format todays date" in forAllTimeZones { formattedDateNow must beMatching("\\d\\d\\d\\d/\\d\\d/\\d\\d") } - "provide a formattedTimeNow function to format now's time with the TimeZone" in { - val regex = "\\d\\d:\\d\\d (....?|GMT((\\+|\\-)\\d\\d:00)?)" + "provide a formattedTimeNow function to format now's time with the TimeZone" in forAllTimeZones { + val regex = "\\d\\d:\\d\\d (....?.?|GMT((\\+|\\-)\\d\\d:\\d\\d)?)" "10:00 CEST" must beMatching(regex) "10:00 GMT+02:00" must beMatching(regex) "10:00 GMT" must beMatching(regex) @@ -183,16 +189,16 @@ object TimeHelpersSpec extends Specification with ScalaCheck with TimeAmountsGen formattedTimeNow must beMatching(regex) } - "provide a parseInternetDate function to parse a string formatted using the internet format" in { + "provide a parseInternetDate function to parse a string formatted using the internet format" in forAllTimeZones { parseInternetDate(internetDateFormatter.format(now)).getTime.toLong must beCloseTo(now.getTime.toLong, 1000L) } - "provide a parseInternetDate function returning new Date(0) if the input date cant be parsed" in { + "provide a parseInternetDate function returning new Date(0) if the input date cant be parsed" in forAllTimeZones { parseInternetDate("unparsable") must_== new Date(0) } - "provide a toInternetDate function formatting a date to the internet format" in { + "provide a toInternetDate function formatting a date to the internet format" in forAllTimeZones { toInternetDate(now) must beMatching("..., \\d* ... \\d\\d\\d\\d \\d\\d:\\d\\d:\\d\\d .*") } - "provide a toDate returning a Full(date) from many kinds of objects" in { + "provide a toDate returning a Full(date) from many kinds of objects" in forAllTimeZones { val d = now List(null, Nil, None, Failure("", Empty, Empty)) forall { toDate(_) must_== Empty } List(Full(d), Some(d), List(d)) forall { toDate(_) must_== Full(d) } @@ -205,24 +211,46 @@ object TimeHelpersSpec extends Specification with ScalaCheck with TimeAmountsGen } "The Calendar class" should { - "have a setDay method setting the day of month and returning the updated Calendar" in { + "have a setDay method setting the day of month and returning the updated Calendar" in forAllTimeZones { day(today.setTimezone(utc).setDay(1).getTime) must_== 1 } - "have a setMonth method setting the month and returning the updated Calendar" in { + "have a setMonth method setting the month and returning the updated Calendar" in forAllTimeZones { month(today.setTimezone(utc).setMonth(0).getTime) must_== 0 } - "have a setYear method setting the year and returning the updated Calendar" in { + "have a setYear method setting the year and returning the updated Calendar" in forAllTimeZones { year(today.setTimezone(utc).setYear(2008).getTime) must_== 2008 } - "have a setTimezone method to setting the time zone and returning the updated Calendar" in { + "have a setTimezone method to setting the time zone and returning the updated Calendar" in forAllTimeZones { today.setTimezone(utc).getTimeZone must_== utc } - "have a noTime method to setting the time to 00:00:00 and returning the updated Calendar" in { + "have a noTime method to setting the time to 00:00:00 and returning the updated Calendar" in forAllTimeZones { hourFormat(today.noTime.getTime) must_== "00:00:00" } } } +object forAllTimeZones extends Around with MatchersImplicits { + override def around[T: AsResult](f: => T) = synchronized { // setDefault is on static context so tests should be sequenced + import collection.convert.wrapAsScala._ + // some timezones for java (used in formatters) and for Joda (other computations) has other offset + val commonJavaAndJodaTimeZones = (TimeZone.getAvailableIDs.toSet & DateTimeZone.getAvailableIDs.toSet).filter { timeZoneId => + TimeZone.getTimeZone(timeZoneId).getOffset(millis) == DateTimeZone.forID(timeZoneId).getOffset(millis) + } + val tzBefore = TimeZone.getDefault + val dtzBefore = DateTimeZone.getDefault + try { + forall(commonJavaAndJodaTimeZones) { timeZoneId => + TimeZone.setDefault(TimeZone.getTimeZone(timeZoneId)) + DateTimeZone.setDefault(DateTimeZone.forID(timeZoneId)) + f + } + } finally { + TimeZone.setDefault(tzBefore) + DateTimeZone.setDefault(dtzBefore) + } + } +} + trait TimeAmountsGen { diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 5f57d17032..d0d1c94720 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -33,7 +33,7 @@ object Dependencies { lazy val commons_fileupload = "commons-fileupload" % "commons-fileupload" % "1.2.2" lazy val commons_httpclient = "commons-httpclient" % "commons-httpclient" % "3.1" lazy val javamail = "javax.mail" % "mail" % "1.4.4" - lazy val joda_time = "joda-time" % "joda-time" % "2.1" + lazy val joda_time = "joda-time" % "joda-time" % "2.6" lazy val joda_convert = "org.joda" % "joda-convert" % "1.2" lazy val htmlparser = "nu.validator.htmlparser" % "htmlparser" % "1.4" lazy val mongo_java_driver = "org.mongodb" % "mongo-java-driver" % "2.12.2" From aae78768c829192040bfb990b27070276901aad0 Mon Sep 17 00:00:00 2001 From: Arek Burdach Date: Wed, 14 Jan 2015 10:09:33 +0100 Subject: [PATCH 1117/1949] Expand and clarify deprecations - deprecations for Int/Long -> TimeSpan, TimeSpan -> Long conversion - deprecation since version notice added --- .../scala/net/liftweb/util/TimeHelpers.scala | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala b/core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala index c8547fc6fd..9ba93a1ae5 100644 --- a/core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala @@ -46,9 +46,11 @@ trait TimeHelpers { self: ControlHelpers => implicit def intToTimeSpanBuilder(in: Int): TimeSpanBuilder = TimeSpanBuilder(in) /** transforms a long to a TimeSpan object. Usage: 3000L returns a TimeSpan of 3000L millis */ + @deprecated("Long to TimeSpan conversion will be removed for possibility of ambiguous behaviours", "3.0.0") implicit def longToTimeSpan(in: Long): TimeSpan = TimeSpan(in) /** transforms an int to a TimeSpan object. Usage: 3000 returns a TimeSpan of 3000L millis */ + @deprecated("Int to TimeSpan conversion will be removed for possibility of ambiguous behaviours", "3.0.0") implicit def intToTimeSpan(in: Int): TimeSpan = TimeSpan(in) /** class building TimeSpans given an amount (len) and a method specify the time unit */ @@ -63,13 +65,13 @@ trait TimeHelpers { self: ControlHelpers => def day = days def weeks = new TimeSpan(Left(Duration.standardDays(len * 7L))) def week = weeks - @deprecated("TimeSpan will not support operations on non-millis periods in future") + @deprecated("TimeSpan will not support operations on non-millis periods in future", "3.0.0") def months = new TimeSpan(Right(new Period().plusMonths(len.toInt))) - @deprecated("TimeSpan will not support operations on non-millis periods in future") + @deprecated("TimeSpan will not support operations on non-millis periods in future", "3.0.0") def month = months - @deprecated("TimeSpan will not support operations on non-millis periods in future") + @deprecated("TimeSpan will not support operations on non-millis periods in future", "3.0.0") def years = new TimeSpan(Right(new Period().plusYears(len.toInt))) - @deprecated("TimeSpan will not support operations on non-millis periods in future") + @deprecated("TimeSpan will not support operations on non-millis periods in future", "3.0.0") def year = years } @@ -88,22 +90,22 @@ trait TimeHelpers { self: ControlHelpers => /** * Convert to a Date. The number of millis seconds will be used to create a Date object starting from the Epoch time. */ - @deprecated("TimeSpan to Date conversion will be removed for possibility of mistakes in on-duration operations") + @deprecated("TimeSpan to Date conversion will be removed for possibility of mistakes in on-duration operations", "3.0.0") def date: Date = new Date(millis) /** * Convert to a Date. The number of millis seconds will be used to create a Date object starting from the Epoch time. */ - @deprecated("TimeSpan to Date conversion will be removed for possibility of mistakes in on-duration operations") + @deprecated("TimeSpan to Date conversion will be removed for possibility of mistakes in on-duration operations", "3.0.0") def toDate: Date = date /** * Convert to a JodaTime DateTime. The number of millis seconds will be used to create a Date object starting from the Epoch time. */ - @deprecated("TimeSpan to DateTime conversion will be removed for possibility of mistakes in on-duration operations") + @deprecated("TimeSpan to DateTime conversion will be removed for possibility of mistakes in on-duration operations", "3.0.0") def toDateTime = new DateTime(millis) - @deprecated("TimeSpan will not support operations on non-millis periods in future") + @deprecated("TimeSpan will not support operations on non-millis periods in future", "3.0.0") private[util] def toPeriod: Period = dt match { // package protected because of view bound usage in tsToPeriod case Left(duration) => duration.toPeriod case Right(period) => period @@ -190,7 +192,7 @@ trait TimeHelpers { self: ControlHelpers => /** * Convert a Date to a TimeSpan */ - @deprecated("Date to TimeSpan conversion will be removed for possibility of mistakes in on-duration operations") + @deprecated("Date to TimeSpan conversion will be removed for possibility of mistakes in on-duration operations", "3.0.0") implicit def dateToTS(in: Date): TimeSpan = new TimeSpan(Left(new Duration(in.getTime))) @@ -204,20 +206,20 @@ trait TimeHelpers { self: ControlHelpers => /** * Convert a Period to a TimeSpan */ - @deprecated("Period to TimeSpan conversion will be removed for possibility of mistakes in on-duration operations") + @deprecated("Period to TimeSpan conversion will be removed for possibility of mistakes in on-duration operations", "3.0.0") implicit def periodToTS(in: Period): TimeSpan = new TimeSpan(Right(in)) /** * Convert a TimeSpan to a Period */ - @deprecated("TimeSpan to Period conversion will be removed for possibility of mistakes in on-duration operations") + @deprecated("TimeSpan to Period conversion will be removed for possibility of mistakes in on-duration operations", "3.0.0") implicit def tsToPeriod[TS <% TimeSpan](in: TS): Period = in.toPeriod /** * Convert a DateTime to a TimeSpan */ - @deprecated("DateTime to TimeSpan conversion will be removed for possibility of mistakes in on-duration operations") + @deprecated("DateTime to TimeSpan conversion will be removed for possibility of mistakes in on-duration operations", "3.0.0") implicit def dateTimeToTS(in: DateTime): TimeSpan = new TimeSpan(Left(new Duration(in.getMillis))) } @@ -433,20 +435,22 @@ trait TimeHelpers { self: ControlHelpers => } -@deprecated("TimeSpan to Date/DateTime conversion will be removed for possibility of mistakes in on-duration operations") +@deprecated("TimeSpan to Date/DateTime conversion will be removed for possibility of mistakes in on-duration operations", "3.0.0") trait ConvertableToDate { - @deprecated("TimeSpan to Date conversion will be removed for possibility of mistakes in on-duration operations") + @deprecated("TimeSpan to Date conversion will be removed for possibility of mistakes in on-duration operations", "3.0.0") def toDate: Date - @deprecated("TimeSpan to DateTime conversion will be removed for possibility of mistakes in on-duration operations") + @deprecated("TimeSpan to DateTime conversion will be removed for possibility of mistakes in on-duration operations", "3.0.0") def toDateTime: DateTime + @deprecated("TimeSpan to Long conversion will be removed for possibility of ambiguous behaviours", "3.0.0") def millis: Long } -@deprecated("TimeSpan to Date/DateTime conversion will be removed for possibility of mistakes in on-duration operations") +@deprecated("TimeSpan to Date/DateTime conversion will be removed for possibility of mistakes in on-duration operations", "3.0.0") object ConvertableToDate { - @deprecated("TimeSpan to Date conversion will be removed for possibility of mistakes in on-duration operations") + @deprecated("TimeSpan to Date conversion will be removed for possibility of mistakes in on-duration operations", "3.0.0") implicit def toDate(in: ConvertableToDate): Date = in.toDate - @deprecated("TimeSpan to DateTime conversion will be removed for possibility of mistakes in on-duration operations") + @deprecated("TimeSpan to DateTime conversion will be removed for possibility of mistakes in on-duration operations", "3.0.0") implicit def toDateTime(in: ConvertableToDate): DateTime = in.toDateTime + @deprecated("TimeSpan to Long conversion will be removed for possibility of ambiguous behaviours", "3.0.0") implicit def toMillis(in: ConvertableToDate): Long = in.millis } \ No newline at end of file From 52bf6f5663c2c43c099f02e04e7bc1940051d3e0 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 17 Jan 2015 19:19:33 -0500 Subject: [PATCH 1118/1949] Clarify/expand on scaladocs for TimeSpan methods. --- .../scala/net/liftweb/util/TimeHelpers.scala | 123 +++++++++++++++--- 1 file changed, 107 insertions(+), 16 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala b/core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala index 9ba93a1ae5..f5373dbe17 100644 --- a/core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala @@ -76,31 +76,51 @@ trait TimeHelpers { self: ControlHelpers => } /** - * The TimeSpan class represents Duration of time in milliseconds. + * The `TimeSpan` class represents a duration of time in milliseconds. In this + * way, it is similar to the `[[scala.concurrent.Duration]]` class. It is + * mostly used in Lift APIs in similar positions as the Scala `Duration` + * class (for example, in event scheduling). * - * For deprecated years and month builders it handle an operations on duration field values. Then it could be - * used only in to-period implicit conversion. + * Unlike in the Lift 2.x series, building a `TimeSpan` with a `Long` will not + * have different behavior depending on the value passed. Any passed `Long` + * will be used as a duration. + * + * Prior to Lift 3.0, `TimeSpan` was an amalgam of duration and joda + * `DateTime`, and allowed conversions between the two. As a result, + * operational semantics were poorly defined and it was easy to call a method + * that seemed like it should have simple duration semantics but run into + * `DateTime` semantics that made things more complicated instead. + * + * Lift 3.0 mostly maintains API compatibility with the Lift 2.x series, but + * introduces a series of deprecations to indicate places where dangerous + * and potentially unclear behavior may occur. Lift 3.1 will maintain API + * compatibility with all non-deprecated parts of the `TimeSpan` API, but will + * remove the deprecated aspects. + * + * For deprecated years and month builders it handle an operations on duration + * field values. Then it could be used only in to-period implicit conversion. */ class TimeSpan(private val dt: Either[Duration, Period]) extends ConvertableToDate { - def this(ms: Long) = this(Left(new Duration(ms))) /** - * Convert to a Date. The number of millis seconds will be used to create a Date object starting from the Epoch time. + * Convert to a Java `Date`. The number of milliseconds in the `Duration` + * will be added to the UNIX epoch to create a `Date` object. */ @deprecated("TimeSpan to Date conversion will be removed for possibility of mistakes in on-duration operations", "3.0.0") def date: Date = new Date(millis) /** - * Convert to a Date. The number of millis seconds will be used to create a Date object starting from the Epoch time. + * Convert to a Java `Date`. Synonym of `[[date]]`. */ @deprecated("TimeSpan to Date conversion will be removed for possibility of mistakes in on-duration operations", "3.0.0") def toDate: Date = date /** - * Convert to a JodaTime DateTime. The number of millis seconds will be used to create a Date object starting from the Epoch time. + * Convert to a Joda-Time `DateTime`. The number of milliseconds in the `Duration` + * will be added to the UNIX epoch to create a `DateTime` object. */ @deprecated("TimeSpan to DateTime conversion will be removed for possibility of mistakes in on-duration operations", "3.0.0") def toDateTime = new DateTime(millis) @@ -112,21 +132,53 @@ trait TimeHelpers { self: ControlHelpers => } /** - * @return amount of milliseconds in duration - * @throws UnsupportedOperationException if was created by deprecated months/years builder + * @return The amount of milliseconds this `TimeSpan` represents. + * @throws UnsupportedOperationException When created by the deprecated + * months/years builder (month and year lengths in milliseconds + * are only defined with respect to a reference point, since the + * length of a month or year can vary). */ def toMillis = millis /** - * @return amount of milliseconds in duration - * @throws UnsupportedOperationException if was created by deprecated months/years builder + * @return The amount of milliseconds this `TimeSpan` represents. + * @throws UnsupportedOperationException When created by the deprecated months/years builder ( + * month and year lengths in milliseconds are only defined with respect to a reference point, + * since the length of a month or year can vary). */ def millis = dt match { case Left(duration) => duration.getMillis case Right(period) => period.toStandardDuration.getMillis // will throw exception because it holds month or year } - /** @return a TimeSpan representing the addition of 2 TimeSpans */ + // TODO If we choose to move away from TimeSpan, we'll need to take into + // TODO account the fact that this method can take anything convertible to + // TODO a TimeSpan, so we'll need at least one release where TimeSpan is + // TODO around for the purposes of these implicit conversions in case client + // TODO code defines one. + /** + * Sums this `TimeSpan` with an object that can be converted to a + * `TimeSpan`. If either `TimeSpan` represents a `Duration`, add the + * `Duration`s directly. If both `TimeSpan`s represents a `Period` (which is + * deprecated behavior), adds them using `Period` addition. + * + * @note Adding two `TimeSpan`s where one of the two was constructed using the + * deprecated `months` or `years` builders will throw an exception. + * @note Adding two `TimeSpan`s where both were constructed using the + * deprecated `months` or `years` builders will result in a `TimeSpan` + * representing a `Period`. These `TimeSpan`s can behave in unexpected + * ways, including throwing exceptions when their millisecond duration + * is required. + * + * @return A `TimeSpan` representing the sum of this span and `in`'s + * `TimeSpan` representation. + * @throws UnsupportedOperationException If only one of the two `TimeSpan`s + * represents a `Period` and that `Period` has a year or month + * component (this only occurs if the deprecated `months` or + * `years` builders are used, as month and year lengths in + * milliseconds are only defined with respect to a reference point, + * since the length of a month or year can vary) + */ def +[B](in: B)(implicit f: B => TimeSpan): TimeSpan = (this.dt, f(in).dt) match { case (Right(p1), Right(p2)) => new TimeSpan(Right(p1.plus(p2))) @@ -135,10 +187,40 @@ trait TimeHelpers { self: ControlHelpers => case (Left(d1), Left(d2)) => new TimeSpan(Left(d1.plus(d2))) } - /** @return a TimeSpan representing the addition of 2 TimeSpans */ + /** + * Alias for `[[+]]`. + */ def plus[B](in: B)(implicit f: B => TimeSpan): TimeSpan = this.+(in)(f) - /** @return a TimeSpan representing the substraction of 2 TimeSpans */ + // TODO If we choose to move away from TimeSpan, we'll need to take into + // TODO account the fact that this method can take anything convertible to + // TODO a TimeSpan, so we'll need at least one release where TimeSpan is + // TODO around for the purposes of these implicit conversions in case client + // TODO code defines one. + /** + * Subtracts an object that can be converted to a `TimeSpan` from this + * `TimeSpan`. If either `TimeSpan` represents a `Duration`, subtracts the + * `Duration`s directly. If both `TimeSpan`s represents a `Period` (which is + * deprecated behavior), subtracts them using `Period` subtraction. + * + * @note Subtracting two `TimeSpan`s where one of the two was constructed + * using the deprecated `months` or `years` builders will throw an + * exception. + * @note Subtracting two `TimeSpan`s where both were constructed using the + * deprecated `months` or `years` builders will result in a `TimeSpan` + * representing a `Period`. These `TimeSpan`s can behave in unexpected + * ways, including throwing exceptions when their millisecond duration + * is required. + * + * @return A `TimeSpan` representing the sum of this span and `in`'s + * `TimeSpan` representation. + * @throws UnsupportedOperationException If only one of the two `TimeSpan`s + * represents a `Period` and that `Period` has a year or month + * component (this only occurs if the deprecated `months` or + * `years` builders are used, as month and year lengths in + * milliseconds are only defined with respect to a reference point, + * since the length of a month or year can vary) + */ def -[B](in: B)(implicit f: B => TimeSpan): TimeSpan = (this.dt, f(in).dt) match { case (Right(p1), Right(p2)) => new TimeSpan(Right(p1.minus(p2))) @@ -147,7 +229,13 @@ trait TimeHelpers { self: ControlHelpers => case (Left(d1), Left(d2)) => new TimeSpan(Left(d1.minus(d2))) } - /** override the equals method so that TimeSpans can be compared to long, int and TimeSpan */ + /** + * Override the equals method so that `TimeSpan`s can be compared to long, int, + * Joda-Time `Duration`, and `TimeSpan`. + * + * @note Comparing to a Joda-Time `Period` is also done correctly, but is + * deprecated. + */ override def equals(cmp: Any) = { cmp match { case lo: Long => lo == this.millis @@ -159,7 +247,10 @@ trait TimeHelpers { self: ControlHelpers => } } - /** override the toString method to display a readable amount of time */ + /** + * Override the toString method to display a readable amount of time using + * `[[TimeSpan.format]]`` + */ override def toString = TimeSpan.format(millis) } From f6b3cc87ba0867bafffcd25bec48313094b872a1 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 17 Jan 2015 19:24:59 -0500 Subject: [PATCH 1119/1949] Reword TimeSpan deprecations. They all include suggested alternatives now. --- .../scala/net/liftweb/util/TimeHelpers.scala | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala b/core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala index f5373dbe17..8b478653bf 100644 --- a/core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala @@ -65,13 +65,13 @@ trait TimeHelpers { self: ControlHelpers => def day = days def weeks = new TimeSpan(Left(Duration.standardDays(len * 7L))) def week = weeks - @deprecated("TimeSpan will not support operations on non-millis periods in future", "3.0.0") + @deprecated("This builder will be removed due to its unclear behavior; use Joda-Time `Period.months` and convert to `TimeSpan` manually instead.", "3.0.0") def months = new TimeSpan(Right(new Period().plusMonths(len.toInt))) - @deprecated("TimeSpan will not support operations on non-millis periods in future", "3.0.0") + @deprecated("This builder will be removed due to its unclear behavior; use Joda-Time `Period.months` and convert to `TimeSpan` manually instead.", "3.0.0") def month = months - @deprecated("TimeSpan will not support operations on non-millis periods in future", "3.0.0") + @deprecated("This builder will be removed due to its unclear behavior; use Joda-Time `Period.years` and convert to `TimeSpan` manually instead.", "3.0.0") def years = new TimeSpan(Right(new Period().plusYears(len.toInt))) - @deprecated("TimeSpan will not support operations on non-millis periods in future", "3.0.0") + @deprecated("This builder will be removed due to its unclear behavior; use Joda-Time `Period.years` and convert to `TimeSpan` manually instead.", "3.0.0") def year = years } @@ -109,23 +109,23 @@ trait TimeHelpers { self: ControlHelpers => * Convert to a Java `Date`. The number of milliseconds in the `Duration` * will be added to the UNIX epoch to create a `Date` object. */ - @deprecated("TimeSpan to Date conversion will be removed for possibility of mistakes in on-duration operations", "3.0.0") + @deprecated("This method will be removed due to its unclear behavior; use new Date(timeSpan.millis) instead.", "3.0.0") def date: Date = new Date(millis) /** * Convert to a Java `Date`. Synonym of `[[date]]`. */ - @deprecated("TimeSpan to Date conversion will be removed for possibility of mistakes in on-duration operations", "3.0.0") + @deprecated("This method will be removed due to its unclear behavior; use new Date(timeSpan.millis) instead.", "3.0.0") def toDate: Date = date /** * Convert to a Joda-Time `DateTime`. The number of milliseconds in the `Duration` * will be added to the UNIX epoch to create a `DateTime` object. */ - @deprecated("TimeSpan to DateTime conversion will be removed for possibility of mistakes in on-duration operations", "3.0.0") + @deprecated("This method will be removed due to its unclear behavior; use new DateTime(timeSpan.millis) instead.", "3.0.0") def toDateTime = new DateTime(millis) - @deprecated("TimeSpan will not support operations on non-millis periods in future", "3.0.0") + @deprecated("TimeSpan will not support operations on Joda-Time `Period`s in the future; use new Period(timeSpan.millis) instead.", "3.0.0") private[util] def toPeriod: Period = dt match { // package protected because of view bound usage in tsToPeriod case Left(duration) => duration.toPeriod case Right(period) => period @@ -297,20 +297,20 @@ trait TimeHelpers { self: ControlHelpers => /** * Convert a Period to a TimeSpan */ - @deprecated("Period to TimeSpan conversion will be removed for possibility of mistakes in on-duration operations", "3.0.0") + @deprecated("Implicit conversion from Period to TimeSpan will be removed due to its unclear behavior; use new Period(timeSpan.millis) instead.", "3.0.0") implicit def periodToTS(in: Period): TimeSpan = new TimeSpan(Right(in)) /** * Convert a TimeSpan to a Period */ - @deprecated("TimeSpan to Period conversion will be removed for possibility of mistakes in on-duration operations", "3.0.0") + @deprecated("Implicit conversion from TimeSpan to Period will be removed due to its unclear behavior; use new TimeSpan(period.toDurationFrom(startDateTime)) instead.", "3.0.0") implicit def tsToPeriod[TS <% TimeSpan](in: TS): Period = in.toPeriod /** * Convert a DateTime to a TimeSpan */ - @deprecated("DateTime to TimeSpan conversion will be removed for possibility of mistakes in on-duration operations", "3.0.0") + @deprecated("Implicit conversion from DateTime to TimeSpan will be removed due to its unclear behavior; use new TimeSpan(dateTime.getMillis) instead.", "3.0.0") implicit def dateTimeToTS(in: DateTime): TimeSpan = new TimeSpan(Left(new Duration(in.getMillis))) } From 1e5fead8eee099b98a272a06bcbf335e91b06f2e Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 17 Jan 2015 19:30:08 -0500 Subject: [PATCH 1120/1949] Drop ConvertableToDate deprecations. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These aren’t directly related to TimeSpans, and we need to figure out how exactly we want these deprecations to work first. --- .../src/main/scala/net/liftweb/util/TimeHelpers.scala | 8 -------- 1 file changed, 8 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala b/core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala index 8b478653bf..7fe95651b5 100644 --- a/core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/TimeHelpers.scala @@ -526,22 +526,14 @@ trait TimeHelpers { self: ControlHelpers => } -@deprecated("TimeSpan to Date/DateTime conversion will be removed for possibility of mistakes in on-duration operations", "3.0.0") trait ConvertableToDate { - @deprecated("TimeSpan to Date conversion will be removed for possibility of mistakes in on-duration operations", "3.0.0") def toDate: Date - @deprecated("TimeSpan to DateTime conversion will be removed for possibility of mistakes in on-duration operations", "3.0.0") def toDateTime: DateTime - @deprecated("TimeSpan to Long conversion will be removed for possibility of ambiguous behaviours", "3.0.0") def millis: Long } -@deprecated("TimeSpan to Date/DateTime conversion will be removed for possibility of mistakes in on-duration operations", "3.0.0") object ConvertableToDate { - @deprecated("TimeSpan to Date conversion will be removed for possibility of mistakes in on-duration operations", "3.0.0") implicit def toDate(in: ConvertableToDate): Date = in.toDate - @deprecated("TimeSpan to DateTime conversion will be removed for possibility of mistakes in on-duration operations", "3.0.0") implicit def toDateTime(in: ConvertableToDate): DateTime = in.toDateTime - @deprecated("TimeSpan to Long conversion will be removed for possibility of ambiguous behaviours", "3.0.0") implicit def toMillis(in: ConvertableToDate): Long = in.millis } \ No newline at end of file From efc7a15bbaaccef19accd6cc3134c3f9658dd095 Mon Sep 17 00:00:00 2001 From: Diego Medina Date: Sat, 17 Jan 2015 20:58:25 -0500 Subject: [PATCH 1121/1949] update build script to build 3.0 from master --- unsafePublishLift.sh | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/unsafePublishLift.sh b/unsafePublishLift.sh index 5bdedc111e..161ee87439 100755 --- a/unsafePublishLift.sh +++ b/unsafePublishLift.sh @@ -120,8 +120,8 @@ for MODULE in framework ; do CURRENT_BRANCH=`git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/\1/'` debug "Current branch for $MODULE is $CURRENT_BRANCH" - if [ "${CURRENT_BRANCH}" != "lift_26" ]; then - echo "Currently releases can only be built from lift_26. $MODULE is on branch $CURRENT_BRANCH. Aborting build." + if [ "${CURRENT_BRANCH}" != "master" ]; then + echo "Currently releases can only be built from master. $MODULE is on branch $CURRENT_BRANCH. Aborting build." exit fi @@ -155,18 +155,7 @@ for MODULE in framework ; do #git push origin ${RELEASE_VERSION}-release >> ${BUILDLOG} || die "Could not push release tag!" # Do a separate build for each configured Scala version so we don't blow the heap - # We have one project for scala versions pre 2.11.x - for SCALA_VERSION in 2.9.1 2.9.1-1 2.9.2 2.10.4 ; do - echo -n " Building against Scala ${SCALA_VERSION}..." - if ! ./liftsh ++${SCALA_VERSION} "project lift-framework-pre-211" clean update test publishSigned >> ${BUILDLOG} ; then - echo "failed! See build log for details" - exit - fi - echo "complete" - done - - #and we have another for >= 2.11.x - for SCALA_VERSION in 2.11.1; do + for SCALA_VERSION in $(grep crossScalaVersions build.sbt | cut -d '(' -f 2 | sed s/[,\)\"]//g ); do echo -n " Building against Scala ${SCALA_VERSION}..." if ! ./liftsh ++${SCALA_VERSION} clean update test publishSigned >> ${BUILDLOG} ; then echo "failed! See build log for details" @@ -175,14 +164,11 @@ for MODULE in framework ; do echo "complete" done - echo "Build complete for module ${MODULE}" done echo -e "\n\nRelease complete!" -echo -e "\n\nPlease update the lift_sbt_2.6 templates!" -echo -e "\n\nand write something about this release on the liftweb.net site." - - - +echo -e "\n\nPlease update the lift_30_sbt templates!" +echo -e "\n\nwrite something about this release on the liftweb.net site." +echo -e "\n\nand if all went well, push tags to github" From 61b43f9fc11f930a14f107d36e3452fa30975380 Mon Sep 17 00:00:00 2001 From: Arek Burdach Date: Mon, 5 Jan 2015 17:54:58 +0100 Subject: [PATCH 1122/1949] travis integration: - build script - build status in README --- .travis.yml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..3f45b93318 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,7 @@ +services: + - mongodb + +jdk: + - oraclejdk7 + +script: ./liftsh test \ No newline at end of file From 0652dcdd018935f4e0fc55c236aeebd2e83541ad Mon Sep 17 00:00:00 2001 From: Arek Burdach Date: Mon, 5 Jan 2015 23:21:46 +0100 Subject: [PATCH 1123/1949] openjdk7 profile --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 3f45b93318..97cfc95cf2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,5 +3,6 @@ services: jdk: - oraclejdk7 + - openjdk7 script: ./liftsh test \ No newline at end of file From 68de581a4f340a904f521ae08d39cba8d90d10b9 Mon Sep 17 00:00:00 2001 From: Andreas Joseph Krogh Date: Wed, 21 Jan 2015 15:19:05 +0100 Subject: [PATCH 1124/1949] Fixed NPE in LiftRules:913 --- web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index c850d8f9c6..24d1ad6c82 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -910,7 +910,7 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { case snippetName :: encodedArguments => val decodedMetaData = pairsToMetaData(encodedArguments.flatMap(_.roboSplit("[;&]"))) - if (decodedMetaData("parallel").headOption == Some(Text("true"))) { + if (decodedMetaData.get("parallel").headOption == Some(Text("true"))) { DataAttributeProcessorAnswerFuture(LAFuture(() => new Elem("lift", snippetName, decodedMetaData, element.scope, false, element) )) From 9ba83c304ff9945bf40295a59bc4208abb60926e Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Fri, 23 Jan 2015 00:09:28 -0500 Subject: [PATCH 1125/1949] Ensure uniqueness of comet requests. We now track a "current count" for a comet request, and any calls to start a request carry the then-current count. Once a comet request is fired, the count is incremented, ensuring that even if a new comet request is scheduled multiple times, only one will run at any given moment. This is only the new part of the guard. The other part of the guard is that we track the actual current comet request and abort it if a comet request restart is needed. However, an AJAX request that was just begun cannot be aborted, so if multiple restarts are scheduled in one tick for whatever reason, we end up with multiple requests--this fix guards against that scneario. --- web/webkit/src/main/resources/toserve/lift.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/web/webkit/src/main/resources/toserve/lift.js b/web/webkit/src/main/resources/toserve/lift.js index a37b6e0186..52dbc1b166 100644 --- a/web/webkit/src/main/resources/toserve/lift.js +++ b/web/webkit/src/main/resources/toserve/lift.js @@ -293,7 +293,9 @@ ///// Comet //////////////////////////////////// //////////////////////////////////////////////// - var currentCometRequest = null; + var currentCometRequest = null, + // Used to ensure that we can only fire one comet request at a time. + cometRequestCount = 0; // http://stackoverflow.com/questions/4994201/is-object-empty function is_empty(obj) { @@ -320,11 +322,13 @@ } function cometFailureFunc() { - setTimeout(cometEntry, settings.cometFailureRetryTimeout); + var requestCount = cometRequestCount; + setTimeout(function() { cometEntry(requestCount) }, settings.cometFailureRetryTimeout); } function cometSuccessFunc() { - setTimeout(cometEntry, 100); + var requestCount = cometRequestCount; + setTimeout(function() { cometEntry(requestCount); }, 100); } function calcCometPath() { @@ -345,11 +349,12 @@ cometSuccessFunc(); } - function cometEntry() { + function cometEntry(requestedCount) { var isEmpty = is_empty(toWatch); - if (!isEmpty) { + if (!isEmpty && requestedCount == cometRequestCount) { uriSuffix = undefined; + cometRequestCount++; currentCometRequest = settings.ajaxGet( calcCometPath(), From 8c84b469c572d8c8cd3ec6e9f249017ce3abb224 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Fri, 23 Jan 2015 00:11:57 -0500 Subject: [PATCH 1126/1949] Fix a misnamed parameter in registerComet. The paramter was named restartComet, but restartComet is the name of the restart function, while startComet is the correct name for the parameter. --- web/webkit/src/main/resources/toserve/lift.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/webkit/src/main/resources/toserve/lift.js b/web/webkit/src/main/resources/toserve/lift.js index 52dbc1b166..e451420e6a 100644 --- a/web/webkit/src/main/resources/toserve/lift.js +++ b/web/webkit/src/main/resources/toserve/lift.js @@ -378,7 +378,7 @@ // Called to register a comet. Sets the comet with guid `cometGuid` // to have the version `cometVersion`. If `startComet` is set to // `true`, restarts the comet request cycle. - function registerComet(cometGuid, cometVersion, restartComet) { + function registerComet(cometGuid, cometVersion, startComet) { toWatch[cometGuid] = cometVersion; if (startComet) { From 16e23d79a13061ba5e395512031909ca2f75a0ed Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 25 Jan 2015 15:49:39 -0500 Subject: [PATCH 1127/1949] Fixed an == vs === issue codacy caught in JS. --- web/webkit/src/main/resources/toserve/lift.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/webkit/src/main/resources/toserve/lift.js b/web/webkit/src/main/resources/toserve/lift.js index e451420e6a..4dd22c297a 100644 --- a/web/webkit/src/main/resources/toserve/lift.js +++ b/web/webkit/src/main/resources/toserve/lift.js @@ -352,7 +352,7 @@ function cometEntry(requestedCount) { var isEmpty = is_empty(toWatch); - if (!isEmpty && requestedCount == cometRequestCount) { + if (!isEmpty && requestedCount === cometRequestCount) { uriSuffix = undefined; cometRequestCount++; currentCometRequest = From 58c4d0b427294a6170ad1aa882a313a865d24f4c Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 25 Jan 2015 15:49:54 -0500 Subject: [PATCH 1128/1949] Dropped an unused function in lift.js. --- web/webkit/src/main/resources/toserve/lift.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/web/webkit/src/main/resources/toserve/lift.js b/web/webkit/src/main/resources/toserve/lift.js index 4dd22c297a..46bb2e69f7 100644 --- a/web/webkit/src/main/resources/toserve/lift.js +++ b/web/webkit/src/main/resources/toserve/lift.js @@ -375,17 +375,6 @@ toWatch = ret; } - // Called to register a comet. Sets the comet with guid `cometGuid` - // to have the version `cometVersion`. If `startComet` is set to - // `true`, restarts the comet request cycle. - function registerComet(cometGuid, cometVersion, startComet) { - toWatch[cometGuid] = cometVersion; - - if (startComet) { - restartComet(); - } - } - // Called to register comets in bulk. `cometInfo` should be // an object of comet ids associated with comet versions. // From 6ed96fc746a9e843aed0725a8e82c4736194adc9 Mon Sep 17 00:00:00 2001 From: Andreas Joseph Krogh Date: Mon, 26 Jan 2015 00:30:22 +0100 Subject: [PATCH 1129/1949] Fixed premature termination of declaration-list. --- web/webkit/src/main/resources/toserve/lift.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/webkit/src/main/resources/toserve/lift.js b/web/webkit/src/main/resources/toserve/lift.js index 46bb2e69f7..8ad992d0df 100644 --- a/web/webkit/src/main/resources/toserve/lift.js +++ b/web/webkit/src/main/resources/toserve/lift.js @@ -702,7 +702,7 @@ onDocumentReady: function(fn) { var done = false, top = true, win = window, doc = win.document, root = doc.documentElement, - pre = doc.addEventListener ? '' : 'on'; + pre = doc.addEventListener ? '' : 'on', rem = doc.addEventListener ? 'removeEventListener' : 'detachEvent', init = function(e) { From d3710e4b67b1773817dd1a43e894a020519b9132 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 25 Jan 2015 17:17:30 -0500 Subject: [PATCH 1130/1949] Add SecurityRules and related code. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SecurityRules provides a way to set security rules like HTTPS requirements and a content security policy, which are in turn served with resources from Lift via headers. Right now, we support Content-Security-Policy and Strict-Transport-Security headers. While a default reporting URI is in place for content security policy violations, there’s not yet any code that handles information sent to that URI. --- .../scala/net/liftweb/http/LiftRules.scala | 28 +- .../net/liftweb/http/SecurityRules.scala | 373 ++++++++++++++++++ 2 files changed, 396 insertions(+), 5 deletions(-) create mode 100644 web/webkit/src/main/scala/net/liftweb/http/SecurityRules.scala diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 24d1ad6c82..84f29a820a 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -264,6 +264,14 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { */ val beforeSend = RulesSeq[(BasicResponse, HTTPResponse, List[(String, String)], Box[Req]) => Any] + private lazy val defaultSecurityRules = SecurityRules() + /** + * The security rules used by Lift to secure this application. These mostly + * relate to HTTPS handling and HTTP `Content-Security-Policy`. See the + * `[[SecurityRules]]` documentation for more. + */ + @volatile var securityRules: () => SecurityRules = () => defaultSecurityRules + /** * Defines the resources that are protected by authentication and authorization. If this function * is not defined for the input data, the resource is considered unprotected ergo no authentication @@ -1428,10 +1436,10 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { @volatile var defaultHeaders: PartialFunction[(NodeSeq, Req), List[(String, String)]] = { case _ => val d = Helpers.nowAsInternetDate + List("Expires" -> d, "Date" -> d, - "Cache-Control" -> - "no-cache, private, no-store", + "Cache-Control" -> "no-cache, private, no-store", "Pragma" -> "no-cache" ) } @@ -1634,9 +1642,19 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { @volatile var cometGetTimeout = 140000 /** - * Compute the headers to be sent to the browser in addition to anything else that's sent - */ - val supplementalHeaders: FactoryMaker[List[(String, String)]] = new FactoryMaker(() => List(("X-Lift-Version", liftVersion), ("X-Frame-Options", "SAMEORIGIN"))) {} + * Compute the headers to be sent to the browser in addition to anything else + * that's sent. + * + * Note that the headers for the applications `SecurityRules` are also set + * here, so if you override the supplemental headers, you should + * either refer back to the default set or make sure to include + * `LiftRules.securityRules.headers`. + */ + val supplementalHeaders: FactoryMaker[List[(String, String)]] = new FactoryMaker(() => { + ("X-Lift-Version", liftVersion) :: + ("X-Frame-Options", "SAMEORIGIN") :: + securityRules().headers + }) {} @volatile var calcIE6ForResponse: () => Boolean = () => S.request.map(_.isIE6) openOr false diff --git a/web/webkit/src/main/scala/net/liftweb/http/SecurityRules.scala b/web/webkit/src/main/scala/net/liftweb/http/SecurityRules.scala new file mode 100644 index 0000000000..22d1875dd7 --- /dev/null +++ b/web/webkit/src/main/scala/net/liftweb/http/SecurityRules.scala @@ -0,0 +1,373 @@ +/* + * Copyright 2007-2015 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 java.net.URI + +import scala.concurrent.duration._ + +import util.Props + +// TODO Set it up so we can redirect HTTP->HTTPS but not send Strict-Transport-Security. +/** + * Rules for HTTPS usage by a Lift application. + * + * Currently corresponds directly to the [[HTTP `Strict-Transport-Security` + * header http://tools.ietf.org/html/rfc6797]]. + */ +final case class HttpsRules( + /** + * When set, the duration of the requirement that HTTPS be used for this + * site. It's usually a good idea for this to be a high number. If unset, + * HTTPS is not required when interacting with this site. + */ + requiredTime: Option[Duration] = None, + /** + * When set to true, the required time above includes subdomains. + */ + includeSubDomains: Boolean = false +) { + lazy val headers: List[(String, String)] = { + requiredTime.toList.map { duration => + val age = s"max-age=${duration.toSeconds}" + + val header = + if (includeSubDomains) { + s"$age ; includeSubDomains" + } else { + age + } + + ("Strict-Transport-Security" -> header) + } + } + + /** + * Returns the headers implied by this set of HTTPS rules. If + * `enforceInDevMode` is false and we are in dev mode, returns nothing. + */ + def headers(enforceInDevMode: Boolean = false): List[(String, String)] = { + if (! enforceInDevMode && Props.devMode) { + Nil + } else { + headers + } + } +} +object HttpsRules { + /** + * Creates a restrictive set of HTTPS rules requiring HTTPS for 365 days and + * including subdomains in the requirement. + */ + def secure = apply(Some(365.days), true) +} + +/** + * Base trait for content source restrictions. These are different ways of + * restricting where contents of various types are allowed to come from, in + * conjunction with `ContentSecurityPolicy` categories. + */ +sealed trait ContentSourceRestriction { + /** + * The `Content-Security-Policy` string that represents this restriction. + */ + def sourceRestrictionString: String +} +/** + * Marker trait for restrictions that only apply to JavaScript. + */ +sealed trait JavaScriptSourceRestriction extends ContentSourceRestriction +/** + * Marker trait for restrictions that only apply to stylesheets. + */ +sealed trait StylesheetSourceRestriction extends ContentSourceRestriction + +object ContentSourceRestriction { + /** + * Indicates content from all sources is allowed. + */ + case object All extends ContentSourceRestriction { + val sourceRestrictionString = "*" + } + /** + * Indicates content from the given host path is allowed. See the + * `Content-Security-Policy` spec's [[matching rules for `host-source` + * http://www.w3.org/TR/CSP/#matching]] for more about what this can look + * like. + * + * Example: + * {{{ + * Host("https://base.*.example.com") + * }}} + */ + case class Host(hostAndPath: String) extends ContentSourceRestriction { + val sourceRestrictionString = hostAndPath + } + /** + * Indicates content from the given scheme is allowed. The scheme should not + * include the trailing `:`. + * + * Example: + * {{{ + * Scheme("data") + * }}} + */ + case class Scheme(scheme: String) extends ContentSourceRestriction { + val sourceRestrictionString = scheme + ":" + } + /** + * Indicates content from no sources is allowed. + */ + case object None extends ContentSourceRestriction { + val sourceRestrictionString = "'none'" + } + /** + * Indicates content from the same origin as the content is allowed. + */ + case object Self extends ContentSourceRestriction { + val sourceRestrictionString = "'self'" + } + /** + * Indicates inline content on the page is allowed to be interpreted. It is + * highly recommended that this not be used, as it exposes your application to + * cross-site scripting and other vulnerabilities. + * + * If not specified for JavaScript, JavaScript `on*` event handler attributes, + * ` + + + + + + + + + + + + + + + +

        Run the assets task in lift-webkit to fetch the dependencies.

        + + diff --git a/web/webkit/karma.conf.js b/web/webkit/karma.conf.js new file mode 100644 index 0000000000..ddf446af51 --- /dev/null +++ b/web/webkit/karma.conf.js @@ -0,0 +1,71 @@ +// Karma configuration + +module.exports = function(config) { + config.set({ + + // base path that will be used to resolve all patterns (eg. files, exclude) + basePath: '', + + + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ['jasmine'], + + + // list of files / patterns to load in the browser + files: [ + 'target/web/web-modules/main/webjars/lib/jquery/dist/jquery.min.js', + 'target/web/web-modules/main/webjars/lib/jasmine-ajax/lib/mock-ajax.js', + 'src/main/resources/toserve/lift.js', + 'src/test/assets/js/**/*.js' + ], + + + // list of files to exclude + exclude: [ + ], + + + // preprocess matching files before serving them to the browser + // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor + preprocessors: { + }, + + + // test results reporter to use + // possible values: 'dots', 'progress' + // available reporters: https://npmjs.org/browse/keyword/karma-reporter + reporters: ['mocha'], + + + // web server port + port: 9876, + + + // enable / disable colors in the output (reporters and logs) + colors: true, + + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: false, + + + // start these browsers + // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher + browsers: ['PhantomJS'], + + + // Continuous Integration mode + // if true, Karma captures browsers, runs the tests and exits + singleRun: true, + + // Concurrency level + // how many browser should be started simultanous + concurrency: Infinity + }) +} diff --git a/web/webkit/package.json b/web/webkit/package.json new file mode 100644 index 0000000000..e20aadfc96 --- /dev/null +++ b/web/webkit/package.json @@ -0,0 +1,31 @@ +{ + "name": "lift", + "version": "3.0.0", + "description": "A JavaScript library for use with the Lift web framework", + "scripts": { + "test": "karma start karma.conf.js", + "lint": "jshint src/main/resources/toserve/lift.js" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/lift/framework.git" + }, + "keywords": [ + "lift" + ], + "author": "", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/lift/framework/issues" + }, + "homepage": "https://github.com/lift/framework", + "devDependencies": { + "jasmine-core": "^2.4.0", + "jshint": "^2.8.0", + "karma": "^0.13.15", + "karma-jasmine": "^0.3.6", + "karma-mocha-reporter": "^1.1.3", + "karma-phantomjs-launcher": "^0.2.1", + "phantomjs": "^1.9.19" + } +} diff --git a/web/webkit/src/main/resources/toserve/lift.js b/web/webkit/src/main/resources/toserve/lift.js index 113a29a5b5..0e749afe1a 100644 --- a/web/webkit/src/main/resources/toserve/lift.js +++ b/web/webkit/src/main/resources/toserve/lift.js @@ -5,11 +5,11 @@ window.lift = (function() { // "private" vars - var ajaxPath = function() { return settings.liftPath + '/ajax' }, + var ajaxPath = function() { return settings.liftPath + '/ajax'; }, ajaxQueue = [], ajaxInProcess = null, ajaxVersion = 0, - cometPath = function() { return settings.liftPath + '/comet' }, + cometPath = function() { return settings.liftPath + '/comet'; }, doCycleQueueCnt = 0, ajaxShowing = false, initialized = false, @@ -30,7 +30,7 @@ /** * By default lift uses a garbage-collection mechanism of removing unused bound functions from LiftSesssion. - * Setting this to false will disable this mechanisms and there will be no Ajax polling requests attempted. + * Setting this to false will disable this mechanism and there will be no Ajax polling requests attempted. */ enableGc: true, @@ -302,9 +302,12 @@ // http://stackoverflow.com/questions/4994201/is-object-empty function is_empty(obj) { // null and undefined are empty + /* jshint eqnull:true */ if (obj == null) { return true; } + /* jshint eqnull:false */ + // Assume if it has a length property with a non-zero value // that that property is correct. if (obj.length && obj.length > 0) { @@ -325,7 +328,7 @@ function cometFailureFunc() { var requestCount = cometRequestCount; - setTimeout(function() { cometEntry(requestCount) }, settings.cometFailureRetryTimeout); + setTimeout(function() { cometEntry(requestCount); }, settings.cometFailureRetryTimeout); } function cometSuccessFunc() { @@ -463,8 +466,9 @@ catch (e) { settings.logError(e); } - }; + } + /* jshint eqnull:true */ if (evt.done != null) { doneMsg(); } @@ -474,6 +478,7 @@ else if (evt.failure != null) { failMsg(evt.failure); } + /* jshint eqnull:false */ }; self.then = function(f) { @@ -496,7 +501,7 @@ catch (e) { settings.logError(e); } - }; + } return self; }; @@ -520,7 +525,7 @@ catch (e) { settings.logError(e); } - }; + } return this; }; @@ -565,11 +570,11 @@ } } else if (attributes[i].name.match(/^data-lift-comet-/)) { cometGuid = attributes[i].name.substring('data-lift-comet-'.length).toUpperCase(); - cometVersion = parseInt(attributes[i].value) + cometVersion = parseInt(attributes[i].value); comets[cometGuid] = cometVersion; } else if (attributes[i].name == 'data-lift-session-id') { - sessionId = attributes[i].value + sessionId = attributes[i].value; } } @@ -583,9 +588,9 @@ doCycleIn200(); }); }, - defaultLogError: function(msg) { consoleOrAlert(msg) }, - logError: function() { settings.logError.apply(this, arguments) }, - onEvent: function() { settings.onEvent.apply(this, arguments) }, + defaultLogError: function(msg) { consoleOrAlert(msg); }, + logError: function() { settings.logError.apply(this, arguments); }, + onEvent: function() { settings.onEvent.apply(this, arguments); }, ajax: appendToQueue, startGc: successRegisterGC, ajaxOnSessionLost: function() { @@ -679,8 +684,9 @@ cache: false, success: onSuccess, error: function(_, status) { - if (status != 'abort') - return onFailure.apply(this, arguments) + if (status != 'abort') { + return onFailure.apply(this, arguments); + } } }); } @@ -745,7 +751,7 @@ if (xhr.status === 200) { if (dataType === "script") { try { - eval(xhr.responseText); + eval(xhr.responseText); // jshint ignore:line } catch (e) { settings.logError('The server call succeeded, but the returned Javascript contains an error: '+e); @@ -778,6 +784,7 @@ xhr.open("POST", url, true); xhr.timeout = settings.ajaxPostTimeout; + xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); // set content-type header if the form has been serialized into a string if (typeof data === "string") { @@ -811,7 +818,7 @@ if (xhr.readyState === 4) { // Done if (xhr.status === 200) { try { - eval(xhr.responseText); + eval(xhr.responseText); // jshint ignore:line } catch (e) { settings.logError('The server call succeeded, but the returned Javascript contains an error: '+e); @@ -837,6 +844,7 @@ xhr.open("GET", url, true); xhr.timeout = settings.cometGetTimeout; xhr.setRequestHeader("Accept", "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript, */*; q=0.01"); + xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); xhr.send(); return xhr; diff --git a/web/webkit/src/test/assets/js/liftJQuery.spec.js b/web/webkit/src/test/assets/js/liftJQuery.spec.js new file mode 100644 index 0000000000..f45365b333 --- /dev/null +++ b/web/webkit/src/test/assets/js/liftJQuery.spec.js @@ -0,0 +1,51 @@ +"use strict"; + +describe("Lift jquery", function() { + + beforeEach(function() { + jasmine.Ajax.install(); + + liftJQuery.logError = function(msg) { + console.log(msg); + } + }); + + afterEach(function() { + jasmine.Ajax.uninstall(); + }); + + describe("ajaxGet", function() { + var request; + + beforeEach(function() { + liftJQuery.ajaxGet("/nowhere", {}); + request = jasmine.Ajax.requests.mostRecent(); + }); + + it("sets request method", function() { + expect(request.method).toBe('GET'); + }); + + it("sets X-Requested-With header", function() { + expect(request.requestHeaders['X-Requested-With']).toBe('XMLHttpRequest'); + }); + }); + + describe("ajaxPost", function() { + var request; + + beforeEach(function() { + liftJQuery.ajaxPost("/nowhere", {}); + request = jasmine.Ajax.requests.mostRecent(); + }); + + it("sets request method", function() { + expect(request.method).toBe('POST'); + }); + + it("sets X-Requested-With header", function() { + expect(request.requestHeaders['X-Requested-With']).toBe('XMLHttpRequest'); + }); + }); + +}); diff --git a/web/webkit/src/test/assets/js/liftVanilla.spec.js b/web/webkit/src/test/assets/js/liftVanilla.spec.js new file mode 100644 index 0000000000..ee8adc21d8 --- /dev/null +++ b/web/webkit/src/test/assets/js/liftVanilla.spec.js @@ -0,0 +1,51 @@ +"use strict"; + +describe("Lift vanilla", function() { + + beforeEach(function() { + jasmine.Ajax.install(); + + liftVanilla.logError = function(msg) { + console.log(msg); + } + }); + + afterEach(function() { + jasmine.Ajax.uninstall(); + }); + + describe("ajaxGet", function() { + var request; + + beforeEach(function() { + liftVanilla.ajaxGet("/nowhere", {}); + request = jasmine.Ajax.requests.mostRecent(); + }); + + it("sets request method", function() { + expect(request.method).toBe('GET'); + }); + + it("sets X-Requested-With header", function() { + expect(request.requestHeaders['X-Requested-With']).toBe('XMLHttpRequest'); + }); + }); + + describe("ajaxPost", function() { + var request; + + beforeEach(function() { + liftVanilla.ajaxPost("/nowhere", {}); + request = jasmine.Ajax.requests.mostRecent(); + }); + + it("sets request method", function() { + expect(request.method).toBe('POST'); + }); + + it("sets X-Requested-With header", function() { + expect(request.requestHeaders['X-Requested-With']).toBe('XMLHttpRequest'); + }); + }); + +}); From 7be6dd4abc511f54ba7f61949686fa8733f3c8f8 Mon Sep 17 00:00:00 2001 From: Tim Nelson Date: Sat, 26 Dec 2015 09:25:01 -0600 Subject: [PATCH 1295/1949] Upgrade mongo-java-driver to v2.14.0 --- .../mongodb/record/field/DBRefField.scala | 16 +++++++--------- .../record/field/MongoCaseClassField.scala | 3 ++- .../mongodb/record/MongoClientSaveSpec.scala | 6 +++--- .../mongodb/record/MongoFieldSpec.scala | 12 ++++++------ .../record/MongoRecordExamplesSpec.scala | 4 ++-- .../mongodb/record/MongoRecordSpec.scala | 12 ++++++------ .../liftweb/mongodb/record/MongoTestKit.scala | 4 ++-- .../mongodb/record/QueryExamplesSpec.scala | 4 ++-- .../main/scala/net/liftweb/mongodb/Mongo.scala | 5 ++++- .../net/liftweb/mongodb/MongoDirectSpec.scala | 17 +++++------------ .../mongodb/MongoDocumentExamplesSpec.scala | 18 +++++------------- .../scala/net/liftweb/record/RecordSpec.scala | 4 ++-- project/Dependencies.scala | 4 ++-- 13 files changed, 48 insertions(+), 61 deletions(-) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/DBRefField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/DBRefField.scala index a411d04ef0..ccbac9dac7 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/DBRefField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/DBRefField.scala @@ -1,5 +1,5 @@ /* - * Copyright 2010-2011 WorldWide Conferencing, LLC + * Copyright 2010-2015 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. @@ -64,7 +64,7 @@ class DBRefField[OwnerType <: BsonRecord[OwnerType], RefType <: MongoRecord[RefT def asXHtml =
        - def defaultValue = new DBRef(null, null, null) + def defaultValue = new DBRef("", null) def setFromAny(in: Any): Box[DBRef] = in match { case ref: DBRef => Full(set(ref)) @@ -81,13 +81,11 @@ class DBRefField[OwnerType <: BsonRecord[OwnerType], RefType <: MongoRecord[RefT // assume string is json def setFromString(in: String): Box[DBRef] = { val dbo = JSON.parse(in).asInstanceOf[BasicDBObject] - MongoDB.use(ref.meta.connectionIdentifier) ( db => { - val id = dbo.get("$id").toString - ObjectId.isValid(id) match { - case true => Full(set(new DBRef(db, dbo.get("$ref").toString, new ObjectId(id)))) - case false => Full(set(new DBRef(db, dbo.get("$ref").toString, id))) - } - }) + val id = dbo.get("$id").toString + ObjectId.isValid(id) match { + case true => Full(set(new DBRef(dbo.get("$ref").toString, new ObjectId(id)))) + case false => Full(set(new DBRef(dbo.get("$ref").toString, id))) + } } def toForm: Box[NodeSeq] = Empty diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoCaseClassField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoCaseClassField.scala index 1e2bd346c0..43397eb4bc 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoCaseClassField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoCaseClassField.scala @@ -1,5 +1,5 @@ /* -* Copyright 2010-2011 WorldWide Conferencing, LLC +* Copyright 2010-2015 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. @@ -107,6 +107,7 @@ class MongoCaseClassListField[OwnerType <: Record[OwnerType],CaseType](rec: Owne asJValue match { case JArray(list) => list.foreach(v => dbl.add(JObjectParser.parse(v.asInstanceOf[JObject]))) + case _ => } dbl diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoClientSaveSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoClientSaveSpec.scala index e4ee756fe7..3770d73283 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoClientSaveSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoClientSaveSpec.scala @@ -1,5 +1,5 @@ /* - * Copyright 2014 WorldWide Conferencing, LLC + * Copyright 2014-2015 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. @@ -38,7 +38,7 @@ package mongoclientsaverecords { object SaveDoc extends SaveDoc with MongoMetaRecord[SaveDoc] { import BsonDSL._ - ensureIndex(("name" -> 1), true) // unique name + createIndex(("name" -> 1), true) // unique name } } @@ -64,7 +64,7 @@ class MongoClientSaveSpec extends Specification with MongoTestKit { sd2.save(false) // no exception thrown sd2.save(true) must throwA[MongoException] sd2.saveBox() must beLike { - case Failure(msg, _, _) => msg must contain("E11000 duplicate key error index") // exception thrown + case Failure(msg, _, _) => msg must contain("E11000 duplicate key error") // exception thrown } sd3.save() diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala index 2de7ff7e90..e7eab9ac4f 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala @@ -1,5 +1,5 @@ /* - * Copyright 2006-2014 WorldWide Conferencing, LLC + * Copyright 2006-2015 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. @@ -246,7 +246,7 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundExample ttjo, rec.mandatoryJsonObjectField, new JsExp { - def toJsCmd = Printer.compact(render(json)) + def toJsCmd = compactRender(json) }, json, Empty @@ -496,7 +496,7 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundExample lst, rec.mandatoryMongoJsonObjectListField, new JsExp { - def toJsCmd = Printer.compact(render(json)) + def toJsCmd = compactRender(json) }, json, Empty @@ -575,7 +575,7 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundExample ("uuid" -> ("$uuid" -> subRec.uuid.value.toString)) val srJsExp = new JsExp { - def toJsCmd = Printer.compact(render(srJson)) + def toJsCmd = compactRender(srJson) } passBasicTests(subRec, subRec2, rec.mandatoryBsonRecordField, Full(rec.legacyOptionalBsonRecordField), false) @@ -624,10 +624,10 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundExample ("uuid" -> ("$uuid" -> lst(1).uuid.value.toString)) val sr1JsExp = new JsExp { - def toJsCmd = compact(render(sr1Json)) + def toJsCmd = compactRender(sr1Json) } val sr2JsExp = new JsExp { - def toJsCmd = compact(render(sr2Json)) + def toJsCmd = compactRender(sr2Json) } passBasicTests(lst, lst2, rec.mandatoryBsonRecordListField, Full(rec.legacyOptionalBsonRecordListField)) diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordExamplesSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordExamplesSpec.scala index 544b0d7343..1869cccf94 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordExamplesSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordExamplesSpec.scala @@ -1,5 +1,5 @@ /* - * Copyright 2010-2014 WorldWide Conferencing, LLC + * Copyright 2010-2015 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. @@ -183,7 +183,7 @@ package mongotestrecords { import net.liftweb.json.JsonDSL._ - ensureIndex(("name" -> 1), true) // unique name + createIndex(("name" -> 1), true) // unique name } } diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala index 798c67e2bd..d72c42a414 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala @@ -1,5 +1,5 @@ /* - * Copyright 2010-2014 WorldWide Conferencing, LLC + * Copyright 2010-2015 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. @@ -495,31 +495,31 @@ class MongoRecordSpec extends Specification with MongoTestKit { } "get set from json string using lift-json parser" in { - val mfftrFromJson = MongoFieldTypeTestRecord.fromJsonString(compact(render(mfttrJson))) + val mfftrFromJson = MongoFieldTypeTestRecord.fromJsonString(compactRender(mfttrJson)) mfftrFromJson.isDefined must_== true mfftrFromJson foreach { tr => tr mustEqual mfttr } - val pftrFromJson = PatternFieldTestRecord.fromJsonString(compact(render(pftrJson))) + val pftrFromJson = PatternFieldTestRecord.fromJsonString(compactRender(pftrJson)) pftrFromJson.isDefined must_== true pftrFromJson foreach { tr => tr mustEqual pftr } - val ltrFromJson = ListTestRecord.fromJsonString(compact(render(ltrJson))) + val ltrFromJson = ListTestRecord.fromJsonString(compactRender(ltrJson)) ltrFromJson.isDefined must_== true ltrFromJson foreach { tr => tr mustEqual ltr } - val mtrFromJson = MapTestRecord.fromJsonString(compact(render(mtrJson))) + val mtrFromJson = MapTestRecord.fromJsonString(compactRender(mtrJson)) mtrFromJson.isDefined must_== true mtrFromJson.toList map { tr => tr mustEqual mtr } - val joftrFromJson = JObjectFieldTestRecord.fromJsonString(compact(render(joftrJson))) + val joftrFromJson = JObjectFieldTestRecord.fromJsonString(compactRender(joftrJson)) joftrFromJson must_== Full(joftr) } diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoTestKit.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoTestKit.scala index 324913d7ed..8c4c416ee0 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoTestKit.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoTestKit.scala @@ -1,5 +1,5 @@ /* - * Copyright 2010-2014 WorldWide Conferencing, LLC + * Copyright 2010-2015 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. @@ -54,7 +54,7 @@ trait MongoTestKit extends Specification with BeforeAfterExample { false else { dbs foreach { case (id, _) => - MongoDB.use(id) ( db => { db.getLastError } ) + MongoDB.use(id) ( db => { db.getName } ) } true } diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/QueryExamplesSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/QueryExamplesSpec.scala index 051c1996d7..42074d53dc 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/QueryExamplesSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/QueryExamplesSpec.scala @@ -1,5 +1,5 @@ /* - * Copyright 2011-2014 WorldWide Conferencing, LLC + * Copyright 2011-2015 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. @@ -42,7 +42,7 @@ package queryexamplesfixtures { } object Person extends Person with MongoMetaRecord[Person] { // index name - ensureIndex(("name" -> 1)) + createIndex(("name" -> 1)) // implicit formats already exists def findAllBornAfter(dt: Date) = findAll(("birthDate" -> ("$gt" -> dt))) diff --git a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/Mongo.scala b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/Mongo.scala index cdbfa3e5cb..460e94dc72 100644 --- a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/Mongo.scala +++ b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/Mongo.scala @@ -1,5 +1,5 @@ /* - * Copyright 2010-2014 WorldWide Conferencing, LLC + * Copyright 2010-2015 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. @@ -45,6 +45,7 @@ object MongoDB { /** * Define and authenticate a Mongo db using a MongoClient instance. */ + @deprecated("Credentials are now passed in via MongoClient", "3.0") def defineDbAuth(name: ConnectionIdentifier, mngo: MongoClient, dbName: String, username: String, password: String) { if (!mngo.getDB(dbName).authenticate(username, password.toCharArray)) throw new MongoException("Authorization failed: "+mngo.toString) @@ -126,6 +127,7 @@ object MongoDB { * and the use of getLastError. * See: http://docs.mongodb.org/ecosystem/drivers/java-concurrency/ */ + @deprecated("No longer relevant. See mongo-java-drivers's JavaDocs for details", "3.0") def useSession[T](name: ConnectionIdentifier)(f: (DB) => T): T = { val db = getDb(name) match { @@ -147,6 +149,7 @@ object MongoDB { /** * Same as above except uses DefaultConnectionIdentifier */ + @deprecated("No longer relevant. See mongo-java-drivers's JavaDocs for details", "3.0") def useSession[T](f: (DB) => T): T = { val db = getDb(DefaultConnectionIdentifier) match { diff --git a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDirectSpec.scala b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDirectSpec.scala index ee031748ed..b5bd6f588e 100644 --- a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDirectSpec.scala +++ b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDirectSpec.scala @@ -1,5 +1,5 @@ /* - * Copyright 2010-2014 WorldWide Conferencing, LLC + * Copyright 2010-2015 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. @@ -200,12 +200,12 @@ class MongoDirectSpec extends Specification with MongoTestKit { success } - "Mongo useSession example" in { + "Mongo more examples" in { checkMongoIsRunning - // use a Mongo instance directly with a session - MongoDB.useSession ( db => { + // use a Mongo instance directly + MongoDB.use ( db => { val coll = db.getCollection("testCollection") // create a unique index on name @@ -233,7 +233,7 @@ class MongoDirectSpec extends Specification with MongoTestKit { coll.save(doc2, WriteConcern.SAFE) must throwA[MongoException] Helpers.tryo(coll.save(doc2, WriteConcern.SAFE)) must beLike { case Failure(msg, _, _) => - msg must contain("E11000 duplicate key error index") + msg must contain("E11000 duplicate key error") } Helpers.tryo(coll.save(doc3, WriteConcern.SAFE)).toOption must beSome @@ -244,18 +244,11 @@ class MongoDirectSpec extends Specification with MongoTestKit { // modifier operations $inc, $set, $push... val o2 = new BasicDBObject o2.put("$inc", new BasicDBObject("count", 1)) // increment count by 1 - //o2.put("$set", new BasicDBObject("type", "docdb")) // set type - /** - * The update method only updates one document. see: - * http://jira.mongodb.org/browse/SERVER-268 - */ coll.update(qry, o2, false, false).getN must_== 1 coll.update(qry, o2, false, false).isUpdateOfExisting must_== true - // this update query won't find any docs to update coll.update(new BasicDBObject("name", "None"), o2, false, false).getN must_== 0 - db.getLastError.get("updatedExisting") must_== false // regex query example val key = "name" diff --git a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDocumentExamplesSpec.scala b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDocumentExamplesSpec.scala index 560e532f00..8df3c26126 100644 --- a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDocumentExamplesSpec.scala +++ b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDocumentExamplesSpec.scala @@ -1,5 +1,5 @@ /* - * Copyright 2010-2014 WorldWide Conferencing, LLC + * Copyright 2010-2015 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. @@ -478,7 +478,7 @@ class MongoDocumentExamplesSpec extends Specification with MongoTestKit { success } - "Mongo useSession example" in { + "Mongo examples" in { checkMongoIsRunning @@ -486,15 +486,15 @@ class MongoDocumentExamplesSpec extends Specification with MongoTestKit { val tc2 = SessCollection(ObjectId.get, "MongoSession", "db", 1) val tc3 = SessCollection(ObjectId.get, "MongoDB", "db", 1) - // use a Mongo instance directly with a session - MongoDB.useSession( db => { + // use a Mongo instance directly + MongoDB.use( db => { // save to db Helpers.tryo(SessCollection.save(tc, db)).toOption must beSome SessCollection.save(tc2, db) must throwA[MongoException] Helpers.tryo(SessCollection.save(tc2, db)) must beLike { case Failure(msg, _, _) => - msg must contain("E11000 duplicate key error index") + msg must contain("E11000 duplicate key error") } Helpers.tryo(SessCollection.save(tc3, db)).toOption must beSome @@ -505,16 +505,8 @@ class MongoDocumentExamplesSpec extends Specification with MongoTestKit { // modifier operations $inc, $set, $push... val o2 = ("$inc" -> ("count" -> 1)) // increment count by 1 - //("$set" -> ("dbtype" -> "docdb")) // set dbtype SessCollection.update(qry, o2, db) - db.getLastError.get("updatedExisting") must_== true - /* The update method only updates one document. */ - db.getLastError.get("n") must_== 1 - - /* Multiple documents now supported */ SessCollection.update(qry, o2, db, Multi) - db.getLastError.get("updatedExisting") must_== true - db.getLastError.get("n") must_== 2 // regex query example val lst = SessCollection.findAll(new BasicDBObject("name", Pattern.compile("^Mongo"))) diff --git a/persistence/record/src/test/scala/net/liftweb/record/RecordSpec.scala b/persistence/record/src/test/scala/net/liftweb/record/RecordSpec.scala index 7224188102..14e6b7e478 100644 --- a/persistence/record/src/test/scala/net/liftweb/record/RecordSpec.scala +++ b/persistence/record/src/test/scala/net/liftweb/record/RecordSpec.scala @@ -1,5 +1,5 @@ /* - * Copyright 2010-2014 WorldWide Conferencing, LLC + * Copyright 2010-2015 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. @@ -214,7 +214,7 @@ object RecordSpec extends Specification { ("mandatoryBinaryField" -> "EhMU") ~ ("mandatoryJodaTimeField" -> dt.getMillis) - val fttrJson: String = compact(render(fttrJValue)) + val fttrJson: String = compactRender(fttrJValue) val fttrAsJsObj = JsObj( ("mandatoryBooleanField", JsFalse), diff --git a/project/Dependencies.scala b/project/Dependencies.scala index cd0fdeff6a..2f1e3f5f85 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -1,5 +1,5 @@ /* - * Copyright 2011-2014 WorldWide Conferencing, LLC + * Copyright 2011-2015 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. @@ -36,7 +36,7 @@ object Dependencies { lazy val joda_time = "joda-time" % "joda-time" % "2.6" lazy val joda_convert = "org.joda" % "joda-convert" % "1.2" lazy val htmlparser = "nu.validator.htmlparser" % "htmlparser" % "1.4" - lazy val mongo_java_driver = "org.mongodb" % "mongo-java-driver" % "2.12.2" + lazy val mongo_java_driver = "org.mongodb" % "mongo-java-driver" % "2.14.0" lazy val paranamer = "com.thoughtworks.paranamer" % "paranamer" % "2.4.1" lazy val scalajpa = "org.scala-libs" % "scalajpa" % "1.5" cross CVMappingAll lazy val scalap: ModuleMap = "org.scala-lang" % "scalap" % _ From c083f71e795dfe3f9bdbee4bbc975c944773ecd9 Mon Sep 17 00:00:00 2001 From: Tim Nelson Date: Sat, 26 Dec 2015 12:44:18 -0600 Subject: [PATCH 1296/1949] Use S.originalRequest in Menu snippet --- .../net/liftweb/builtin/snippet/Menu.scala | 12 +- .../liftweb/builtin/snippet/MenuSpec.scala | 122 ++++++++++++++++++ 2 files changed, 128 insertions(+), 6 deletions(-) create mode 100644 web/webkit/src/test/scala/net/liftweb/builtin/snippet/MenuSpec.scala diff --git a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Menu.scala b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Menu.scala index bd59356ebf..16ad9d040b 100644 --- a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Menu.scala +++ b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Menu.scala @@ -77,7 +77,7 @@ object Menu extends DispatchSnippet { *

        If you are using designer friendly invocation, you can access the namespaced attributes:
        * <div class="lift:Menu?li_item:class=foo+bar">menu</div> *

        - * + * *

        For a simple, default menu, simply add

        * *
        @@ -433,25 +433,25 @@ object Menu extends DispatchSnippet {
               // Builds a link for the given loc
               def buildLink[T](loc : Loc[T]) = {
                 Group(SiteMap.buildLink(name, text) match {
        -          case e : Elem => 
        +          case e : Elem =>
                     Helpers.addCssClass(loc.cssClassForMenuItem,
                                         e % S.prefixedAttrsToMetaData("a"))
                   case x => x
                 })
               }
         
        -      (S.request.flatMap(_.location), S.attr("param"), SiteMap.findAndTestLoc(name)) match {
        +      (S.originalRequest.flatMap(_.location), S.attr("param"), SiteMap.findAndTestLoc(name)) match {
                  case (_, Full(param), Full(loc: Loc[T] with ConvertableLoc[T])) => {
                    (for {
                      pv <- loc.convert(param)
                      link <- loc.createLink(pv)
        -           } yield 
        +           } yield
                      Helpers.addCssClass(loc.cssClassForMenuItem,
        -                                  % 
        +                                  %
                                          S.prefixedAttrsToMetaData("a"))) openOr
                    Text("")
                  }
        -         
        +
                  case (Full(loc), _, _) if loc.name == name => {
                    (linkToSelf, donthide) match {
                      case (true, _) => buildLink(loc)
        diff --git a/web/webkit/src/test/scala/net/liftweb/builtin/snippet/MenuSpec.scala b/web/webkit/src/test/scala/net/liftweb/builtin/snippet/MenuSpec.scala
        new file mode 100644
        index 0000000000..592881ade5
        --- /dev/null
        +++ b/web/webkit/src/test/scala/net/liftweb/builtin/snippet/MenuSpec.scala
        @@ -0,0 +1,122 @@
        +/*
        + * Copyright 2015 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 builtin.snippet
        +
        +import xml._
        +import org.specs2.matcher.XmlMatchers
        +import org.specs2.mutable.Specification
        +
        +import common._
        +import http._
        +import mockweb._
        +import MockWeb._
        +import mocks._
        +import sitemap._
        +import util.Helpers.{randomString, secureXML}
        +
        +/**
        + * System under specification for Menu.
        + */
        +object MenuSpec extends Specification with XmlMatchers {
        +  "Menu Specification".title
        +
        +  case class Param(s: String)
        +
        +  def mockSiteMap[T](f: (SiteMap => T)): T = {
        +    val siteMap = SiteMap(
        +      sitemap.Menu.i("foobar") / "foo" / "bar",
        +      sitemap.Menu.i("foobaz") / "foo" / "baz",
        +      sitemap.Menu.param[Param]("foobiz", "foobiz", s => Full(Param(s)), p => p.s) / "foo" / "biz" / *
        +    )
        +
        +    f(siteMap)
        +  }
        +
        +  def testSiteMap[T](uri: String)(f: => T): T = {
        +    mockSiteMap { siteMap =>
        +      val mockReq = new MockHttpServletRequest(uri)
        +
        +      testS(mockReq) {
        +        LiftRules.setSiteMap(siteMap)
        +        f
        +      }
        +    }
        +  }
        +
        +  "The built-in Menu snippet" should {
        +    "Properly render a menu item with default link text" in {
        +      testSiteMap("http://test.com/foo/baz") {
        +        S.withAttrs(new UnprefixedAttribute("name", "foobar", Null)) {
        +          Menu.item(NodeSeq.Empty).toString mustEqual """foobar"""
        +        }
        +      }
        +    }
        +    "Properly render a menu item with passed in link text" in {
        +      testSiteMap("http://test.com/foo/baz") {
        +        S.withAttrs(new UnprefixedAttribute("name", "foobar", Null)) {
        +          Menu.item(Text("Foo")).toString mustEqual """Foo"""
        +        }
        +      }
        +    }
        +    "Hide item when on same page by default" in {
        +      testSiteMap("http://test.com/foo/baz") {
        +        S.withAttrs(new UnprefixedAttribute("name", "foobaz", Null)) {
        +          Menu.item(NodeSeq.Empty).toString mustEqual ""
        +        }
        +      }
        +    }
        +    "Show text only when on same page using the 'donthide' attribute" in {
        +      testSiteMap("http://test.com/foo/baz") {
        +        val donthide = new UnprefixedAttribute("donthide", "true", Null)
        +        S.withAttrs(new UnprefixedAttribute("name", "foobaz", donthide)) {
        +          Menu.item(NodeSeq.Empty).toString mustEqual "foobaz"
        +        }
        +      }
        +    }
        +    "Show full item when on same page using the 'linkToSelf' attribute" in {
        +      testSiteMap("http://test.com/foo/baz") {
        +        val linkToSelf = new UnprefixedAttribute("linkToSelf", "true", Null)
        +        S.withAttrs(new UnprefixedAttribute("name", "foobaz", linkToSelf)) {
        +          Menu.item(NodeSeq.Empty).toString mustEqual """foobaz"""
        +        }
        +      }
        +    }
        +
        +    // I wasn't able to figure out a way to mock a Req where S.originalRequest gets set - TN
        +    // "Properly render a menu item in an ajax request" in {
        +    //   mockSiteMap { siteMap =>
        +    //     val session = new LiftSession("", randomString(20), Empty)
        +    //     val origReq = new MockHttpServletRequest("http://test.com/foo/bar")
        +    //     testS(origReq, Full(session)) {
        +    //       LiftRules.setSiteMap(siteMap)
        +    //       val mockReq = new MockHttpServletRequest("http://test.com/ajax/abc123")
        +    //       mockReq.headers += "X-Requested-With" -> List("XMLHttpRequest")
        +
        +    //       testS(mockReq, Full(session)) {
        +    //         println(s"S.ajax_?: ${S.request.map(_.ajax_?)}")
        +
        +    //         S.withAttrs(new UnprefixedAttribute("name", "foobar", Null)) {
        +    //           Menu.item(NodeSeq.Empty).toString mustEqual """foobar"""
        +    //         }
        +    //       }
        +    //     }
        +    //   }
        +    // }
        +  }
        +}
        +
        
        From 97cef6fe2e43bfa6f7571544ff72fcf167dac8f7 Mon Sep 17 00:00:00 2001
        From: Tim Nelson 
        Date: Mon, 28 Dec 2015 06:15:19 -0600
        Subject: [PATCH 1297/1949] Only check for error code in Mongo tests
        
        ---
         .../scala/net/liftweb/mongodb/record/MongoClientSaveSpec.scala  | 2 +-
         .../src/test/scala/net/liftweb/mongodb/MongoDirectSpec.scala    | 2 +-
         .../scala/net/liftweb/mongodb/MongoDocumentExamplesSpec.scala   | 2 +-
         3 files changed, 3 insertions(+), 3 deletions(-)
        
        diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoClientSaveSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoClientSaveSpec.scala
        index 3770d73283..2ec0f11265 100644
        --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoClientSaveSpec.scala
        +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoClientSaveSpec.scala
        @@ -64,7 +64,7 @@ class MongoClientSaveSpec extends Specification with MongoTestKit {
             sd2.save(false) // no exception thrown
             sd2.save(true) must throwA[MongoException]
             sd2.saveBox() must beLike {
        -      case Failure(msg, _, _) => msg must contain("E11000 duplicate key error") // exception thrown
        +      case Failure(msg, _, _) => msg must contain("E11000")
             }
             sd3.save()
         
        diff --git a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDirectSpec.scala b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDirectSpec.scala
        index b5bd6f588e..170545a1a9 100644
        --- a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDirectSpec.scala
        +++ b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDirectSpec.scala
        @@ -233,7 +233,7 @@ class MongoDirectSpec extends Specification with MongoTestKit {
               coll.save(doc2, WriteConcern.SAFE) must throwA[MongoException]
               Helpers.tryo(coll.save(doc2, WriteConcern.SAFE)) must beLike {
                 case Failure(msg, _, _) =>
        -          msg must contain("E11000 duplicate key error")
        +          msg must contain("E11000")
               }
               Helpers.tryo(coll.save(doc3, WriteConcern.SAFE)).toOption must beSome
         
        diff --git a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDocumentExamplesSpec.scala b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDocumentExamplesSpec.scala
        index 8df3c26126..476a8314af 100644
        --- a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDocumentExamplesSpec.scala
        +++ b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDocumentExamplesSpec.scala
        @@ -494,7 +494,7 @@ class MongoDocumentExamplesSpec extends Specification with MongoTestKit {
               SessCollection.save(tc2, db) must throwA[MongoException]
               Helpers.tryo(SessCollection.save(tc2, db)) must beLike {
                 case Failure(msg, _, _) =>
        -          msg must contain("E11000 duplicate key error")
        +          msg must contain("E11000")
               }
         
               Helpers.tryo(SessCollection.save(tc3, db)).toOption must beSome
        
        From f16fbfebf1c185b66d4201343095bb808f2ebaee Mon Sep 17 00:00:00 2001
        From: Tim Nelson 
        Date: Mon, 28 Dec 2015 06:17:44 -0600
        Subject: [PATCH 1298/1949] Updated MongoDB.useSession deprecation message.
        
        ---
         .../mongodb/src/main/scala/net/liftweb/mongodb/Mongo.scala    | 4 ++--
         1 file changed, 2 insertions(+), 2 deletions(-)
        
        diff --git a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/Mongo.scala b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/Mongo.scala
        index 460e94dc72..e55458bdf4 100644
        --- a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/Mongo.scala
        +++ b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/Mongo.scala
        @@ -127,7 +127,7 @@ object MongoDB {
             * and the use of getLastError.
             * See: http://docs.mongodb.org/ecosystem/drivers/java-concurrency/
             */
        -  @deprecated("No longer relevant. See mongo-java-drivers's JavaDocs for details", "3.0")
        +  @deprecated("No longer needed. See mongo-java-drivers's JavaDocs for details", "3.0")
           def useSession[T](name: ConnectionIdentifier)(f: (DB) => T): T = {
         
             val db = getDb(name) match {
        @@ -149,7 +149,7 @@ object MongoDB {
           /**
             * Same as above except uses DefaultConnectionIdentifier
             */
        -  @deprecated("No longer relevant. See mongo-java-drivers's JavaDocs for details", "3.0")
        +  @deprecated("No longer needed. See mongo-java-drivers's JavaDocs for details", "3.0")
           def useSession[T](f: (DB) => T): T = {
         
             val db = getDb(DefaultConnectionIdentifier) match {
        
        From bce94cc434b05ec80a4d0a56aeae082486cba52d Mon Sep 17 00:00:00 2001
        From: Tim Nelson 
        Date: Mon, 28 Dec 2015 06:21:36 -0600
        Subject: [PATCH 1299/1949] Fix double equals in lift.js
        
        ---
         web/webkit/src/main/resources/toserve/lift.js | 2 +-
         1 file changed, 1 insertion(+), 1 deletion(-)
        
        diff --git a/web/webkit/src/main/resources/toserve/lift.js b/web/webkit/src/main/resources/toserve/lift.js
        index 0e749afe1a..e543956865 100644
        --- a/web/webkit/src/main/resources/toserve/lift.js
        +++ b/web/webkit/src/main/resources/toserve/lift.js
        @@ -684,7 +684,7 @@
                 cache: false,
                 success: onSuccess,
                 error: function(_, status) {
        -          if (status != 'abort') {
        +          if (status !== 'abort') {
                     return onFailure.apply(this, arguments);
                   }
                 }
        
        From 86f59884878bfaa8baf8de54e7a8cbd3c92030e4 Mon Sep 17 00:00:00 2001
        From: Tim Nelson 
        Date: Mon, 28 Dec 2015 11:22:26 -0600
        Subject: [PATCH 1300/1949] Run webapptests last
        
        ---
         .../mongodb/record/MongoClientSaveSpec.scala  |  2 +-
         .../net/liftweb/mongodb/MongoDirectSpec.scala |  2 +-
         .../mongodb/MongoDocumentExamplesSpec.scala   |  2 +-
         .../net/liftweb/squerylrecord/Fixtures.scala  | 28 +++++++++----------
         project/Build.scala                           | 20 +++++++++++--
         project/Dependencies.scala                    |  2 +-
         .../scala/net/liftweb/http/LiftRules.scala    |  8 ++----
         .../liftweb/builtin/snippet/MenuSpec.scala    | 17 ++++++-----
         .../net/liftweb/mockweb/MockWebSpec.scala     | 14 +++++-----
         9 files changed, 54 insertions(+), 41 deletions(-)
        
        diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoClientSaveSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoClientSaveSpec.scala
        index e4ee756fe7..067c2af0ad 100644
        --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoClientSaveSpec.scala
        +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoClientSaveSpec.scala
        @@ -64,7 +64,7 @@ class MongoClientSaveSpec extends Specification with MongoTestKit {
             sd2.save(false) // no exception thrown
             sd2.save(true) must throwA[MongoException]
             sd2.saveBox() must beLike {
        -      case Failure(msg, _, _) => msg must contain("E11000 duplicate key error index") // exception thrown
        +      case Failure(msg, _, _) => msg must contain("E11000")
             }
             sd3.save()
         
        diff --git a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDirectSpec.scala b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDirectSpec.scala
        index ee031748ed..a98da38869 100644
        --- a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDirectSpec.scala
        +++ b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDirectSpec.scala
        @@ -233,7 +233,7 @@ class MongoDirectSpec extends Specification with MongoTestKit {
               coll.save(doc2, WriteConcern.SAFE) must throwA[MongoException]
               Helpers.tryo(coll.save(doc2, WriteConcern.SAFE)) must beLike {
                 case Failure(msg, _, _) =>
        -          msg must contain("E11000 duplicate key error index")
        +          msg must contain("E11000")
               }
               Helpers.tryo(coll.save(doc3, WriteConcern.SAFE)).toOption must beSome
         
        diff --git a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDocumentExamplesSpec.scala b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDocumentExamplesSpec.scala
        index 560e532f00..f4de74dec1 100644
        --- a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDocumentExamplesSpec.scala
        +++ b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDocumentExamplesSpec.scala
        @@ -494,7 +494,7 @@ class MongoDocumentExamplesSpec extends Specification with MongoTestKit {
               SessCollection.save(tc2, db) must throwA[MongoException]
               Helpers.tryo(SessCollection.save(tc2, db)) must beLike {
                 case Failure(msg, _, _) =>
        -          msg must contain("E11000 duplicate key error index")
        +          msg must contain("E11000")
               }
         
               Helpers.tryo(SessCollection.save(tc3, db)).toOption must beSome
        diff --git a/persistence/squeryl-record/src/test/scala/net/liftweb/squerylrecord/Fixtures.scala b/persistence/squeryl-record/src/test/scala/net/liftweb/squerylrecord/Fixtures.scala
        index b736c39b8f..acb77966d9 100644
        --- a/persistence/squeryl-record/src/test/scala/net/liftweb/squerylrecord/Fixtures.scala
        +++ b/persistence/squeryl-record/src/test/scala/net/liftweb/squerylrecord/Fixtures.scala
        @@ -1,5 +1,5 @@
         /*
        - * Copyright 2010 WorldWide Conferencing, LLC
        + * Copyright 2010-2015 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.
        @@ -54,7 +54,7 @@ object DBHelper {
                 MySchema.dropAndCreate
                 MySchema.createTestData
               } catch {
        -        case e => e.printStackTrace()
        +        case e: Exception => e.printStackTrace()
                   throw e;
               }
             }
        @@ -82,15 +82,15 @@ class Company private () extends Record[Company] with KeyedRecord[Long] with Opt
         
         }
         object Company extends Company with MetaRecord[Company] with CRUDify[Long, Company]{
        -  
        +
           def table = MySchema.companies
        -  
        +
           def idFromString(in: String) = in.toLong
        -  
        +
         }
         
         object EmployeeRole extends Enumeration {
        -  
        +
           type EmployeeRole = Value
         
           val Programmer, Manager = Value
        @@ -212,21 +212,21 @@ object MySchema extends Schema {
             allCompanies.foreach(companies.insert(_))
             allEmployees.foreach(employees.insert(_))
             allRooms.foreach(rooms.insert(_))
        -    
        +
             e1.rooms.associate(r1)
             e1.rooms.associate(r2)
           }
         
           object TestData {
        -    
        +
             val c1 = Company.createRecord.name("First Company USA").
               created(Calendar.getInstance()).
               country(Countries.USA).postCode("12345")
        -      
        +
             val c2 = Company.createRecord.name("Second Company USA").
               created(Calendar.getInstance()).
               country(Countries.USA).postCode("54321")
        -      
        +
             val c3 = Company.createRecord.name("Company or Employee").
               created(Calendar.getInstance()).
               country(Countries.Canada).postCode("1234")
        @@ -258,13 +258,13 @@ object MySchema extends Schema {
               photo(Array[Byte](1))
         
             lazy val allEmployees = List(e1, e2, e3)
        -    
        +
             val r1 = Room.createRecord.name("Room 1")
        -    
        +
             val r2 = Room.createRecord.name("Room 2")
        -    
        +
             val r3 = Room.createRecord.name("Room 3")
        -    
        +
             val allRooms = List(r1, r2, r3)
           }
         }
        diff --git a/project/Build.scala b/project/Build.scala
        index ca72eb600d..c9c662a24b 100644
        --- a/project/Build.scala
        +++ b/project/Build.scala
        @@ -73,7 +73,7 @@ object BuildDef extends Build {
                 .dependsOn(common)
                 .settings(description := "Simple Actor",
                           parallelExecution in Test := false)
        -                  
        +
           lazy val markdown =
             coreProject("markdown")
                 .settings(description := "Markdown Parser",
        @@ -136,10 +136,26 @@ object BuildDef extends Build {
                           },
                           initialize in Test <<= (sourceDirectory in Test) { src =>
                             System.setProperty("net.liftweb.webapptest.src.test.webapp", (src / "webapp").absString)
        +                  },
        +                  /**
        +                    * This is to ensure that the tests in net.liftweb.webapptest run last
        +                    * so that other tests (MenuSpec in particular) run before the SiteMap
        +                    * is set.
        +                    */
        +                  testGrouping in Test <<= (definedTests in Test).map { tests =>
        +                    import Tests._
        +
        +                    val (webapptests, others) = tests.partition { test =>
        +                      test.name.startsWith("net.liftweb.webapptest")
        +                    }
        +
        +                    Seq(
        +                      new Group("others", others, InProcess),
        +                      new Group("webapptests", webapptests, InProcess)
        +                    )
                           })
         
         
        -
           // Persistence Projects
           // --------------------
           lazy val persistence: Seq[ProjectReference] =
        diff --git a/project/Dependencies.scala b/project/Dependencies.scala
        index cd0fdeff6a..036f44c5e4 100644
        --- a/project/Dependencies.scala
        +++ b/project/Dependencies.scala
        @@ -36,7 +36,7 @@ object Dependencies {
           lazy val joda_time              = "joda-time"                  % "joda-time"          % "2.6"
           lazy val joda_convert           = "org.joda"                   % "joda-convert"       % "1.2"
           lazy val htmlparser             = "nu.validator.htmlparser"    % "htmlparser"         % "1.4"
        -  lazy val mongo_java_driver      = "org.mongodb"                % "mongo-java-driver"  % "2.12.2"
        +  lazy val mongo_java_driver      = "org.mongodb"                % "mongo-java-driver"  % "2.14.0"
           lazy val paranamer              = "com.thoughtworks.paranamer" % "paranamer"          % "2.4.1"
           lazy val scalajpa               = "org.scala-libs"             % "scalajpa"           % "1.5"     cross CVMappingAll
           lazy val scalap: ModuleMap      = "org.scala-lang"             % "scalap"             % _
        diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala
        index 701307747f..5ad0816481 100644
        --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala
        +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala
        @@ -1,5 +1,5 @@
         /*
        - * Copyright 2007-2011 WorldWide Conferencing, LLC
        + * Copyright 2007-2015 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.
        @@ -1132,10 +1132,8 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable {
           /**
            * A unified set of properties for managing how to treat
            * HTML, XHTML, HTML5.  The default behavior is to return an
        -   * OldHtmlPropteries instance, but you can change this
        -   * to return an Html5Properties instance any you'll get
        -   * HTML5 support.
        -   * LiftRules.htmlProperties.default.set((r: Req) => new Html5Properties(r.userAgent))
        +   * Html5Properties instance, but you can change this.
        +   * LiftRules.htmlProperties.default.set((r: Req) => new XHtmlInHtml5OutProperties(r.userAgent))
            */
           val htmlProperties: FactoryMaker[Req => HtmlProperties] =
             new FactoryMaker(() => (r: Req) => new Html5Properties(r.userAgent): HtmlProperties) {}
        diff --git a/web/webkit/src/test/scala/net/liftweb/builtin/snippet/MenuSpec.scala b/web/webkit/src/test/scala/net/liftweb/builtin/snippet/MenuSpec.scala
        index 592881ade5..cf80be92ed 100644
        --- a/web/webkit/src/test/scala/net/liftweb/builtin/snippet/MenuSpec.scala
        +++ b/web/webkit/src/test/scala/net/liftweb/builtin/snippet/MenuSpec.scala
        @@ -17,8 +17,7 @@
         package net.liftweb
         package builtin.snippet
         
        -import xml._
        -import org.specs2.matcher.XmlMatchers
        +import scala.xml._
         import org.specs2.mutable.Specification
         
         import common._
        @@ -27,12 +26,9 @@ import mockweb._
         import MockWeb._
         import mocks._
         import sitemap._
        -import util.Helpers.{randomString, secureXML}
        +import util.Helpers.randomString
         
        -/**
        - * System under specification for Menu.
        - */
        -object MenuSpec extends Specification with XmlMatchers {
        +object MenuSpec extends Specification {
           "Menu Specification".title
         
           case class Param(s: String)
        @@ -52,8 +48,11 @@ object MenuSpec extends Specification with XmlMatchers {
               val mockReq = new MockHttpServletRequest(uri)
         
               testS(mockReq) {
        -        LiftRules.setSiteMap(siteMap)
        -        f
        +        val rules = new LiftRules()
        +        rules.setSiteMap(siteMap)
        +        LiftRulesMocker.devTestLiftRulesInstance.doWith(rules) {
        +          f
        +        }
               }
             }
           }
        diff --git a/web/webkit/src/test/scala/net/liftweb/mockweb/MockWebSpec.scala b/web/webkit/src/test/scala/net/liftweb/mockweb/MockWebSpec.scala
        index dbbefee878..e696026760 100644
        --- a/web/webkit/src/test/scala/net/liftweb/mockweb/MockWebSpec.scala
        +++ b/web/webkit/src/test/scala/net/liftweb/mockweb/MockWebSpec.scala
        @@ -37,7 +37,7 @@ object MockWebSpec extends Specification  {
           import MockWeb._
         
           /** We can create our own LiftRules instance for the purpose of this spec. In the
        -   * examples below we can call LiftRulesMocker.devTestLiftRulesInstance.doWit(mockLiftRules) {...}
        +   * examples below we can call LiftRulesMocker.devTestLiftRulesInstance.doWith(mockLiftRules) {...}
            * whenever we want to evaluate LiftRules. For simpler usage, WebSpecSpec provides
            * full-featured LiftRules mocking.
            */
        @@ -77,14 +77,14 @@ object MockWebSpec extends Specification  {
           "MockWeb" should {
             "provide a Req corresponding to a string url" in {
               testReq("http://foo.com/test/this?a=b&a=c", "/test") {
        -        req => 
        +        req =>
                   req.uri must_== "/this"
                   req.params("a") must_== List("b","c")
               }
             }
         
             "provide a Req corresponding to a HttpServletRequest" in {
        -      val mockReq = 
        +      val mockReq =
                 new MockHttpServletRequest("http://foo.com/test/this", "/test")
         
               mockReq.method = "POST"
        @@ -93,7 +93,7 @@ object MockWebSpec extends Specification  {
         
               mockReq.body = ("name" -> "joe") ~ ("age" -> 35)
         
        -      testReq(mockReq) { 
        +      testReq(mockReq) {
                 req =>
                   req.json_? must_== true
               }
        @@ -126,9 +126,9 @@ object MockWebSpec extends Specification  {
             }
         
             "initialize S based on a HttpServletRequest" in {
        -      val mockReq = 
        +      val mockReq =
                 new MockHttpServletRequest("http://foo.com/test/this?foo=bar", "/test")
        -      
        +
               testS(mockReq) {
                 S.param("foo") must_== Full("bar")
         
        @@ -170,7 +170,7 @@ object MockWebSpec extends Specification  {
         
             "simplify shared sessions" in {
               object testVar extends SessionVar[String]("Empty")
        -   
        +
               val session = testS("http://foo.com/test") {
                 testVar("Foo!")
                 S.session // returns the current session
        
        From 7939c6abb5bf1484d962b6b98b14e4047d0e8bef Mon Sep 17 00:00:00 2001
        From: Tim Nelson 
        Date: Wed, 30 Dec 2015 07:01:21 -0600
        Subject: [PATCH 1301/1949] Added .jshintrc file
        
        ---
         npmsh                                         |  8 +++
         travis.sh                                     |  5 +-
         web/webkit/.jshintrc                          | 22 ++++++++
         web/webkit/src/main/resources/toserve/lift.js | 51 ++++++++++---------
         4 files changed, 57 insertions(+), 29 deletions(-)
         create mode 100755 npmsh
         create mode 100644 web/webkit/.jshintrc
        
        diff --git a/npmsh b/npmsh
        new file mode 100755
        index 0000000000..dd0cfc7936
        --- /dev/null
        +++ b/npmsh
        @@ -0,0 +1,8 @@
        +#!/bin/bash
        +
        +set -ev
        +
        +cd web/webkit
        +npm run lint
        +npm run test
        +cd -
        diff --git a/travis.sh b/travis.sh
        index a879354cf6..46dee0eaef 100755
        --- a/travis.sh
        +++ b/travis.sh
        @@ -4,10 +4,7 @@ set -ev
         
         ./liftsh test
         
        -cd web/webkit
        -npm run lint
        -npm run test
        -cd -
        +./npmsh
         
         if [ "${TRAVIS_PULL_REQUEST}" = "false" ]; then
           mkdir -p ~/.sbt/0.13/
        diff --git a/web/webkit/.jshintrc b/web/webkit/.jshintrc
        new file mode 100644
        index 0000000000..b9e9f06c79
        --- /dev/null
        +++ b/web/webkit/.jshintrc
        @@ -0,0 +1,22 @@
        +{
        +  "curly": true,
        +  "eqeqeq": true,
        +  "immed": true,
        +  "latedef": "nofunc",
        +  "newcap": true,
        +  "noarg": true,
        +  "sub": true,
        +  "undef": true,
        +  "unused": true,
        +  "boss": true,
        +  "eqnull": true,
        +  "browser": true,
        +  "evil": true,
        +  "jquery": true,
        +  "node": true,
        +  "mocha": true,
        +  "predef" : ["define"],
        +  "globals": {
        +
        +  }
        +}
        diff --git a/web/webkit/src/main/resources/toserve/lift.js b/web/webkit/src/main/resources/toserve/lift.js
        index e543956865..e3f4c6edf7 100644
        --- a/web/webkit/src/main/resources/toserve/lift.js
        +++ b/web/webkit/src/main/resources/toserve/lift.js
        @@ -67,10 +67,10 @@
               ajaxGet: function() {
                 consoleOrAlert("ajaxGet function must be defined in settings");
               },
        -      onEvent: function(elementOrId, eventName, fn) {
        +      onEvent: function() {
                 consoleOrAlert("onEvent function must be defined in settings");
               },
        -      onDocumentReady: function(fn) {
        +      onDocumentReady: function() {
                 consoleOrAlert("onDocumentReady function must be defined in settings");
               },
               cometGetTimeout: 140000,
        @@ -348,8 +348,9 @@
             // Forcibly restart the comet cycle; use this, for example, when a
             // new comet has been received.
             function restartComet() {
        -      if (currentCometRequest)
        +      if (currentCometRequest) {
                 currentCometRequest.abort();
        +      }
         
               cometSuccessFunc();
             }
        @@ -427,7 +428,7 @@
         
               // "private" funcs
               function successMsg(value) {
        -        if (_done || _failed) return;
        +        if (_done || _failed) { return; }
                 _values.push(value);
                 for (var f in _valueFuncs) {
                   _valueFuncs[f](value);
        @@ -435,7 +436,7 @@
               }
         
               function failMsg(msg) {
        -        if (_done || _failed) return;
        +        if (_done || _failed) { return; }
                 removePromise(self.guid);
                 _failed = true;
                 _failMsg = msg;
        @@ -446,7 +447,7 @@
               }
         
               function doneMsg() {
        -        if (_done || _failed) return;
        +          if (_done || _failed) { return; }
                 removePromise(self.guid);
                 _done = true;
         
        @@ -459,7 +460,7 @@
               self.guid = makeGuid();
         
               self.processMsg = function(evt) {
        -        if (_done || _failed) return;
        +        if (_done || _failed) { return; }
                 _events.push(evt);
                 for (var v in _eventFuncs) {
                   try { _eventFuncs[v](evt); }
        @@ -533,15 +534,15 @@
               self.map = function(f) {
                 var ret = new Promise();
         
        -        done(function() {
        +        self.done(function() {
                   ret.doneMsg();
                 });
         
        -        fail(function (m) {
        +        self.fail(function (m) {
                   ret.failMsg(m);
                 });
         
        -        then(function (v) {
        +        self.then(function (v) {
                   ret.successMsg(f(v));
                 });
         
        @@ -563,7 +564,7 @@
                       cometGuid, cometVersion,
                       comets = {};
                   for (var i = 0; i < attributes.length; ++i) {
        -            if (attributes[i].name == 'data-lift-gc') {
        +            if (attributes[i].name === 'data-lift-gc') {
                       pageId = attributes[i].value;
                       if (settings.enableGc) {
                         lift.startGc();
        @@ -573,12 +574,12 @@
                       cometVersion = parseInt(attributes[i].value);
         
                       comets[cometGuid] = cometVersion;
        -            } else if (attributes[i].name == 'data-lift-session-id') {
        +            } else if (attributes[i].name === 'data-lift-session-id') {
                       sessionId = attributes[i].value;
                     }
                   }
         
        -          if (typeof cometGuid != 'undefined') {
        +          if (typeof cometGuid !== 'undefined') {
                     registerComets(comets, true);
                   }
         
        @@ -645,7 +646,7 @@
         
           window.liftJQuery = {
             onEvent: function(elementOrId, eventName, fn) {
        -      if (typeof elementOrId == 'string') {
        +      if (typeof elementOrId === 'string') {
                 elementOrId = '#' + elementOrId;
               }
         
        @@ -702,22 +703,22 @@
                   pre = doc.addEventListener ? '' : 'on';
         
               var element = elementOrId;
        -      if (typeof elementOrId == 'string') {
        +      if (typeof elementOrId === 'string') {
                 element = document.getElementById(elementOrId);
               }
         
               element[add](pre + eventName, fn, false);
             },
             onDocumentReady: function(fn) {
        -      var done = false, top = true,
        +      var settings = this, done = false, top = true,
               win = window, doc = win.document, root = doc.documentElement,
               pre = doc.addEventListener ? '' : 'on',
               rem = doc.addEventListener ? 'removeEventListener' : 'detachEvent',
         
               init = function(e) {
        -        if (e.type == 'readystatechange' && doc.readyState != 'complete') return;
        -        (e.type == 'load' ? win : doc)[rem](pre + e.type, init, false);
        -        if (!done && (done = true)) fn.call(win, e.type || e);
        +        if (e.type === 'readystatechange' && doc.readyState !== 'complete') { return; }
        +        (e.type === 'load' ? win : doc)[rem](pre + e.type, init, false);
        +        if (!done && (done = true)) { fn.call(win, e.type || e); }
               },
         
               poll = function() {
        @@ -725,16 +726,16 @@
                 init('poll');
               };
         
        -      if (doc.readyState == 'complete') {
        +      if (doc.readyState === 'complete') {
                 fn.call(win, 'lazy');
               } else {
                 if (doc.createEventObject && root.doScroll) {
        -            try { top = !win.frameElement; } catch(e) { }
        -            if (top) poll();
        +          try { top = !win.frameElement; } catch(e) { }
        +          if (top) { poll(); }
                 }
        -        liftVanilla.onEvent(doc, 'DOMContentLoaded', init);
        -        liftVanilla.onEvent(doc, 'readystatechange', init);
        -        liftVanilla.onEvent(win, 'load', init);
        +        settings.onEvent(doc, 'DOMContentLoaded', init);
        +        settings.onEvent(doc, 'readystatechange', init);
        +        settings.onEvent(win, 'load', init);
               }
             },
             ajaxPost: function(url, data, dataType, onSuccess, onFailure, onUploadProgress) {
        
        From c126569979feb7089fa4849dde0410d66b26d2c9 Mon Sep 17 00:00:00 2001
        From: Tim Nelson 
        Date: Wed, 30 Dec 2015 07:21:14 -0600
        Subject: [PATCH 1302/1949] Added formatting to LiftRules.htmlProperties
         comment
        
        ---
         web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala | 2 ++
         1 file changed, 2 insertions(+)
        
        diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala
        index 5ad0816481..20ea3121ce 100644
        --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala
        +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala
        @@ -1133,7 +1133,9 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable {
            * A unified set of properties for managing how to treat
            * HTML, XHTML, HTML5.  The default behavior is to return an
            * Html5Properties instance, but you can change this.
        +   * {{{
            * LiftRules.htmlProperties.default.set((r: Req) => new XHtmlInHtml5OutProperties(r.userAgent))
        +   * }}}
            */
           val htmlProperties: FactoryMaker[Req => HtmlProperties] =
             new FactoryMaker(() => (r: Req) => new Html5Properties(r.userAgent): HtmlProperties) {}
        
        From b26e3cf10650b288666bc92edce89613c2810f05 Mon Sep 17 00:00:00 2001
        From: Tim Nelson 
        Date: Wed, 30 Dec 2015 09:03:05 -0600
        Subject: [PATCH 1303/1949] Better .jshintrc
        
        ---
         web/webkit/.jshintrc                            | 17 +++++++----------
         web/webkit/package.json                         |  6 ++++--
         web/webkit/src/main/resources/toserve/lift.js   |  7 ++++---
         web/webkit/src/test/assets/js/.jshintrc         |  9 +++++++++
         .../src/test/assets/js/liftJQuery.spec.js       |  2 +-
         .../src/test/assets/js/liftVanilla.spec.js      |  2 +-
         6 files changed, 26 insertions(+), 17 deletions(-)
         create mode 100644 web/webkit/src/test/assets/js/.jshintrc
        
        diff --git a/web/webkit/.jshintrc b/web/webkit/.jshintrc
        index b9e9f06c79..bc465ac020 100644
        --- a/web/webkit/.jshintrc
        +++ b/web/webkit/.jshintrc
        @@ -1,22 +1,19 @@
         {
           "curly": true,
           "eqeqeq": true,
        -  "immed": true,
        +  "esversion": 3,
        +  "futurehostile": true,
           "latedef": "nofunc",
        -  "newcap": true,
           "noarg": true,
        -  "sub": true,
        +  "nocomma": true,
        +  "nonbsp": true,
        +  "nonew": true,
        +  "strict": true,
           "undef": true,
           "unused": true,
        -  "boss": true,
        -  "eqnull": true,
           "browser": true,
        -  "evil": true,
           "jquery": true,
        -  "node": true,
        -  "mocha": true,
        -  "predef" : ["define"],
           "globals": {
        -
        +    "JSON": false
           }
         }
        diff --git a/web/webkit/package.json b/web/webkit/package.json
        index e20aadfc96..adb5f3a6cb 100644
        --- a/web/webkit/package.json
        +++ b/web/webkit/package.json
        @@ -4,7 +4,9 @@
           "description": "A JavaScript library for use with the Lift web framework",
           "scripts": {
             "test": "karma start karma.conf.js",
        -    "lint": "jshint src/main/resources/toserve/lift.js"
        +    "lint": "npm run lint:main && npm run lint:test",
        +    "lint:main": "jshint src/main/resources/toserve/lift.js",
        +    "lint:test": "jshint --config src/test/assets/js/.jshintrc src/test/assets/js/*spec.js"
           },
           "repository": {
             "type": "git",
        @@ -21,7 +23,7 @@
           "homepage": "https://github.com/lift/framework",
           "devDependencies": {
             "jasmine-core": "^2.4.0",
        -    "jshint": "^2.8.0",
        +    "jshint": "^2.9.1-rc2",
             "karma": "^0.13.15",
             "karma-jasmine": "^0.3.6",
             "karma-mocha-reporter": "^1.1.3",
        diff --git a/web/webkit/src/main/resources/toserve/lift.js b/web/webkit/src/main/resources/toserve/lift.js
        index e3f4c6edf7..335e38c036 100644
        --- a/web/webkit/src/main/resources/toserve/lift.js
        +++ b/web/webkit/src/main/resources/toserve/lift.js
        @@ -5,7 +5,8 @@
         
           window.lift = (function() {
             // "private" vars
        -    var ajaxPath = function() { return settings.liftPath + '/ajax'; },
        +    var settings,
        +        ajaxPath = function() { return settings.liftPath + '/ajax'; },
                 ajaxQueue = [],
                 ajaxInProcess = null,
                 ajaxVersion = 0,
        @@ -20,7 +21,7 @@
                 knownPromises = {};
         
             // default settings
        -    var settings = {
        +    settings = {
               /**
                 * Contains the Ajax URI path used by Lift to process Ajax requests.
                 */
        @@ -571,7 +572,7 @@
                       }
                     } else if (attributes[i].name.match(/^data-lift-comet-/)) {
                       cometGuid = attributes[i].name.substring('data-lift-comet-'.length).toUpperCase();
        -              cometVersion = parseInt(attributes[i].value);
        +              cometVersion = parseInt(attributes[i].value, 10);
         
                       comets[cometGuid] = cometVersion;
                     } else if (attributes[i].name === 'data-lift-session-id') {
        diff --git a/web/webkit/src/test/assets/js/.jshintrc b/web/webkit/src/test/assets/js/.jshintrc
        new file mode 100644
        index 0000000000..441e34d3b2
        --- /dev/null
        +++ b/web/webkit/src/test/assets/js/.jshintrc
        @@ -0,0 +1,9 @@
        +{
        +  "extends": "../../../../.jshintrc",
        +  "node": true,
        +  "jasmine": true,
        +  "globals": {
        +    "liftVanilla": false,
        +    "liftJQuery": false
        +  }
        +}
        diff --git a/web/webkit/src/test/assets/js/liftJQuery.spec.js b/web/webkit/src/test/assets/js/liftJQuery.spec.js
        index f45365b333..4062e02554 100644
        --- a/web/webkit/src/test/assets/js/liftJQuery.spec.js
        +++ b/web/webkit/src/test/assets/js/liftJQuery.spec.js
        @@ -7,7 +7,7 @@ describe("Lift jquery", function() {
         
             liftJQuery.logError = function(msg) {
               console.log(msg);
        -    }
        +    };
           });
         
           afterEach(function() {
        diff --git a/web/webkit/src/test/assets/js/liftVanilla.spec.js b/web/webkit/src/test/assets/js/liftVanilla.spec.js
        index ee8adc21d8..81effde69b 100644
        --- a/web/webkit/src/test/assets/js/liftVanilla.spec.js
        +++ b/web/webkit/src/test/assets/js/liftVanilla.spec.js
        @@ -7,7 +7,7 @@ describe("Lift vanilla", function() {
         
             liftVanilla.logError = function(msg) {
               console.log(msg);
        -    }
        +    };
           });
         
           afterEach(function() {
        
        From 8d49b699e54637e04bfe538c5e54a0ed12528097 Mon Sep 17 00:00:00 2001
        From: Matt Farmer 
        Date: Sat, 9 Jan 2016 16:27:05 -0500
        Subject: [PATCH 1304/1949] Implement a spec covering the current behavior of
         find all.
        
        ---
         .../scala/net/liftweb/json/JsonAstSpec.scala  | 19 +++++++++++++++++++
         1 file changed, 19 insertions(+)
        
        diff --git a/core/json/src/test/scala/net/liftweb/json/JsonAstSpec.scala b/core/json/src/test/scala/net/liftweb/json/JsonAstSpec.scala
        index f9369c015f..e76f4989a7 100644
        --- a/core/json/src/test/scala/net/liftweb/json/JsonAstSpec.scala
        +++ b/core/json/src/test/scala/net/liftweb/json/JsonAstSpec.scala
        @@ -187,6 +187,25 @@ object JsonAstSpec extends Specification with JValueGen with ScalaCheck {
             x.## must_== y.##
           }
         
        +  "find all children" in {
        +    val subject = JObject(
        +      JField("alpha", JString("apple")) ::
        +      JField("beta", JObject(
        +        JField("alpha", JString("bacon")) ::
        +        JField("charlie", JString("i'm a masseuse")) ::
        +        Nil
        +      )) ::
        +      Nil
        +    )
        +
        +    subject \\ "alpha" must_== JObject(
        +      JField("alpha", JString("apple")) ::
        +      JField("alpha", JString("bacon")) ::
        +      Nil
        +    )
        +    subject \\ "charlie" must_== JString("i'm a masseuse")
        +  }
        +
           private def reorderFields(json: JValue) = json map {
             case JObject(xs) => JObject(xs.reverse)
             case x => x
        
        From b46a994e21a85b3fec55b253a5e07721b9c2879b Mon Sep 17 00:00:00 2001
        From: Antonio Salazar Cardozo 
        Date: Sun, 3 Jan 2016 18:40:53 -0500
        Subject: [PATCH 1305/1949] Add LiftMerge spec.
        
        ---
         .../net/liftweb/http/LiftMergeSpec.scala      | 450 ++++++++++++++++++
         1 file changed, 450 insertions(+)
         create mode 100644 web/webkit/src/test/scala/net/liftweb/http/LiftMergeSpec.scala
        
        diff --git a/web/webkit/src/test/scala/net/liftweb/http/LiftMergeSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/LiftMergeSpec.scala
        new file mode 100644
        index 0000000000..a73ed6d9df
        --- /dev/null
        +++ b/web/webkit/src/test/scala/net/liftweb/http/LiftMergeSpec.scala
        @@ -0,0 +1,450 @@
        +package net.liftweb
        +package http
        +
        +import scala.xml._
        +
        +import org.specs2._
        +  import execute.{Result, AsResult}
        +  import mutable.{Around, Specification}
        +  import matcher.XmlMatchers
        +  import mock.Mockito
        +
        +import org.mockito.Mockito._
        +
        +import common._
        +
        +import js.JE.JsObj
        +
        +trait BaseAround extends Around {
        +  override def around[T: AsResult](test: =>T): Result = {
        +    AsResult(test)
        +  }
        +}
        +
        +trait LiftRulesSetup extends Around {
        +  def rules: LiftRules
        +
        +  abstract override def around[T: AsResult](test: => T): Result = {
        +    super.around {
        +      LiftRulesMocker.devTestLiftRulesInstance.doWith(rules) {
        +        AsResult(test)
        +      }
        +    }
        +  }
        +}
        +
        +trait SSetup extends Around {
        +  def session: LiftSession
        +  def req: Box[Req]
        +
        +  abstract override def around[T: AsResult](test: => T): Result = {
        +    super.around {
        +      S.init(req, session) {
        +        AsResult(test)
        +      }
        +    }
        +  }
        +}
        +
        +class WithRules(val rules: LiftRules) extends BaseAround with LiftRulesSetup
        +
        +class WithLiftContext(val rules: LiftRules, val session: LiftSession, val req: Box[Req] = Empty) extends BaseAround with LiftRulesSetup with SSetup
        +
        +class LiftMergeSpec extends Specification with XmlMatchers with Mockito {
        +  val mockReq = mock[Req]
        +  mockReq.contextPath returns "/context-path"
        +
        +  val testSession = new LiftSession("/context-path", "underlying id", Empty)
        +
        +  val testRules = new LiftRules()
        +  // Avoid extra appended elements by default.
        +  testRules.javaScriptSettings.default.set(() => () => Empty)
        +  testRules.autoIncludeAjaxCalc.default.set(() => () => (_: LiftSession) => false)
        +  testRules.excludePathFromContextPathRewriting.default
        +    .set(
        +      () => { in: String => 
        +        in.startsWith("exclude-me")
        +      }
        +    )
        +
        +  "LiftMerge when doing the final page merge" should {
        +    "merge head segments in the page body in order into main head" in new WithRules(testRules) {
        +      val result =
        +        testSession.merge(
        +          
        +            
        +              
        +            
        +            
        +              
        +                
        +                
        +              
        +              
        +

        + + + +

        +
        + + , + mockReq + ) + + (result \ "head" \ "_") must_== (Seq( + , + , + , + + ): NodeSeq) + } + + "merge tail segments in the page body in order at the end of the body" in new WithRules(testRules) { + val result = + testSession.merge( + + + + + + + + + +
        +

        + + + +

        +
        + +

        Thingies

        +

        More thingies

        + + , + mockReq + ) + + (result \ "body" \ "_").takeRight(3) must_== (Seq( + , + , + + ): NodeSeq) + } + + "not merge tail segments in the head" in new WithRules(testRules) { + val result = + testSession.merge( + + + + + + + + + + + +
        +

        + + + +

        +
        + +

        Thingies

        +

        More thingies

        + + , + mockReq + ) + + (result \ "body" \ "_").takeRight(3) must_== (Seq( + , + , + + ): NodeSeq) + } + + "normalize absolute link hrefs everywhere" in new WithLiftContext(testRules, testSession) { + val result = + testSession.merge( + + + + + + + + + + +
        +

        + + + +

        +
        + +

        Thingies

        +

        More thingies

        + + , + mockReq + ) + + (result \\ "link").map(_ \@ "href") must_== + "/context-path/testlink" :: + "/context-path/testlink2" :: + "/context-path/testlink3" :: Nil + } + + "normalize absolute script srcs everywhere" in new WithLiftContext(testRules, testSession) { + val result = + testSession.merge( + + + + + + + + + + +
        +

        + + + +

        +
        + +

        Thingies

        +

        More thingies

        + + , + mockReq + ) + + (result \\ "script").map(_ \@ "src") must_== + "/context-path/testscript" :: + "/context-path/testscript2" :: Nil + } + + "normalize absolute a hrefs everywhere" in new WithLiftContext(testRules, testSession) { + val result = + testSession.merge( + + + Booyan + + + Booyan + + Booyan + +
        + Booyan +

        + + Booyan + +

        +
        + +

        Thingies Booyan

        +

        More thingies

        + + , + mockReq + ) + + (result \\ "a").map(_ \@ "href") must_== + "/context-path/testa1" :: + "testa3" :: + "/context-path/testa2" :: + "testa4" :: + "/context-path/testa6" :: + "/context-path/testa5" :: Nil + } + + "normalize absolute form actions everywhere" in new WithLiftContext(testRules, testSession) { + val result = + testSession.merge( + + + Booyan + + +
        Booyan
        + +
        Booyan
        + +
        +
        Booyan
        +

        + +

        Booyan
        + +

        +
        + +

        Thingies

        Booyan

        +

        More thingies

        + + , + mockReq + ) + + (result \\ "form").map(_ \@ "action") must_== + "/context-path/testform1" :: + "testform3" :: + "/context-path/testform2" :: + "testform4" :: + "/context-path/testform6" :: + "/context-path/testform5" :: Nil + } + + "not rewrite script srcs anywhere" in new WithLiftContext(testRules, testSession) { + val result = + URLRewriter.doWith((_: String) => "rewritten") { + testSession.merge( + + + + + + + + +
        +

        + + + + + + +

        +

        + +

        +
        + +

        Thingies

        +

        More thingies

        + + , + "/context-path", + false + ).nodes + + result must ==/( + + + + + + + +
        +

        + +

        +
        + +

        Thingies

        +

        More thingies

        + + + ) + } + + "extract events from all elements at any depth" in { + val NodesAndEventJs(html, js) = + HtmlNormalizer.normalizeHtmlAndEventHandlers( + + + + + + + +
        +

        + +

        +
        + +

        Thingies

        +

        More thingies

        + + , + "/context-path", + false + ) + + List("testJs1", + "testJs2", + "testJs3", + "testJs4", + "testJs5", + "testJs6", + "testJs7", + "testJs8", + "testJs9", + "testJs10") + .foreach(js.toJsCmd must contain(_)) + + html.toString must beLike { + case eventAttributeMatcher(eventAttribute) => + ko(s"Contained unexpected event: ${eventAttribute}") + case _ => + ok + } + } + + "reuse ids when they are already on an element with an event" in { + val NodesAndEventJs(html, js) = + HtmlNormalizer.normalizeHtmlAndEventHandlers( + , + "/context-path", + false + ) + + html must ==/() + js.toJsCmd must_== """| + | + |lift.onEvent("testid","event",function(event) {doStuff;}); + |""".stripMargin('|') + } + + "generate ids for elements with events if they don't have one" in { + val NodesAndEventJs(html, js) = + HtmlNormalizer.normalizeHtmlAndEventHandlers( + , + "/context-path", + false + ) + + val id = html \@ "id" + + id must not be empty + js.toJsCmd must_== s"""| + | + |lift.onEvent("$id","event",function(event) {doStuff;}); + |""".stripMargin('|') + } + + "extract event js correctly for multiple elements" in { + val NodesAndEventJs(_, js) = + HtmlNormalizer.normalizeHtmlAndEventHandlers( +
        + + + +
        , + "/context-path", + false + ) + + js.toJsCmd must be matching(s"""|(?ms) + | + | + | + | + |lift\\.onEvent\\("lift-event-js-[^"]+","event",function\\(event\\) \\{doStuff;\\}\\); + | + | + | + |lift\\.onEvent\\("hello","event",function\\(event\\) \\{doStuff2;\\}\\); + | + | + | + |lift\\.onEvent\\("lift-event-js-[^"]+","event",function\\(event\\) \\{doStuff3;\\}\\); + | + |""".stripMargin('|').r + ) + } + + "extract events from hrefs and actions" in { + val NodesAndEventJs(html, js) = + HtmlNormalizer.normalizeHtmlAndEventHandlers( +
        + + + + // Note here we have the same behavior as browsers: javascript:/ + // is *processed as JavaScript* but it is *invalid JavaScript* + // (i.e., it corresponds to a JS expression that starts with `/`). + +
        , + "/context-path", + false + ) + + (html \ "myelement").map(_ \@ "href").filter(_.nonEmpty) must beEmpty + (html \ "myelement").map(_ \@ "action").filter(_.nonEmpty) must beEmpty + js.toJsCmd must be matching(s"""|(?ms) + | + | + | + | + |lift\\.onEvent\\("lift-event-js-[^"]+","click",function\\(event\\) \\{doStuff; event.preventDefault\\(\\);\\}\\); + | + | + | + |lift\\.onEvent\\("hello","submit",function\\(event\\) \\{doStuff2; event.preventDefault\\(\\);\\}\\); + | + | + | + |lift\\.onEvent\\("hello2","click",function\\(event\\) \\{doStuff3; event.preventDefault\\(\\);\\}\\); + | + | + | + |lift\\.onEvent\\("lift-event-js-[^"]+","submit",function\\(event\\) \\{/doStuff4; event.preventDefault\\(\\);\\}\\); + | + |""".stripMargin('|').r + ) + } + + "not extract events from hrefs and actions without the proper prefix" in { + val NodesAndEventJs(html, js) = + HtmlNormalizer.normalizeHtmlAndEventHandlers( +
        + + + + +
        , + "/context-path", + false + ) + + (html \ "myelement").map(_ \@ "href").filter(_.nonEmpty) must_== List("doStuff", "javascrip://doStuff3") + (html \ "myelement").map(_ \@ "action").filter(_.nonEmpty) must_== List("javascrip:doStuff2", "doStuff4") + js.toJsCmd.trim must beEmpty + } + + "normalize absolute link hrefs everywhere" in { + val result = + HtmlNormalizer.normalizeHtmlAndEventHandlers( + + + + + + + +
        +

        + +

        +
        + +

        Thingies

        +

        More thingies

        + + , + "/context-path", + false + ).nodes + + (result \\ "link").map(_ \@ "href") must_== + "/context-path/testlink" :: + "/context-path/testlink2" :: + "/context-path/testlink3" :: Nil + } + + "normalize absolute script srcs everywhere" in { + val result = + HtmlNormalizer.normalizeHtmlAndEventHandlers( + + + + + + + + +
        +

        + +

        +
        + +

        Thingies

        +

        More thingies

        + + , + "/context-path", + false + ).nodes + + (result \\ "script").map(_ \@ "src") must_== + "/context-path/testscript" :: + "/context-path/testscript2" :: Nil + } + + "normalize absolute a hrefs everywhere" in { + val result = + HtmlNormalizer.normalizeHtmlAndEventHandlers( + + + Booyan + + + Booyan + Booyan +
        + Booyan +

        + Booyan +

        +
        + +

        Thingies Booyan

        +

        More thingies

        + + , + "/context-path", + false + ).nodes + + (result \\ "a").map(_ \@ "href") must_== + "/context-path/testa1" :: + "/context-path/testa2" :: + "testa3" :: + "testa4" :: + "/context-path/testa5" :: + "/context-path/testa6" :: Nil + } + + "normalize absolute form actions everywhere" in { + val result = + HtmlNormalizer.normalizeHtmlAndEventHandlers( + + +
        Booyan
        + + +
        Booyan
        +
        Booyan
        +
        +
        Booyan
        +

        +

        Booyan
        +

        +
        + +

        Thingies

        Booyan

        +

        More thingies

        + + , + "/context-path", + false + ).nodes + + (result \\ "form").map(_ \@ "action") must_== + "/context-path/testform1" :: + "/context-path/testform2" :: + "testform3" :: + "testform4" :: + "/context-path/testform5" :: + "/context-path/testform6" :: Nil + } + + "not rewrite script srcs anywhere" in { + val result = + URLRewriter.doWith((_: String) => "rewritten") { + HtmlNormalizer.normalizeHtmlAndEventHandlers( + + + + + + +
        +

        + + + + + + +

        +

        + +

        +
        + +

        Thingies

        +

        More thingies

        + + , + "/context-path", + false, + true + ).nodes + + (result \\ "script").map(_ \@ "src") must_== + "/context-path/testscript" :: + "/context-path/testscript2" :: Nil + } + + "normalize absolute a hrefs everywhere" in { + val result = + HtmlNormalizer.normalizeHtmlAndEventHandlers( + + + Booyan + + + Booyan + Booyan +
        + Booyan +

        + Booyan +

        +
        + +

        Thingies Booyan

        +

        More thingies

        + + , + "/context-path", + false, + true + ).nodes + + (result \\ "a").map(_ \@ "href") must_== + "/context-path/testa1" :: + "/context-path/testa2" :: + "testa3" :: + "testa4" :: + "/context-path/testa5" :: + "/context-path/testa6" :: Nil + } + + "normalize absolute form actions everywhere" in { + val result = + HtmlNormalizer.normalizeHtmlAndEventHandlers( + + +
        Booyan
        + + +
        Booyan
        +
        Booyan
        +
        +
        Booyan
        +

        +

        Booyan
        +

        +
        + +

        Thingies

        Booyan

        +

        More thingies

        + + , + "/context-path", + false, + true + ).nodes + + (result \\ "form").map(_ \@ "action") must_== + "/context-path/testform1" :: + "/context-path/testform2" :: + "testform3" :: + "testform4" :: + "/context-path/testform5" :: + "/context-path/testform6" :: Nil + } + + "not rewrite script srcs anywhere" in { + val result = + URLRewriter.doWith((_: String) => "rewritten") { + HtmlNormalizer.normalizeHtmlAndEventHandlers( + + + + + + +
        +

        + + + + + +

        +

        + +

        +
        + +

        Thingies

        +

        More thingies

        + + + + val NodesAndEventJs(html, js) = + HtmlNormalizer.normalizeHtmlAndEventHandlers( + startingHtml, + "/context-path", + false, + false + ) + + html.toString must_== startingHtml.toString + js.toJsCmd.length must_== 0 + } + + "not extract events from hrefs and actions" in { + val startingHtml = +
        + + + + // Note here we have the same behavior as browsers: javascript:/ + // is *processed as JavaScript* but it is *invalid JavaScript* + // (i.e., it corresponds to a JS expression that starts with `/`). + +
        + + val NodesAndEventJs(html, js) = + HtmlNormalizer.normalizeHtmlAndEventHandlers( + startingHtml, + "/context-path", + false, + false + ) + + html.toString must_== startingHtml.toString + js.toJsCmd.length must_== 0 + } + + "normalize absolute link hrefs everywhere" in { + val result = + HtmlNormalizer.normalizeHtmlAndEventHandlers( + + + + + + + +
        +

        + +

        +
        + +

        Thingies

        +

        More thingies

        + + , + "/context-path", + false, false ).nodes - (result \\ "link").map(_ \@ "href") must_== + (result \\ "link").map(_ \@ "href") must_== "/context-path/testlink" :: "/context-path/testlink2" :: "/context-path/testlink3" :: Nil @@ -247,10 +573,11 @@ class HtmlNormalizerSpec extends Specification with XmlMatchers with Mockito { , "/context-path", + false, false ).nodes - (result \\ "script").map(_ \@ "src") must_== + (result \\ "script").map(_ \@ "src") must_== "/context-path/testscript" :: "/context-path/testscript2" :: Nil } @@ -277,10 +604,11 @@ class HtmlNormalizerSpec extends Specification with XmlMatchers with Mockito { , "/context-path", + false, false ).nodes - (result \\ "a").map(_ \@ "href") must_== + (result \\ "a").map(_ \@ "href") must_== "/context-path/testa1" :: "/context-path/testa2" :: "testa3" :: @@ -311,10 +639,11 @@ class HtmlNormalizerSpec extends Specification with XmlMatchers with Mockito { , "/context-path", + false, false ).nodes - (result \\ "form").map(_ \@ "action") must_== + (result \\ "form").map(_ \@ "action") must_== "/context-path/testform1" :: "/context-path/testform2" :: "testform3" :: @@ -344,11 +673,12 @@ class HtmlNormalizerSpec extends Specification with XmlMatchers with Mockito { , "/context-path", + false, false ).nodes } - (result \\ "script").map(_ \@ "src") must_== + (result \\ "script").map(_ \@ "src") must_== "testscript" :: "testscript2" :: "testscript3" :: Nil @@ -375,11 +705,12 @@ class HtmlNormalizerSpec extends Specification with XmlMatchers with Mockito { , "/context-path", + false, false ).nodes } - (result \\ "link").map(_ \@ "href") must_== + (result \\ "link").map(_ \@ "href") must_== "testlink" :: "testlink2" :: "testlink3" :: Nil @@ -406,11 +737,12 @@ class HtmlNormalizerSpec extends Specification with XmlMatchers with Mockito { , "/context-path", + false, false ).nodes } - (result \\ "a").map(_ \@ "href") must_== + (result \\ "a").map(_ \@ "href") must_== "rewritten" :: "rewritten" :: "rewritten" :: Nil @@ -437,11 +769,12 @@ class HtmlNormalizerSpec extends Specification with XmlMatchers with Mockito { , "/context-path", + false, false ).nodes } - (result \\ "form").map(_ \@ "action") must_== + (result \\ "form").map(_ \@ "action") must_== "rewritten" :: "rewritten" :: "rewritten" :: Nil From 7ecb1ebba6e14c3503928d75db770131c39be042 Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Mon, 12 Sep 2016 10:02:59 +0530 Subject: [PATCH 1424/1949] Replace "can" with "box" --- .../scala/net/liftweb/common/BoxSpec.scala | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala index aa93620a6c..e89858d0ef 100644 --- a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala +++ b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala @@ -135,20 +135,22 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { "define a 'map' method to transform its value" in { Full(1) map { _.toString } must_== Full("1") } - "define a 'flatMap' method transforming its value in another Box. If the value is transformed in a Full can, the total result is a Full can" in { + "define a 'flatMap' method transforming its value in another Box. If the value is transformed in a Full box, the total result is a Full box" in { Full(1) flatMap { x: Int => if (x > 0) Full("full") else Empty } must_== Full("full") } - "define a 'flatMap' method transforming its value in another Box. If the value is transformed in an Empty can, the total result is an Empty can" in { + "define a 'flatMap' method transforming its value in another Box. If the value is transformed in an Empty box, the total result is an Empty box" in { Full(0) flatMap { x: Int => if (x > 0) Full("full") else Empty } must beEmpty } - "define a 'flatten' method if it contains another Box. If the inner box is a Full can, the final result is a Full can" in { - Full(Full(1)).flatten must_== Full(1) - } - "define a 'flatten' method if it contains another Box. If the inner box is a Failure, the final result is a Failure" in { - Full(Failure("error", Empty, Empty)).flatten must_== Failure("error", Empty, Empty) - } - "define a 'flatten' method if it contains another Box. If the inner box is an Empty can, the final result is an Empty can" in { - Full(Empty).flatten must_== Empty + "define a 'flatten' method if it contains another Box." in { + "If the inner box is a Full box, the final result is identical to that box" in { + Full(Full(1)).flatten must_== Full(1) + } + "If the inner box is a Failure, the final result is identical to that box" in { + Full(Failure("error", Empty, Empty)).flatten must_== Failure("error", Empty, Empty) + } + "If the inner box is an Empty box, the final result is identical to that box" in { + Full(Empty).flatten must_== Empty + } } "define an 'elements' method returning an iterator containing its value" in { Full(1).elements.next must_== 1 From 81ec3ca8bc00dcbac8800eba375d2967a669df06 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Thu, 22 Sep 2016 17:38:00 -0400 Subject: [PATCH 1425/1949] Add note about liftVanilla being experimental in LiftRules. Per agreement on the mailing list on where the best place to indicate this is. --- web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index d87445fe25..94df785e4d 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -403,6 +403,11 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { /** * Holds the JS library specific UI artifacts. By default it uses JQuery's artifacts + * + * Please note that currently any setting other than `JQueryArtifacts` will switch + * you to using Lift's liftVanilla implementation, which is meant to work independent + * of any framework. '''This implementation is experimental in Lift 3.0, so use it at + * your own risk and make sure you test your application!''' */ @volatile var jsArtifacts: JSArtifacts = JQueryArtifacts From 3a2fe971f30e8cdef4f0fc7891184724c822a0c3 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Fri, 23 Sep 2016 19:55:21 -0400 Subject: [PATCH 1426/1949] Default extractInlineJavaScript to false. Per committer consensus, we're disabling this by default for the initial release of Lift 3. --- web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 94df785e4d..0992f6fe30 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -591,7 +591,7 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { * security policy, you can allow inline scripts and set * `extractEventAttributes` to false to disable event extraction. */ - @volatile var extractInlineJavaScript: Boolean = true + @volatile var extractInlineJavaScript: Boolean = false /** * The attribute used to expose the names of event attributes that were From 9b8de87bc25a04d0cacd4936f975b280ee4cfe26 Mon Sep 17 00:00:00 2001 From: Piotr Dyraga Date: Fri, 7 Oct 2016 16:03:31 +0200 Subject: [PATCH 1427/1949] Request/session - aware futures Lift 3 allows to bind Future / LAFuture instance to template elements easily. Still, when Future gets transformed with one of its map/flatMap/etc methods or when Future body gets executed we don't have an access to current Req or LiftSession. This issue seems to be recurring between various projects using Lift framework. This commit brings request and session-aware futures. It allows to create such a type of LAFuture or decorate Scala's Future instance so that Req and LifSession parameters can be accessed from it. --- .../scala/net/liftweb/actor/LAFuture.scala | 71 +++-- .../net/liftweb/http/FutureWithSession.scala | 128 ++++++++ .../liftweb/http/LAFutureWithSession.scala | 48 +++ .../liftweb/http/FutureWithSessionSpec.scala | 290 ++++++++++++++++++ .../http/LAFutureWithSessionSpec.scala | 251 +++++++++++++++ 5 files changed, 765 insertions(+), 23 deletions(-) create mode 100644 web/webkit/src/main/scala/net/liftweb/http/FutureWithSession.scala create mode 100644 web/webkit/src/main/scala/net/liftweb/http/LAFutureWithSession.scala create mode 100644 web/webkit/src/test/scala/net/liftweb/http/FutureWithSessionSpec.scala create mode 100644 web/webkit/src/test/scala/net/liftweb/http/LAFutureWithSessionSpec.scala diff --git a/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala b/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala index c17e422692..e12f1982b6 100644 --- a/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala +++ b/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala @@ -15,7 +15,7 @@ */ package net.liftweb -package actor +package actor import common._ @@ -24,7 +24,7 @@ import common._ * A container that contains a calculated value * or may contain one in the future */ -class LAFuture[T](val scheduler: LAScheduler) { +class LAFuture[T](val scheduler: LAScheduler = LAScheduler, context: Box[LAFuture.Context] = Empty) { private var item: T = _ private var failure: Box[Nothing] = Empty private var satisfied = false @@ -33,10 +33,6 @@ class LAFuture[T](val scheduler: LAScheduler) { private var onFailure: List[Box[Nothing] => Unit] = Nil private var onComplete: List[Box[T] => Unit] = Nil - def this() { - this(LAScheduler) - } - LAFuture.notifyObservers(this) /** @@ -105,16 +101,18 @@ class LAFuture[T](val scheduler: LAScheduler) { * @return a Future that represents the function applied to the value of the future */ def map[A](f: T => A): LAFuture[A] = { - val ret = new LAFuture[A](scheduler) - onComplete(v => ret.complete(v.flatMap(n => Box.tryo(f(n))))) + val ret = new LAFuture[A](scheduler, context) + val contextFn = LAFuture.inContext(f, context) + onComplete(v => ret.complete(v.flatMap(n => Box.tryo(contextFn(n))))) ret } def flatMap[A](f: T => LAFuture[A]): LAFuture[A] = { - val ret = new LAFuture[A](scheduler) + val ret = new LAFuture[A](scheduler, context) + val contextFn = LAFuture.inContext(f, context) onComplete(v => v match { case Full(v) => - Box.tryo(f(v)) match { + Box.tryo(contextFn(v)) match { case Full(successfullyComputedFuture) => successfullyComputedFuture.onComplete(v2 => ret.complete(v2)) case e: EmptyBox => ret.complete(e) } @@ -124,7 +122,7 @@ class LAFuture[T](val scheduler: LAScheduler) { } def filter(f: T => Boolean): LAFuture[T] = { - val ret = new LAFuture[T](scheduler) + val ret = new LAFuture[T](scheduler, context) onComplete(v => ret.complete(v.filter(f))) ret } @@ -174,8 +172,9 @@ class LAFuture[T](val scheduler: LAScheduler) { * @param f the function to execute on success. */ def onSuccess(f: T => Unit) { + val contextFn = LAFuture.inContext(f, context) synchronized { - if (satisfied) {LAFuture.executeWithObservers(scheduler, () => f(item))} else + if (satisfied) {LAFuture.executeWithObservers(scheduler, () => contextFn(item))} else if (!aborted) { toDo ::= f } @@ -188,10 +187,11 @@ class LAFuture[T](val scheduler: LAScheduler) { * @param f the function to execute. Will receive a Box[Nothing] which may be a Failure if there's exception data */ def onFail(f: Box[Nothing] => Unit) { + val contextFn = LAFuture.inContext(f, context) synchronized { - if (aborted) LAFuture.executeWithObservers(scheduler, () => f(failure)) else + if (aborted) LAFuture.executeWithObservers(scheduler, () => contextFn(failure)) else if (!satisfied) { - onFailure ::= f + onFailure ::= contextFn } } } @@ -202,10 +202,11 @@ class LAFuture[T](val scheduler: LAScheduler) { * @param f the function to execute on completion of the Future */ def onComplete(f: Box[T] => Unit) { + val contextFn = LAFuture.inContext(f, context) synchronized { - if (satisfied) {LAFuture.executeWithObservers(scheduler, () => f(Full(item)))} else - if (aborted) {LAFuture.executeWithObservers(scheduler, () => f(failure))} else - onComplete ::= f + if (satisfied) {LAFuture.executeWithObservers(scheduler, () => contextFn(Full(item)))} else + if (aborted) {LAFuture.executeWithObservers(scheduler, () => contextFn(failure))} else + onComplete ::= contextFn } } @@ -259,11 +260,12 @@ object LAFuture { * @tparam T the type * @return an LAFuture that will yield its value when the value has been computed */ - def apply[T](f: () => T, scheduler: LAScheduler = LAScheduler): LAFuture[T] = { - val ret = new LAFuture[T](scheduler) + def apply[T](f: () => T, scheduler: LAScheduler = LAScheduler, context: Box[Context] = Empty): LAFuture[T] = { + val ret = new LAFuture[T](scheduler, context) + val contextFn = inContext(f, context) scheduler.execute(() => { try { - ret.satisfy(f()) + ret.satisfy(contextFn()) } catch { case e: Exception => ret.fail(e) } @@ -277,8 +279,8 @@ object LAFuture { * @tparam T the type that * @return */ - def build[T](f: => T, scheduler: LAScheduler = LAScheduler): LAFuture[T] = { - this.apply(() => f, scheduler) + def build[T](f: => T, scheduler: LAScheduler = LAScheduler, context: Box[Context] = Empty): LAFuture[T] = { + this.apply(() => f, scheduler, context) } private val threadInfo = new ThreadLocal[List[LAFuture[_] => Unit]] @@ -406,5 +408,28 @@ object LAFuture { ret } -} + private def inContext[T](f: () => T, context: Box[LAFuture.Context]): () => T = { + context.map(_.around(f)) openOr f + } + + private def inContext[A, T](f: (A) => T, context: Box[LAFuture.Context]): (A) => T = { + context.map(_.around(f)) openOr f + } + + /** + * Allows to wrap function in another function providing some additional functionality. + * It may choose to execute or not execute that functionality, but should not interpret + * or change the returned value; instead, it should perform orthogonal actions that + * need to occur around the given functionality. Typical example is setting up DB + * transaction. + * + * This is similar to [[net.liftweb.common.CommonLoanWrapper]], however, it decorates the + * function eagerly. This way, you can access current thread's state which is essential + * to set up e.g. HTTP session wrapper. + */ + trait Context { + def around[T](fn: () => T): () => T + def around[A, T](fn: (A) => T): (A) => T + } +} diff --git a/web/webkit/src/main/scala/net/liftweb/http/FutureWithSession.scala b/web/webkit/src/main/scala/net/liftweb/http/FutureWithSession.scala new file mode 100644 index 0000000000..dfa9e6324a --- /dev/null +++ b/web/webkit/src/main/scala/net/liftweb/http/FutureWithSession.scala @@ -0,0 +1,128 @@ +package net.liftweb.http + +import net.liftweb.common.Full + +import scala.concurrent.duration.Duration +import scala.concurrent.{CanAwait, ExecutionContext, Future} +import scala.util.Try + +/** + * Decorates `Future` instance to allow access to session and request resources. Should be created with + * `FutureWithSession.withCurrentSession` that takes the current Lift request and session and make it available + * to all transformation methods and initial `Future` execution body. Each transformation method returns + * `FutureWithSession`, thus, they can be all chained together. + * + * It's important to bear in mind that each chained method requires current thread's `LiftSession` to be available. + * `FutureWithSession` does _not_ propagate initial session or request to all chained methods. + * + * @see FutureWithSession.withCurrentSession + * + * @param delegate original `Future` instance that will be enriched with session and request access + */ +private[http] class FutureWithSession[T](private[this] val delegate: Future[T]) extends Future[T] { + + import FutureWithSession.withCurrentSession + + override def isCompleted: Boolean = delegate.isCompleted + + override def value: Option[Try[T]] = delegate.value + + override def result(atMost: Duration)(implicit permit: CanAwait): T = delegate.result(atMost) + + override def ready(atMost: Duration)(implicit permit: CanAwait) = { + delegate.ready(atMost) + this + } + + override def onComplete[U](f: (Try[T]) => U)(implicit executor:ExecutionContext): Unit = { + val sessionFn = withCurrentSession(f) + delegate.onComplete(sessionFn) + } + + override def map[S](f: T => S)(implicit executor: ExecutionContext): FutureWithSession[S] = { + val sessionFn = withCurrentSession(f) + new FutureWithSession(delegate.map(sessionFn)) + } + + override def flatMap[S](f: T => Future[S])(implicit executor: ExecutionContext): FutureWithSession[S] = { + val sessionFn = withCurrentSession(f) + new FutureWithSession(delegate.flatMap(sessionFn)) + } + + override def andThen[U](pf: PartialFunction[Try[T], U])(implicit executor: ExecutionContext): FutureWithSession[T] = { + val sessionFn = withCurrentSession(pf) + new FutureWithSession(delegate.andThen { + case t => sessionFn(t) + }) + } + + override def failed: FutureWithSession[Throwable] = { + new FutureWithSession(delegate.failed) + } + + override def fallbackTo[U >: T](that: Future[U]): FutureWithSession[U] = { + new FutureWithSession[U](delegate.fallbackTo(that)) + } + + override def recover[U >: T](pf: PartialFunction[Throwable, U])(implicit executor: ExecutionContext): FutureWithSession[U] = { + val sessionFn = withCurrentSession(pf) + new FutureWithSession(delegate.recover { + case t => sessionFn(t) + }) + } + + override def recoverWith[U >: T](pf: PartialFunction[Throwable, Future[U]])(implicit executor: ExecutionContext): FutureWithSession[U] = { + val sessionFn = withCurrentSession(pf) + new FutureWithSession(delegate.recoverWith { + case t => sessionFn(t) + }) + } + + override def transform[S](s: T => S, f: Throwable => Throwable)(implicit executor: ExecutionContext): Future[S] = { + val sessionSuccessFn = withCurrentSession(s) + val sessionFailureFn = withCurrentSession(f) + + new FutureWithSession(delegate.transform(s => sessionSuccessFn(s), f => sessionFailureFn(f))) + } + + override def zip[U](that: Future[U]): Future[(T, U)] = { + new FutureWithSession(delegate.zip(that)) + } +} + +object FutureWithSession { + + /** + * Creates `Future` instance aware of current request and session. Each `Future` returned by chained + * transformation method (e.g. `map`, `flatMap`) will be also request/session-aware. However, it's + * important to bear in mind that initial request and session are not propagated to chained methods. + * It's required that current execution thread for chained method has its own request/session available + * if reading/writing some data to it as a part of chained method execution. + */ + def withCurrentSession[T](task: => T)(implicit executionContext: ExecutionContext): Future[T] = { + FutureWithSession(task) + } + + private def apply[T](task: => T)(implicit executionContext: ExecutionContext): FutureWithSession[T] = { + S.session match { + case Full(_) => + val sessionFn = withCurrentSession(() => task) + new FutureWithSession(Future[T](sessionFn())) + + case _ => + new FutureWithSession(Future.failed[T]( + new IllegalStateException("LiftSession not available in this thread context") + )) + } + } + + private def withCurrentSession[T](task: () => T): () => T = { + val session = S.session openOrThrowException "LiftSession not available in this thread context" + session.buildDeferredFunction(task) + } + + private def withCurrentSession[A,T](task: (A)=>T): (A)=>T = { + val session = S.session openOrThrowException "LiftSession not available in this thread context" + session.buildDeferredFunction(task) + } +} diff --git a/web/webkit/src/main/scala/net/liftweb/http/LAFutureWithSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LAFutureWithSession.scala new file mode 100644 index 0000000000..132afd55cd --- /dev/null +++ b/web/webkit/src/main/scala/net/liftweb/http/LAFutureWithSession.scala @@ -0,0 +1,48 @@ +package net.liftweb.http + +import net.liftweb.actor.{LAFuture, LAScheduler} +import net.liftweb.common.{EmptyBox, Failure, Full} + + +object LAFutureWithSession { + + /** + * Creates `LAFuture` instance aware of current request and session. Each `LAFuture` returned by chained + * transformation method (e.g. `map`, `flatMap`) will be also request/session-aware. However, it's + * important to bear in mind that initial session or request are not propagated to chained methods. It's required + * that current execution thread for chained method has its own request or session available if reading/writing + * some data to it as a part of chained method execution. + */ + def withCurrentSession[T](task: => T, scheduler: LAScheduler = LAScheduler): LAFuture[T] = { + S.session match { + case Full(session) => + withSession(task, scheduler) + + case empty: EmptyBox => + withFailure(empty ?~! "LiftSession not available in this thread context", scheduler) + } + } + + private[this] def withSession[T](task: => T, scheduler: LAScheduler): LAFuture[T] = { + val sessionContext = new LAFuture.Context { + + def around[S](fn: () => S): () => S = { + val session = S.session openOrThrowException "LiftSession not available in this thread context" + session.buildDeferredFunction(fn) + } + + def around[A, S](fn: (A) => S): (A) => S = { + val session = S.session openOrThrowException "LiftSession not available in this thread context" + session.buildDeferredFunction(fn) + } + } + + LAFuture.build(task, scheduler, Full(sessionContext)) + } + + private[this] def withFailure[T](failure: Failure, scheduler: LAScheduler): LAFuture[T] = { + val future = new LAFuture[T](scheduler) + future.complete(failure) + future + } +} diff --git a/web/webkit/src/test/scala/net/liftweb/http/FutureWithSessionSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/FutureWithSessionSpec.scala new file mode 100644 index 0000000000..b67524cc09 --- /dev/null +++ b/web/webkit/src/test/scala/net/liftweb/http/FutureWithSessionSpec.scala @@ -0,0 +1,290 @@ +package net.liftweb.http + +import net.liftweb.common.Empty +import net.liftweb.mockweb.WebSpec +import org.specs2.matcher.ThrownMessages + +import scala.concurrent.Future +import scala.concurrent.ExecutionContext.Implicits.global +import scala.util.{Failure, Success} + +class FutureWithSessionSpec extends WebSpec with ThrownMessages { + + sequential + + object SessionVar1 extends SessionVar[String]("Uninitialized1") + object SessionVar2 extends SessionVar[String]("Uninitialized2") + + object ReqVar1 extends RequestVar[String]("Uninitialized1") + object ReqVar2 extends RequestVar[String]("Uninitialized2") + + "FutureWithSession" should { + + "fail if session is not available" in { + val future = FutureWithSession.withCurrentSession("kaboom") + + future.value must eventually(beSome(beFailedTry[String].withThrowable[IllegalStateException]( + "LiftSession not available in this thread context" + ))) + } + + "succeed with original value if session is available" withSFor "/" in { + val future = FutureWithSession.withCurrentSession("works!") + + future.value must eventually(beEqualTo(Some(Success("works!")))) + } + + "have access to session variables in Future task" withSFor "/" in { + SessionVar1("dzien dobry") + + val future = FutureWithSession.withCurrentSession(SessionVar1.is) + + future.value must eventually(beEqualTo(Some(Success("dzien dobry")))) + } + + "have access to request variables in Future task" withSFor "/" in { + ReqVar1("guten tag") + + val future = FutureWithSession.withCurrentSession(ReqVar1.is) + + future.value must eventually(beEqualTo(Some(Success("guten tag")))) + } + + "have access to session variables in onComplete()" withSFor "/" in { + // workaround for a possible race condition in SessionVar + // https://groups.google.com/forum/#!topic/liftweb/V1pWy14Wl3A + SessionVar1.is + + val future = FutureWithSession.withCurrentSession("thorgal") + future.onComplete { + case Success(v) => SessionVar1(v) + case Failure(reason) => ko("Future execution failed: " + reason) + } + + SessionVar1.is must eventually(beEqualTo("thorgal")) + } + + "have access to request variables in onComplete()" withSFor "/" in { + val future = FutureWithSession.withCurrentSession("thor") + future.onComplete { + case Success(v) => ReqVar1(v) + case Failure(reason) => ko("Future execution failed: " + reason) + } + + ReqVar1.is must eventually(beEqualTo("thor")) + } + + "have access to session variables in chains of map()" withSFor "/" in { + SessionVar1("b") + SessionVar2("c") + + val future = FutureWithSession.withCurrentSession("a") + val mapped = future.map(_ + SessionVar1.is).map(_ + SessionVar2.is) + + mapped.value must eventually(beEqualTo(Some(Success("abc")))) + } + + "have access to request variables in chains of map()" withSFor "/" in { + ReqVar1("b") + ReqVar2("c") + + val future = FutureWithSession.withCurrentSession("a") + val mapped = future.map(_ + ReqVar1.is).map(_ + ReqVar2.is) + + mapped.value must eventually(beEqualTo(Some(Success("abc")))) + } + + "have access to session variables in chains of flatMap()" withSFor "/" in { + SessionVar1("e") + SessionVar2("f") + + val future = FutureWithSession.withCurrentSession("d") + val mapped = future + .flatMap { s => val out = s + SessionVar1.is; Future(out) } + .flatMap { s => val out = s + SessionVar2.is; Future(out) } + + mapped.value must eventually(beEqualTo(Some(Success("def")))) + } + + "have access to request variables in chains of flatMap()" withSFor "/" in { + ReqVar1("e") + ReqVar2("f") + + val future = FutureWithSession.withCurrentSession("d") + val mapped = future + .flatMap { s => val out = s + ReqVar1.is; Future(out) } + .flatMap { s => val out = s + ReqVar2.is; Future(out) } + + mapped.value must eventually(beEqualTo(Some(Success("def")))) + } + + "have access to session variables in chains of andThen()" withSFor "/" in { + // workaround for a possible race condition in SessionVar + // https://groups.google.com/forum/#!topic/liftweb/V1pWy14Wl3A + SessionVar1.is + SessionVar2.is + + val future = FutureWithSession.withCurrentSession("rambo") + .andThen { case Success(v) => SessionVar1(v) } + .andThen { case Success(v) => SessionVar2(v) } + + SessionVar1.is must eventually(beEqualTo("rambo")) + SessionVar2.is must eventually(beEqualTo("rambo")) + future.value must eventually(beEqualTo(Some(Success("rambo")))) + } + + "have access to request variables in chains of andThen()" withSFor "/" in { + val future = FutureWithSession.withCurrentSession("conan") + .andThen { case Success(v) => ReqVar1(v) } + .andThen { case Success(v) => ReqVar2(v) } + + ReqVar1.is must eventually(beEqualTo("conan")) + ReqVar2.is must eventually(beEqualTo("conan")) + future.value must eventually(beEqualTo(Some(Success("conan")))) + } + + "have access to session variables in failed projection" withSFor "/" in { + SessionVar1("on purpose") + + val future = FutureWithSession.withCurrentSession(throw new Exception("failed")).failed.collect { + case e: Exception => e.getMessage + " " + SessionVar1.is + } + + future.value must eventually(beEqualTo(Some(Success("failed on purpose")))) + } + + "have access to request variables in failed projection" withSFor "/" in { + ReqVar1("on purpose") + + val future = FutureWithSession.withCurrentSession(throw new Exception("failed")).failed.collect { + case e: Exception => e.getMessage + " " + ReqVar1.is + } + + future.value must eventually(beEqualTo(Some(Success("failed on purpose")))) + } + + "have access to session variables in fallbackTo() result" withSFor "/" in { + SessionVar1("result") + + val future = FutureWithSession.withCurrentSession(throw new Exception("failed")) + .fallbackTo(Future("fallback")).map(_ + " " + SessionVar1.is) + + future.value must eventually(beEqualTo(Some(Success("fallback result")))) + } + + "have access to request variables in fallbackTo() result" withSFor "/" in { + ReqVar1("result") + + val future = FutureWithSession.withCurrentSession(throw new Exception("failed")) + .fallbackTo(Future("fallback")).map(_ + " " + ReqVar1.is) + + future.value must eventually(beEqualTo(Some(Success("fallback result")))) + } + + "have access to session variables with recover()" withSFor "/" in { + SessionVar1("g") + SessionVar2("h") + + val future = FutureWithSession.withCurrentSession(throw new Exception("failed")) + .recover { case e: Exception => e.getMessage + " " + SessionVar1.is } + .map(_ + SessionVar2.is) + + future.value must eventually(beEqualTo(Some(Success("failed gh")))) + } + + "have access to request variables with recover()" withSFor "/" in { + ReqVar1("g") + ReqVar2("h") + + val future = FutureWithSession.withCurrentSession(throw new Exception("failed")) + .recover { case e: Exception => e.getMessage + " " + ReqVar1.is } + .map(_ + ReqVar2.is) + + future.value must eventually(beEqualTo(Some(Success("failed gh")))) + } + + "have access to session variables with recoverWith()" withSFor "/" in { + SessionVar1("i") + SessionVar2("j") + + val future = FutureWithSession.withCurrentSession(throw new Exception("failed")) + .recoverWith { case e: Exception => val out = e.getMessage + " " + SessionVar1.is; Future(out) } + .map(_ + SessionVar2.is) + + future.value must eventually(beEqualTo(Some(Success("failed ij")))) + } + + "have access to request variables with recoverWith()" withSFor "/" in { + ReqVar1("k") + ReqVar2("l") + + val future = FutureWithSession.withCurrentSession(throw new Exception("failed")) + .recoverWith { case e: Exception => val out = e.getMessage + " " + ReqVar1.is; Future(out) } + .map(_ + ReqVar2.is) + + future.value must eventually(beEqualTo(Some(Success("failed kl")))) + } + + "have access to session variables with transform()" withSFor "/" in { + SessionVar1("john") + SessionVar2("rambo") + + val future = FutureWithSession.withCurrentSession("something") + .transform(s => throw new Exception(SessionVar1.is), identity[Throwable]) + .transform(identity[String], t => new Exception(t.getMessage + " " + SessionVar2.is)) + .recover { case e: Exception => e.getMessage } + + future.value must eventually(beEqualTo(Some(Success("john rambo")))) + } + + "have access to request variables with transform()" withSFor "/" in { + ReqVar1("chuck") + ReqVar2("norris") + + val future = FutureWithSession.withCurrentSession("something") + .transform(s => throw new Exception(ReqVar1.is), identity[Throwable]) + .transform(identity[String], t => new Exception(t.getMessage + " " + ReqVar2.is)) + .recover { case e: Exception => e.getMessage } + + future.value must eventually(beEqualTo(Some(Success("chuck norris")))) + } + + "yield another session aware future with zip()" withSFor "/" in { + ReqVar1("a") + SessionVar1("hero") + + val future = FutureWithSession.withCurrentSession("gotham") + .zip(Future("needs")) + .collect { case (one, two) => one + two + ReqVar1.is + SessionVar1.is } + + future.value must eventually(beEqualTo(Some(Success("gothamneedsahero")))) + } + + "not leak out initial session between threads with their own sessions" in { + val session1 = new LiftSession("Test session 1", "", Empty) + val session2 = new LiftSession("Test session 2", "", Empty) + val session3 = new LiftSession("Test session 3", "", Empty) + + S.initIfUninitted(session1)(SessionVar1("one")) + S.initIfUninitted(session2)(SessionVar1("two")) + S.initIfUninitted(session3)(SessionVar1("three")) + + val future = S.initIfUninitted(session1)(FutureWithSession.withCurrentSession("zero")) + + S.initIfUninitted(session2) { + val mapped = future.map(v => SessionVar1.is) + mapped.value must eventually(beEqualTo(Some(Success("two")))) + } + + S.initIfUninitted(session3) { + val mapped = future.map(v => SessionVar1.is) + mapped.value must eventually(beEqualTo(Some(Success("three")))) + } + + S.initIfUninitted(session1) { + val mapped = future.map(v => SessionVar1.is) + mapped.value must eventually(beEqualTo(Some(Success("one")))) + } + } + } +} diff --git a/web/webkit/src/test/scala/net/liftweb/http/LAFutureWithSessionSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/LAFutureWithSessionSpec.scala new file mode 100644 index 0000000000..6b0323342d --- /dev/null +++ b/web/webkit/src/test/scala/net/liftweb/http/LAFutureWithSessionSpec.scala @@ -0,0 +1,251 @@ +package net.liftweb.http + +import net.liftweb.actor.LAFuture +import net.liftweb.common.{Empty, Failure, Full} +import net.liftweb.mockweb.WebSpec +import org.specs2.matcher.ThrownMessages + +class LAFutureWithSessionSpec extends WebSpec with ThrownMessages { + + sequential + + object SessionVar1 extends SessionVar[String]("Uninitialized1") + object SessionVar2 extends SessionVar[String]("Uninitialized2") + + object ReqVar1 extends RequestVar[String]("Uninitialized1") + object ReqVar2 extends RequestVar[String]("Uninitialized2") + + val timeout = 10000L + + "LAFutureWithSession" should { + + "fail if session is not available" in { + val future = LAFutureWithSession.withCurrentSession("kaboom") + + future.get(timeout) must_== Failure("LiftSession not available in this thread context", Empty, Empty) + } + + "succeed with original value if session is available" withSFor "/" in { + val future = LAFutureWithSession.withCurrentSession("works!") + + future.get(timeout) must_== Full("works!") + } + + "have access to session variables in LAFuture task" withSFor "/" in { + SessionVar1("dzien dobry") + + val future = LAFutureWithSession.withCurrentSession(SessionVar1.is) + + future.get(timeout) must_== Full("dzien dobry") + } + + "have access to request variables in LAFuture task" withSFor "/" in { + ReqVar1("guten tag") + + val future = LAFutureWithSession.withCurrentSession(ReqVar1.is) + + future.get(timeout) must_== Full("guten tag") + } + + "have access to session variables in onComplete()" withSFor "/" in { + // workaround for a possible race condition in SessionVar + // https://groups.google.com/forum/#!topic/liftweb/V1pWy14Wl3A + SessionVar1.is + + val future = LAFutureWithSession.withCurrentSession("thorgal") + + future.onComplete { + case Full(v) => SessionVar1(v) + case problem => ko("Future computation failed: " + problem) + } + + SessionVar1.is must eventually(beEqualTo("thorgal")) + } + + "have access to request variables in onComplete()" withSFor "/" in { + val future = LAFutureWithSession.withCurrentSession("thor") + future.onComplete { + case Full(v) => ReqVar1(v) + case problem => ko("Future computation failed: " + problem) + } + + ReqVar1.is must eventually(beEqualTo("thor")) + } + + "have access to session variables in onFail()" withSFor "/" in { + // workaround for a possible race condition in SessionVar + // https://groups.google.com/forum/#!topic/liftweb/V1pWy14Wl3A + SessionVar1.is + + val future = LAFutureWithSession.withCurrentSession("geralt") + future.fail(new Exception("kaboom!")) + + future.onFail { + case f: Failure => SessionVar1(f.msg) + case _ => fail("The Future should have failed") + } + + SessionVar1.is must eventually(beEqualTo("kaboom!")) + } + + "have access to request variables on onFail()" withSFor "/" in { + val future = LAFutureWithSession.withCurrentSession("geralt") + future.fail(new Exception("nope!")) + + future.onFail { + case f: Failure => ReqVar1(f.msg) + case _ => fail("The Future should have failed") + } + + ReqVar1.is must eventually(beEqualTo("nope!")) + } + + "have access to session variables in onSuccess()" withSFor "/" in { + val future = LAFutureWithSession.withCurrentSession("cookie monster") + future.satisfy("done") + + future.onSuccess(SessionVar1(_)) + + SessionVar1.is must eventually(beEqualTo("done")) + } + + "have access to request variables in onSuccess()" withSFor "/" in { + val future = LAFutureWithSession.withCurrentSession("gollum") + future.satisfy("my precious") + + future.onSuccess(ReqVar1(_)) + + ReqVar1.is must eventually(beEqualTo("my precious")) + } + + "have access to session variables in chains of filter()" withSFor "/" in { + SessionVar1("see") + SessionVar2("me") + + val future = LAFutureWithSession.withCurrentSession("they see me rollin") + val filtered = future + .filter(_.contains(SessionVar1.is)) + .filter(_.contains(SessionVar2.is)) + + filtered.get(timeout) must eventually(beEqualTo(Full("they see me rollin"))) + } + + "have access to request variables in chains of filter()" withSFor "/" in { + ReqVar1("see") + ReqVar2("me") + + val future = LAFutureWithSession.withCurrentSession("they see me rollin") + val filtered = future + .filter(_.contains(ReqVar1.is)) + .filter(_.contains(ReqVar2.is)) + + filtered.get(timeout) must eventually(beEqualTo("they see me rollin")) + } + + "have access to session variables in chains of withFilter()" withSFor "/" in { + SessionVar1("come") + SessionVar2("prey") + + val future = LAFutureWithSession.withCurrentSession("do not come between the nazgul and his prey") + val filtered = future + .withFilter(_.contains(SessionVar1.is)) + .withFilter(_.contains(SessionVar2.is)) + + filtered.get(timeout) must eventually(beEqualTo("do not come between the nazgul and his prey")) + } + + "have access to request variables in chains of withFilter()" withSFor "/" in { + ReqVar1("hurt") + ReqVar2("precious") + + val future = LAFutureWithSession.withCurrentSession("mustn't go that way, mustn't hurt the precious!") + val filtered = future + .withFilter(_.contains(ReqVar1.is)) + .withFilter(_.contains(ReqVar2.is)) + + filtered.get(timeout) must eventually(beEqualTo("mustn't go that way, mustn't hurt the precious!")) + } + + "have access to session variables in chains of map()" withSFor "/" in { + SessionVar1("b") + SessionVar2("c") + + val future = LAFutureWithSession.withCurrentSession("a") + val mapped = future.map(_ + SessionVar1.is).map(_ + SessionVar2.is) + + mapped.get(timeout) must_== Full("abc") + } + + "have access to request variables in chains of map()" withSFor "/" in { + ReqVar1("b") + ReqVar2("c") + + val future = LAFutureWithSession.withCurrentSession("a") + val mapped = future.map(_ + ReqVar1.is).map(_ + ReqVar2.is) + + mapped.get(timeout) must_== Full("abc") + } + + "have access to session variables in chains of flatMap()" withSFor "/" in { + SessionVar1("e") + SessionVar2("f") + + val future = LAFutureWithSession.withCurrentSession("d") + val mapped = future + .flatMap { s => val out = s + SessionVar1.is; LAFuture.build(out) } + .flatMap { s => val out = s + SessionVar2.is; LAFuture.build(out) } + + mapped.get(timeout) must_== Full("def") + } + + "have access to request variables in chains of flatMap()" withSFor "/" in { + ReqVar1("e") + ReqVar2("f") + + val future = LAFutureWithSession.withCurrentSession("d") + val mapped = future + .flatMap { s => val out = s + ReqVar1.is; LAFuture.build(out) } + .flatMap { s => val out = s + ReqVar2.is; LAFuture.build(out) } + + mapped.get(timeout) must_== Full("def") + } + + "have access to session variables in foreach()" withSFor "/" in { + val future = LAFutureWithSession.withCurrentSession("cookie") + future.foreach(SessionVar1(_)) + + SessionVar1.is must eventually(beEqualTo("cookie")) + } + + "have access to request variables in foreach()" withSFor "/" in { + val future = LAFutureWithSession.withCurrentSession("monster") + future.foreach(ReqVar1(_)) + + ReqVar1.is must eventually(beEqualTo("monster")) + } + + "not leak out initial session between threads with their own sessions" in { + val session1 = new LiftSession("Test session 1", "", Empty) + val session2 = new LiftSession("Test session 2", "", Empty) + val session3 = new LiftSession("Test session 3", "", Empty) + + S.initIfUninitted(session1)(SessionVar1("one")) + S.initIfUninitted(session2)(SessionVar1("two")) + S.initIfUninitted(session3)(SessionVar1("three")) + + val future = S.initIfUninitted(session1)(LAFutureWithSession.withCurrentSession("zero")) + + S.initIfUninitted(session2) { + future.map(v => SessionVar1.is).get(timeout) must eventually(beEqualTo("two")) + } + + S.initIfUninitted(session3) { + future.map(v => SessionVar1.is).get(timeout) must eventually(beEqualTo("three")) + } + + S.initIfUninitted(session1) { + future.map(v => SessionVar1.is).get(timeout) must eventually(beEqualTo("one")) + } + } + } +} From ba4f884099c4011a01e5b5cc44ecb10961d88fc2 Mon Sep 17 00:00:00 2001 From: Seth Tisue Date: Fri, 7 Oct 2016 20:28:31 -0500 Subject: [PATCH 1428/1949] use proper form of specs2 dependency required for compatibility with Scala 2.12 community build. and it's the recommended thing anyway --- contributors.md | 6 ++++++ project/Dependencies.scala | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/contributors.md b/contributors.md index 50930e40f9..114fd45508 100644 --- a/contributors.md +++ b/contributors.md @@ -227,3 +227,9 @@ Arek Burdach ### Email: ### arek.burdach at gmail dot com + +### Name: ### +Seth Tisue + +### Email: ### +seth at tisue dot net diff --git a/project/Dependencies.scala b/project/Dependencies.scala index bfd07f8ee5..673a8060cc 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -80,7 +80,7 @@ object Dependencies { lazy val jwebunit = "net.sourceforge.jwebunit" % "jwebunit-htmlunit-plugin" % "2.5" % "test" lazy val mockito_all = "org.mockito" % "mockito-all" % "1.9.0" % "test" lazy val scalacheck = "org.specs2" %% "specs2-scalacheck" % "3.7" % "test" - lazy val specs2 = "org.specs2" %% "specs2" % "3.7" % "test" + lazy val specs2 = "org.specs2" %% "specs2-core" % "3.7" % "test" lazy val scalatest = "org.scalatest" %% "scalatest" % "2.1.3" % "test" lazy val junit = "junit" % "junit" % "4.8.2" % "test" From 5700feef2bedc7747830e14a2d14ab2dcd0bd91e Mon Sep 17 00:00:00 2001 From: Piotr Dyraga Date: Mon, 10 Oct 2016 08:28:29 +0200 Subject: [PATCH 1429/1949] `ret` local val renamed to `result` (all occurrences) --- .../scala/net/liftweb/actor/LAFuture.scala | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala b/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala index e12f1982b6..21785da79d 100644 --- a/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala +++ b/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala @@ -45,12 +45,12 @@ class LAFuture[T](val scheduler: LAScheduler = LAScheduler, context: Box[LAFutur if (!satisfied && !aborted) { item = value satisfied = true - val ret = toDo + val result = toDo toDo = Nil onFailure = Nil onComplete.foreach(f => LAFuture.executeWithObservers(scheduler, () => f(Full(value)))) onComplete = Nil - ret + result } else Nil } finally { notifyAll() @@ -101,30 +101,30 @@ class LAFuture[T](val scheduler: LAScheduler = LAScheduler, context: Box[LAFutur * @return a Future that represents the function applied to the value of the future */ def map[A](f: T => A): LAFuture[A] = { - val ret = new LAFuture[A](scheduler, context) + val result = new LAFuture[A](scheduler, context) val contextFn = LAFuture.inContext(f, context) - onComplete(v => ret.complete(v.flatMap(n => Box.tryo(contextFn(n))))) - ret + onComplete(v => result.complete(v.flatMap(n => Box.tryo(contextFn(n))))) + result } def flatMap[A](f: T => LAFuture[A]): LAFuture[A] = { - val ret = new LAFuture[A](scheduler, context) + val result = new LAFuture[A](scheduler, context) val contextFn = LAFuture.inContext(f, context) onComplete(v => v match { case Full(v) => Box.tryo(contextFn(v)) match { - case Full(successfullyComputedFuture) => successfullyComputedFuture.onComplete(v2 => ret.complete(v2)) - case e: EmptyBox => ret.complete(e) + case Full(successfullyComputedFuture) => successfullyComputedFuture.onComplete(v2 => result.complete(v2)) + case e: EmptyBox => result.complete(e) } - case e: EmptyBox => ret.complete(e) + case e: EmptyBox => result.complete(e) }) - ret + result } def filter(f: T => Boolean): LAFuture[T] = { - val ret = new LAFuture[T](scheduler, context) - onComplete(v => ret.complete(v.filter(f))) - ret + val result = new LAFuture[T](scheduler, context) + onComplete(v => result.complete(v.filter(f))) + result } def withFilter(f: T => Boolean): LAFuture[T] = filter(f) @@ -261,16 +261,16 @@ object LAFuture { * @return an LAFuture that will yield its value when the value has been computed */ def apply[T](f: () => T, scheduler: LAScheduler = LAScheduler, context: Box[Context] = Empty): LAFuture[T] = { - val ret = new LAFuture[T](scheduler, context) + val result = new LAFuture[T](scheduler, context) val contextFn = inContext(f, context) scheduler.execute(() => { try { - ret.satisfy(contextFn()) + result.satisfy(contextFn()) } catch { - case e: Exception => ret.fail(e) + case e: Exception => result.fail(e) } }) - ret + result } /** @@ -337,9 +337,9 @@ object LAFuture { * collected futures are satisfied */ def collect[T](future: LAFuture[T]*): LAFuture[List[T]] = { - val ret = new LAFuture[List[T]] + val result = new LAFuture[List[T]] if (future.isEmpty) { - ret.satisfy(Nil) + result.satisfy(Nil) } else { val sync = new Object val len = future.length @@ -355,14 +355,14 @@ object LAFuture { vals.insert(idx, Full(v)) gotCnt += 1 if (gotCnt >= len) { - ret.satisfy(vals.toList.flatten) + result.satisfy(vals.toList.flatten) } } } } } - ret + result } /** @@ -373,9 +373,9 @@ object LAFuture { * returned future with an Empty */ def collectAll[T](future: LAFuture[Box[T]]*): LAFuture[Box[List[T]]] = { - val ret = new LAFuture[Box[List[T]]] + val result = new LAFuture[Box[List[T]]] if (future.isEmpty) { - ret.satisfy(Full(Nil)) + result.satisfy(Full(Nil)) } else { val sync = new Object val len = future.length @@ -393,12 +393,12 @@ object LAFuture { vals.insert(idx, Full(v)) gotCnt += 1 if (gotCnt >= len) { - ret.satisfy(Full(vals.toList.flatten)) + result.satisfy(Full(vals.toList.flatten)) } } case eb: EmptyBox => { - ret.satisfy(eb) + result.satisfy(eb) } } } @@ -406,7 +406,7 @@ object LAFuture { } } - ret + result } private def inContext[T](f: () => T, context: Box[LAFuture.Context]): () => T = { From 429844fb73a88cfee575f5619b2ce8f75459b506 Mon Sep 17 00:00:00 2001 From: Piotr Dyraga Date: Mon, 10 Oct 2016 08:31:32 +0200 Subject: [PATCH 1430/1949] Improved LAFuture.Context scaladoc (stylistic change) --- core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala b/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala index 21785da79d..2a4b32ac7c 100644 --- a/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala +++ b/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala @@ -426,7 +426,7 @@ object LAFuture { * * This is similar to [[net.liftweb.common.CommonLoanWrapper]], however, it decorates the * function eagerly. This way, you can access current thread's state which is essential - * to set up e.g. HTTP session wrapper. + * to do things like set up a HTTP session wrapper */ trait Context { def around[T](fn: () => T): () => T From cfddc7d7e5ffd817f94083af4af9843148726bc1 Mon Sep 17 00:00:00 2001 From: Piotr Dyraga Date: Mon, 10 Oct 2016 09:45:51 +0200 Subject: [PATCH 1431/1949] Fix for a possible race condition in AnyVarTrait https://groups.google.com/forum/#!topic/liftweb/V1pWy14Wl3A --- .../liftweb/http/FutureWithSessionSpec.scala | 11 +++++++- .../http/LAFutureWithSessionSpec.scala | 26 ++++++++++++++++++- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/web/webkit/src/test/scala/net/liftweb/http/FutureWithSessionSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/FutureWithSessionSpec.scala index b67524cc09..c26dac3681 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/FutureWithSessionSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/FutureWithSessionSpec.scala @@ -65,6 +65,10 @@ class FutureWithSessionSpec extends WebSpec with ThrownMessages { } "have access to request variables in onComplete()" withSFor "/" in { + // workaround for a possible race condition in AnyVarTrait + // https://groups.google.com/forum/#!topic/liftweb/V1pWy14Wl3A + ReqVar1.is + val future = FutureWithSession.withCurrentSession("thor") future.onComplete { case Success(v) => ReqVar1(v) @@ -119,7 +123,7 @@ class FutureWithSessionSpec extends WebSpec with ThrownMessages { } "have access to session variables in chains of andThen()" withSFor "/" in { - // workaround for a possible race condition in SessionVar + // workaround for a possible race condition in AnyVarTrait // https://groups.google.com/forum/#!topic/liftweb/V1pWy14Wl3A SessionVar1.is SessionVar2.is @@ -134,6 +138,11 @@ class FutureWithSessionSpec extends WebSpec with ThrownMessages { } "have access to request variables in chains of andThen()" withSFor "/" in { + // workaround for a possible race condition in AnyVarTrait + // https://groups.google.com/forum/#!topic/liftweb/V1pWy14Wl3A + ReqVar1.is + ReqVar2.is + val future = FutureWithSession.withCurrentSession("conan") .andThen { case Success(v) => ReqVar1(v) } .andThen { case Success(v) => ReqVar2(v) } diff --git a/web/webkit/src/test/scala/net/liftweb/http/LAFutureWithSessionSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/LAFutureWithSessionSpec.scala index 6b0323342d..ffcb5963bd 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/LAFutureWithSessionSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/LAFutureWithSessionSpec.scala @@ -48,7 +48,7 @@ class LAFutureWithSessionSpec extends WebSpec with ThrownMessages { } "have access to session variables in onComplete()" withSFor "/" in { - // workaround for a possible race condition in SessionVar + // workaround for a possible race condition in AnyVarTrait // https://groups.google.com/forum/#!topic/liftweb/V1pWy14Wl3A SessionVar1.is @@ -63,6 +63,10 @@ class LAFutureWithSessionSpec extends WebSpec with ThrownMessages { } "have access to request variables in onComplete()" withSFor "/" in { + // workaround for a possible race condition in AnyVarTrait + // https://groups.google.com/forum/#!topic/liftweb/V1pWy14Wl3A + ReqVar1.is + val future = LAFutureWithSession.withCurrentSession("thor") future.onComplete { case Full(v) => ReqVar1(v) @@ -89,6 +93,10 @@ class LAFutureWithSessionSpec extends WebSpec with ThrownMessages { } "have access to request variables on onFail()" withSFor "/" in { + // workaround for a possible race condition in AnyVarTrait + // https://groups.google.com/forum/#!topic/liftweb/V1pWy14Wl3A + ReqVar1.is + val future = LAFutureWithSession.withCurrentSession("geralt") future.fail(new Exception("nope!")) @@ -101,6 +109,10 @@ class LAFutureWithSessionSpec extends WebSpec with ThrownMessages { } "have access to session variables in onSuccess()" withSFor "/" in { + // workaround for a possible race condition in AnyVarTrait + // https://groups.google.com/forum/#!topic/liftweb/V1pWy14Wl3A + SessionVar1.is + val future = LAFutureWithSession.withCurrentSession("cookie monster") future.satisfy("done") @@ -110,6 +122,10 @@ class LAFutureWithSessionSpec extends WebSpec with ThrownMessages { } "have access to request variables in onSuccess()" withSFor "/" in { + // workaround for a possible race condition in AnyVarTrait + // https://groups.google.com/forum/#!topic/liftweb/V1pWy14Wl3A + ReqVar1.is + val future = LAFutureWithSession.withCurrentSession("gollum") future.satisfy("my precious") @@ -211,6 +227,10 @@ class LAFutureWithSessionSpec extends WebSpec with ThrownMessages { } "have access to session variables in foreach()" withSFor "/" in { + // workaround for a possible race condition in AnyVarTrait + // https://groups.google.com/forum/#!topic/liftweb/V1pWy14Wl3A + SessionVar1.is + val future = LAFutureWithSession.withCurrentSession("cookie") future.foreach(SessionVar1(_)) @@ -218,6 +238,10 @@ class LAFutureWithSessionSpec extends WebSpec with ThrownMessages { } "have access to request variables in foreach()" withSFor "/" in { + // workaround for a possible race condition in AnyVarTrait + // https://groups.google.com/forum/#!topic/liftweb/V1pWy14Wl3A + ReqVar1.is + val future = LAFutureWithSession.withCurrentSession("monster") future.foreach(ReqVar1(_)) From 0f8f4c4f80af719675bb98c652b84297c4ed46c2 Mon Sep 17 00:00:00 2001 From: Piotr Dyraga Date: Mon, 10 Oct 2016 11:23:44 +0200 Subject: [PATCH 1432/1949] Execute delayed function in context in onSuccess Just like in case of onComplete and onFail we should wrap a function in context before enqueuing it for future execution. --- core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala b/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala index 2a4b32ac7c..2d0ae40895 100644 --- a/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala +++ b/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala @@ -176,7 +176,7 @@ class LAFuture[T](val scheduler: LAScheduler = LAScheduler, context: Box[LAFutur synchronized { if (satisfied) {LAFuture.executeWithObservers(scheduler, () => contextFn(item))} else if (!aborted) { - toDo ::= f + toDo ::= contextFn } } } From ad66cfaa8c2293275eadd0502cb433d7a7946c04 Mon Sep 17 00:00:00 2001 From: Piotr Dyraga Date: Mon, 10 Oct 2016 17:23:55 +0200 Subject: [PATCH 1433/1949] Eliminated possible race conditions in LAFutureWithSessionSpec --- .../http/LAFutureWithSessionSpec.scala | 51 ++++++++++++++----- 1 file changed, 39 insertions(+), 12 deletions(-) diff --git a/web/webkit/src/test/scala/net/liftweb/http/LAFutureWithSessionSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/LAFutureWithSessionSpec.scala index ffcb5963bd..874dae6437 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/LAFutureWithSessionSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/LAFutureWithSessionSpec.scala @@ -52,13 +52,18 @@ class LAFutureWithSessionSpec extends WebSpec with ThrownMessages { // https://groups.google.com/forum/#!topic/liftweb/V1pWy14Wl3A SessionVar1.is - val future = LAFutureWithSession.withCurrentSession("thorgal") + val future = LAFutureWithSession.withCurrentSession { + Thread.sleep(Long.MaxValue) + "292 billion years" + } future.onComplete { case Full(v) => SessionVar1(v) case problem => ko("Future computation failed: " + problem) } + future.satisfy("thorgal") + SessionVar1.is must eventually(beEqualTo("thorgal")) } @@ -67,12 +72,18 @@ class LAFutureWithSessionSpec extends WebSpec with ThrownMessages { // https://groups.google.com/forum/#!topic/liftweb/V1pWy14Wl3A ReqVar1.is - val future = LAFutureWithSession.withCurrentSession("thor") + val future = LAFutureWithSession.withCurrentSession { + Thread.sleep(Long.MaxValue) + "292 billion years" + } + future.onComplete { case Full(v) => ReqVar1(v) case problem => ko("Future computation failed: " + problem) } + future.satisfy("thor") + ReqVar1.is must eventually(beEqualTo("thor")) } @@ -81,30 +92,38 @@ class LAFutureWithSessionSpec extends WebSpec with ThrownMessages { // https://groups.google.com/forum/#!topic/liftweb/V1pWy14Wl3A SessionVar1.is - val future = LAFutureWithSession.withCurrentSession("geralt") - future.fail(new Exception("kaboom!")) + val future = LAFutureWithSession.withCurrentSession { + Thread.sleep(Long.MaxValue) + "292 billion years" + } future.onFail { case f: Failure => SessionVar1(f.msg) case _ => fail("The Future should have failed") } + future.fail(new Exception("kaboom!")) + SessionVar1.is must eventually(beEqualTo("kaboom!")) } - "have access to request variables on onFail()" withSFor "/" in { + "have access to request variables in onFail()" withSFor "/" in { // workaround for a possible race condition in AnyVarTrait // https://groups.google.com/forum/#!topic/liftweb/V1pWy14Wl3A ReqVar1.is - val future = LAFutureWithSession.withCurrentSession("geralt") - future.fail(new Exception("nope!")) + val future = LAFutureWithSession.withCurrentSession { + Thread.sleep(Long.MaxValue) + "292 billion years" + } future.onFail { case f: Failure => ReqVar1(f.msg) case _ => fail("The Future should have failed") } + future.fail(new Exception("nope!")) + ReqVar1.is must eventually(beEqualTo("nope!")) } @@ -113,11 +132,15 @@ class LAFutureWithSessionSpec extends WebSpec with ThrownMessages { // https://groups.google.com/forum/#!topic/liftweb/V1pWy14Wl3A SessionVar1.is - val future = LAFutureWithSession.withCurrentSession("cookie monster") - future.satisfy("done") + val future = LAFutureWithSession.withCurrentSession { + Thread.sleep(Long.MaxValue) + "292 billion years" + } future.onSuccess(SessionVar1(_)) + future.satisfy("done") + SessionVar1.is must eventually(beEqualTo("done")) } @@ -126,12 +149,16 @@ class LAFutureWithSessionSpec extends WebSpec with ThrownMessages { // https://groups.google.com/forum/#!topic/liftweb/V1pWy14Wl3A ReqVar1.is - val future = LAFutureWithSession.withCurrentSession("gollum") - future.satisfy("my precious") + val future = LAFutureWithSession.withCurrentSession { + Thread.sleep(Long.MaxValue) + "292 billion years" + } future.onSuccess(ReqVar1(_)) - ReqVar1.is must eventually(beEqualTo("my precious")) + future.satisfy("my preciousss") + + ReqVar1.is must eventually(beEqualTo("my preciousss")) } "have access to session variables in chains of filter()" withSFor "/" in { From 15aca9a62a6becd092dba37908e2ea0e1b030579 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 12 Nov 2016 11:56:49 -0500 Subject: [PATCH 1434/1949] Added remaining specs2 dependencies for 3.7. 3.7 splits out specs2 into several sub-packages, and we need 3 of them for most specs and 2 for webkit's test helpers. --- build.sbt | 2 +- project/Build.scala | 2 +- project/Dependencies.scala | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index 5dd5aaeb82..911912e4d6 100644 --- a/build.sbt +++ b/build.sbt @@ -16,7 +16,7 @@ scalaVersion in ThisBuild := "2.11.7" crossScalaVersions in ThisBuild := Seq("2.11.7") -libraryDependencies in ThisBuild <++= scalaVersion {sv => Seq(specs2, scalacheck, scalatest) } +libraryDependencies in ThisBuild <++= scalaVersion {sv => Seq(specs2, specs2Matchers, specs2Mock, scalacheck, scalatest) } // Settings for Sonatype compliance pomIncludeRepository in ThisBuild := { _ => false } diff --git a/project/Build.scala b/project/Build.scala index 5094b7bc4c..6bbeb2e458 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -134,7 +134,7 @@ object BuildDef extends Build { .settings(description := "Webkit Library", parallelExecution in Test := false, libraryDependencies <++= scalaVersion { sv => - Seq(commons_fileupload, rhino, servlet_api, specs2.copy(configurations = Some("provided")), jetty6, + Seq(commons_fileupload, rhino, servlet_api, specs2.copy(configurations = Some("provided")), specs2Matchers.copy(configurations = Some("provided")), jetty6, jwebunit) }, initialize in Test <<= (sourceDirectory in Test) { src => diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 673a8060cc..1093334bc5 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -81,6 +81,8 @@ object Dependencies { lazy val mockito_all = "org.mockito" % "mockito-all" % "1.9.0" % "test" lazy val scalacheck = "org.specs2" %% "specs2-scalacheck" % "3.7" % "test" lazy val specs2 = "org.specs2" %% "specs2-core" % "3.7" % "test" + lazy val specs2Matchers = "org.specs2" %% "specs2-matcher-extra" % "3.7" % "test" + lazy val specs2Mock = "org.specs2" %% "specs2-mock" % "3.7" % "test" lazy val scalatest = "org.scalatest" %% "scalatest" % "2.1.3" % "test" lazy val junit = "junit" % "junit" % "4.8.2" % "test" From cedea34031eefec54c36bec54f0f039e99db9978 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 12 Nov 2016 12:18:06 -0500 Subject: [PATCH 1435/1949] Add 2.12 to the cross-compile and bump relevant dependencies. --- build.sbt | 4 ++-- project/Dependencies.scala | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/build.sbt b/build.sbt index 911912e4d6..20dc715d4e 100644 --- a/build.sbt +++ b/build.sbt @@ -12,9 +12,9 @@ startYear in ThisBuild := Some(2006) organizationName in ThisBuild := "WorldWide Conferencing, LLC" -scalaVersion in ThisBuild := "2.11.7" +scalaVersion in ThisBuild := "2.12.0" -crossScalaVersions in ThisBuild := Seq("2.11.7") +crossScalaVersions in ThisBuild := Seq("2.12.0", "2.11.7") libraryDependencies in ThisBuild <++= scalaVersion {sv => Seq(specs2, specs2Matchers, specs2Mock, scalacheck, scalatest) } diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 1093334bc5..86e327edc7 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -41,8 +41,8 @@ object Dependencies { lazy val scalajpa = "org.scala-libs" % "scalajpa" % "1.5" cross CVMappingAll lazy val scalap: ModuleMap = "org.scala-lang" % "scalap" % _ lazy val scala_compiler: ModuleMap = "org.scala-lang" % "scala-compiler" % _ - lazy val scalaz7_core = "org.scalaz" % "scalaz-core" % "7.2.0" cross CVMappingAll - lazy val squeryl = "org.squeryl" % "squeryl" % "0.9.5-7" cross CVMappingAll + lazy val scalaz7_core = "org.scalaz" % "scalaz-core" % "7.2.7" cross CVMappingAll + lazy val squeryl = "org.squeryl" % "squeryl" % "0.9.7" cross CVMappingAll lazy val slf4j_api = "org.slf4j" % "slf4j-api" % slf4jVersion lazy val scala_xml = "org.scala-lang.modules" %% "scala-xml" % "1.0.5" lazy val rhino = "org.mozilla" % "rhino" % "1.7.7.1" @@ -79,11 +79,11 @@ object Dependencies { lazy val jetty6 = "org.mortbay.jetty" % "jetty" % "6.1.26" % "test" lazy val jwebunit = "net.sourceforge.jwebunit" % "jwebunit-htmlunit-plugin" % "2.5" % "test" lazy val mockito_all = "org.mockito" % "mockito-all" % "1.9.0" % "test" - lazy val scalacheck = "org.specs2" %% "specs2-scalacheck" % "3.7" % "test" - lazy val specs2 = "org.specs2" %% "specs2-core" % "3.7" % "test" - lazy val specs2Matchers = "org.specs2" %% "specs2-matcher-extra" % "3.7" % "test" - lazy val specs2Mock = "org.specs2" %% "specs2-mock" % "3.7" % "test" - lazy val scalatest = "org.scalatest" %% "scalatest" % "2.1.3" % "test" + lazy val scalacheck = "org.specs2" %% "specs2-scalacheck" % "3.8.6" % "test" + lazy val specs2 = "org.specs2" %% "specs2-core" % "3.8.6" % "test" + lazy val specs2Matchers = "org.specs2" %% "specs2-matcher-extra" % "3.8.6" % "test" + lazy val specs2Mock = "org.specs2" %% "specs2-mock" % "3.8.6" % "test" + lazy val scalatest = "org.scalatest" %% "scalatest" % "3.0.1" % "test" lazy val junit = "junit" % "junit" % "4.8.2" % "test" lazy val jquery = "org.webjars.bower" % "jquery" % "1.11.3" % "provided" From dc64e4acd45dcdb5110e4cdd62bf2472b0b860e4 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 12 Nov 2016 12:19:02 -0500 Subject: [PATCH 1436/1949] Fix a compile error from 2.12 in SimpleList. --- .../scala/net/liftweb/common/SimpleList.scala | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/core/common/src/main/scala/net/liftweb/common/SimpleList.scala b/core/common/src/main/scala/net/liftweb/common/SimpleList.scala index 4063d5806a..bd416b2682 100644 --- a/core/common/src/main/scala/net/liftweb/common/SimpleList.scala +++ b/core/common/src/main/scala/net/liftweb/common/SimpleList.scala @@ -76,11 +76,11 @@ final case class SimpleList[T](underlying: List[T]) extends JavaList[T] { new JavaIterator[T] { def hasNext() = it.hasNext def next(): T = it.next() - def remove() = throw new UnsupportedOperationException() + override def remove() = throw new UnsupportedOperationException() } } - def subList(from: Int, to: Int): JavaList[T] = + def subList(from: Int, to: Int): JavaList[T] = SimpleList(underlying.drop(from).take(to - from)) def listIterator(): ListIterator[T] = @@ -89,10 +89,10 @@ final case class SimpleList[T](underlying: List[T]) extends JavaList[T] { def listIterator(pos: Int): ListIterator[T] = (new ArrayList(this)).listIterator(pos) - def indexOf(obj: Object): Int = + def indexOf(obj: Object): Int = underlying.indexOf(obj) - def lastIndexOf(obj: Object): Int = + def lastIndexOf(obj: Object): Int = underlying.lastIndexOf(obj) def add(x: T): Boolean = throw new UnsupportedOperationException() @@ -126,7 +126,7 @@ final case class SimpleList[T](underlying: List[T]) extends JavaList[T] { def toArray[X](in: Array[X with Object]): Array[X with Object] = { - val clz = in.getClass.getComponentType() + val clz = in.getClass.getComponentType() val len = underlying.length val ret = java.lang.reflect.Array.newInstance(clz, len).asInstanceOf[Array[X with Object]] @@ -166,7 +166,7 @@ final case class SimpleList[T](underlying: List[T]) extends JavaList[T] { check() } - + } /** @@ -229,11 +229,11 @@ final case class SimpleVector[T](underlying: Vector[T]) extends JavaList[T] { new JavaIterator[T] { def hasNext() = it.hasNext def next(): T = it.next() - def remove() = throw new UnsupportedOperationException() + override def remove() = throw new UnsupportedOperationException() } } - def subList(from: Int, to: Int): JavaList[T] = + def subList(from: Int, to: Int): JavaList[T] = SimpleVector(underlying.drop(from).take(to - from)) def listIterator(): ListIterator[T] = @@ -242,10 +242,10 @@ final case class SimpleVector[T](underlying: Vector[T]) extends JavaList[T] { def listIterator(pos: Int): ListIterator[T] = (new ArrayList(this)).listIterator(pos) - def indexOf(obj: Object): Int = + def indexOf(obj: Object): Int = underlying.indexOf(obj) - def lastIndexOf(obj: Object): Int = + def lastIndexOf(obj: Object): Int = underlying.lastIndexOf(obj) def add(x: T): Boolean = throw new UnsupportedOperationException() @@ -268,7 +268,7 @@ final case class SimpleVector[T](underlying: Vector[T]) extends JavaList[T] { var pos = 0 underlying.foreach { - e => + e => ret(pos) = e.asInstanceOf[Object] pos += 1 } @@ -277,7 +277,7 @@ final case class SimpleVector[T](underlying: Vector[T]) extends JavaList[T] { } def toArray[X](in: Array[X with Object]): Array[X with Object] = { - val clz = in.getClass.getComponentType() + val clz = in.getClass.getComponentType() val len = underlying.length val ret = java.lang.reflect.Array.newInstance(clz, len).asInstanceOf[Array[X with Object]] @@ -314,7 +314,7 @@ final case class SimpleVector[T](underlying: Vector[T]) extends JavaList[T] { check() } - + } // vim: set ts=2 sw=2 et: From 1da057cec0511e1347cc228a7a3a969c3a51f586 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 14 Nov 2016 12:38:42 -0500 Subject: [PATCH 1437/1949] Explicitly publish master for 2.11.7. This is in addition to the main publish, which is now for 2.12. --- travis.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/travis.sh b/travis.sh index 46dee0eaef..d7def40ef1 100755 --- a/travis.sh +++ b/travis.sh @@ -12,6 +12,7 @@ if [ "${TRAVIS_PULL_REQUEST}" = "false" ]; then if [ "${TRAVIS_BRANCH}" = "master" ]; then ./liftsh publish + ./liftsh ++2.11.7 publish elif [ "${TRAVIS_BRANCH}" = "lift_26" ]; then ./liftsh ++2.10.4 "project lift-framework-pre-111" publish ./liftsh ++2.11.1 publish From 91d970afec5ff0b77d1734d3e783a35aca6078e7 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 14 Nov 2016 12:42:45 -0500 Subject: [PATCH 1438/1949] Fix an overload issue in JObjectParser. Unclear why the private overload was so similar to the main declaration, but this now breaks in Scala 2.12 (because the two overloads are compiled to the same JVM function signature), so we fix it by nuking the overload and unifying the implementation with the non-private version. --- .../main/scala/net/liftweb/mongodb/JObjectParser.scala | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/JObjectParser.scala b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/JObjectParser.scala index c44e0fb26e..ab2e4b430b 100644 --- a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/JObjectParser.scala +++ b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/JObjectParser.scala @@ -52,19 +52,17 @@ object JObjectParser extends SimpleInjector { /* * Serialize a DBObject into a JObject */ - def serialize(a: Any)(implicit formats: Formats): JValue = serialize(a, formats) - - private def serialize(a: Any, formats: Formats): JValue = { + def serialize(a: Any)(implicit formats: Formats): JValue = { import Meta.Reflection._ a.asInstanceOf[AnyRef] match { case null => JNull case x if primitive_?(x.getClass) => primitive2jvalue(x) case x if datetype_?(x.getClass) => datetype2jvalue(x)(formats) case x if mongotype_?(x.getClass) => mongotype2jvalue(x)(formats) - case x: BasicDBList => JArray(x.toList.map( x => serialize(x, formats))) + case x: BasicDBList => JArray(x.toList.map( x => serialize(x)(formats))) case x: BasicDBObject => JObject( x.keySet.toList.map { f => - JField(f.toString, serialize(x.get(f.toString), formats)) + JField(f.toString, serialize(x.get(f.toString))(formats)) } ) case x => { From fed17c74753aaefcf7e8a04300ded38c22909dbe Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 14 Nov 2016 12:43:25 -0500 Subject: [PATCH 1439/1949] Trait/syntax updates for latest scalatest. lift-markdown used a bunch of deprecated syntax in scalatest that needed to be updated in order to avoid breakage. It also referenced ShouldMatchers, which is now just Matchers. --- .../liftweb/markdown/BaseParsersTest.scala | 18 ++++----- .../liftweb/markdown/BlockParsersTest.scala | 6 +-- .../liftweb/markdown/InlineParsersTest.scala | 10 +++-- .../liftweb/markdown/LineParsersTest.scala | 22 +++++----- .../liftweb/markdown/LineTokenizerTest.scala | 4 +- .../liftweb/markdown/TransformerTest.scala | 40 +++++++++---------- 6 files changed, 51 insertions(+), 49 deletions(-) diff --git a/core/markdown/src/test/scala/net/liftweb/markdown/BaseParsersTest.scala b/core/markdown/src/test/scala/net/liftweb/markdown/BaseParsersTest.scala index 6bcbb66fcc..64fb96dcb0 100644 --- a/core/markdown/src/test/scala/net/liftweb/markdown/BaseParsersTest.scala +++ b/core/markdown/src/test/scala/net/liftweb/markdown/BaseParsersTest.scala @@ -21,7 +21,7 @@ package net.liftweb.markdown import org.scalatest.junit.JUnitRunner import org.scalatest.FlatSpec -import org.scalatest.matchers.ShouldMatchers +import org.scalatest.Matchers import collection.SortedMap import org.junit.runner.RunWith @@ -30,13 +30,13 @@ import org.junit.runner.RunWith */ @RunWith(classOf[JUnitRunner]) -class BaseParsersTest extends FlatSpec with ShouldMatchers with BaseParsers{ +class BaseParsersTest extends FlatSpec with Matchers with BaseParsers{ "The BaseParsers" should "parse a newline" in { val p = nl apply(p, "\n") should equal ("\n") - evaluating(apply(p, "\r\n")) should produce[IllegalArgumentException] - evaluating(apply(p, " \n")) should produce[IllegalArgumentException] + an [IllegalArgumentException] should be thrownBy(apply(p, "\r\n")) + an [IllegalArgumentException] should be thrownBy(apply(p, " \n")) } it should "parse whitespace" in { @@ -47,12 +47,12 @@ class BaseParsersTest extends FlatSpec with ShouldMatchers with BaseParsers{ apply(p, "\t\t") should equal ("\t\t") apply(p, " \t \t ") should equal (" \t \t ") //we want newlines to be treated diferrently from other ws - evaluating (apply(p, "\n")) should produce[IllegalArgumentException] + an [IllegalArgumentException] should be thrownBy(apply(p, "\n")) } it should "be able to look behind" in { apply (((elem('a') ~ lookbehind(Set('a')) ~ elem('b'))^^{case a~lb~b=>a+""+b}), "ab") should equal ("ab") - evaluating {apply (((elem('a') ~ lookbehind(Set('b')) ~ elem('b'))^^{case a~b=>a+""+b}), "ab")} should produce[IllegalArgumentException] + an [IllegalArgumentException] should be thrownBy { apply (((elem('a') ~ lookbehind(Set('b')) ~ elem('b'))^^{case a~b=>a+""+b}), "ab") } apply( (elem('a') ~ not(lookbehind(Set(' ', '\t', '\n'))) ~ '*' ), "a*" ) @@ -66,9 +66,9 @@ class BaseParsersTest extends FlatSpec with ShouldMatchers with BaseParsers{ apply(p, "5") should equal ('5') apply(p, "0") should equal ('0') apply(p, "9") should equal ('9') - evaluating (apply(p, "a")) should produce[IllegalArgumentException] - evaluating (apply(p, "z")) should produce[IllegalArgumentException] - evaluating (apply(p, "<")) should produce[IllegalArgumentException] + an [IllegalArgumentException] should be thrownBy(apply(p, "a")) + an [IllegalArgumentException] should be thrownBy(apply(p, "z")) + an [IllegalArgumentException] should be thrownBy(apply(p, "<")) } } \ No newline at end of file diff --git a/core/markdown/src/test/scala/net/liftweb/markdown/BlockParsersTest.scala b/core/markdown/src/test/scala/net/liftweb/markdown/BlockParsersTest.scala index e0fe7637d7..24769d5776 100644 --- a/core/markdown/src/test/scala/net/liftweb/markdown/BlockParsersTest.scala +++ b/core/markdown/src/test/scala/net/liftweb/markdown/BlockParsersTest.scala @@ -21,7 +21,7 @@ package net.liftweb.markdown import org.junit.runner.RunWith import org.scalatest.junit.JUnitRunner -import org.scalatest.matchers.ShouldMatchers +import org.scalatest.Matchers import org.scalatest.FlatSpec import xml.{Group, NodeSeq} @@ -29,7 +29,7 @@ import xml.{Group, NodeSeq} * Tests the parsing on block level. */ @RunWith(classOf[JUnitRunner]) -class BlockParsersTest extends FlatSpec with ShouldMatchers with BlockParsers{ +class BlockParsersTest extends FlatSpec with Matchers with BlockParsers{ "The BlockParsers" should "parse optional empty lines" in { val p = optEmptyLines @@ -50,6 +50,6 @@ class BlockParsersTest extends FlatSpec with ShouldMatchers with BlockParsers{ it should "detect line types" in { val p = line(classOf[CodeLine]) apply(p, List(new CodeLine(" ", "code"))) should equal (new CodeLine(" ", "code")) - evaluating(apply(p, List(new OtherLine("foo")))) should produce[IllegalArgumentException] + an [IllegalArgumentException] should be thrownBy(apply(p, List(new OtherLine("foo")))) } } \ No newline at end of file diff --git a/core/markdown/src/test/scala/net/liftweb/markdown/InlineParsersTest.scala b/core/markdown/src/test/scala/net/liftweb/markdown/InlineParsersTest.scala index f54e5a0da7..acd2378c5f 100644 --- a/core/markdown/src/test/scala/net/liftweb/markdown/InlineParsersTest.scala +++ b/core/markdown/src/test/scala/net/liftweb/markdown/InlineParsersTest.scala @@ -20,7 +20,7 @@ package net.liftweb.markdown */ import org.scalatest.FlatSpec -import org.scalatest.matchers.ShouldMatchers +import org.scalatest.Matchers import org.junit.runner.RunWith import org.scalatest.junit.JUnitRunner @@ -28,7 +28,7 @@ import org.scalatest.junit.JUnitRunner * Tests Inline Parsing, i.e. emphasis , strong text, links, escapes etc. */ @RunWith(classOf[JUnitRunner]) -class InlineParsersTest extends FlatSpec with ShouldMatchers with InlineParsers{ +class InlineParsersTest extends FlatSpec with Matchers with InlineParsers{ /////////////////////////////////////////////////////////////// // Inline parsing Tests // @@ -44,7 +44,9 @@ class InlineParsersTest extends FlatSpec with ShouldMatchers with InlineParsers{ } def runExceptionParsingTests(p:Parser[String], l:List[String]) { - for (s <- l) evaluating{apply(p, s)} should produce[IllegalArgumentException] + for (s <- l) { + an [IllegalArgumentException] should be thrownBy { apply(p, s) } + } } val italicTests:List[(String, String)] = List( @@ -193,7 +195,7 @@ class InlineParsersTest extends FlatSpec with ShouldMatchers with InlineParsers{ it should "create fast links" in { runSucceedingParsingTests(fastLink(new InlineContext()), fastLinkTests) val p = fastLink(new InlineContext()) - evaluating(apply(p, "")) should produce[IllegalArgumentException] + an [IllegalArgumentException] should be thrownBy(apply(p, "")) } diff --git a/core/markdown/src/test/scala/net/liftweb/markdown/LineParsersTest.scala b/core/markdown/src/test/scala/net/liftweb/markdown/LineParsersTest.scala index f7b8303fee..0edfb27d4c 100644 --- a/core/markdown/src/test/scala/net/liftweb/markdown/LineParsersTest.scala +++ b/core/markdown/src/test/scala/net/liftweb/markdown/LineParsersTest.scala @@ -19,7 +19,7 @@ package net.liftweb.markdown * Christoph Henkelmann http://henkelmann.eu/ */ -import org.scalatest.matchers.ShouldMatchers +import org.scalatest.Matchers import org.scalatest.FlatSpec import org.junit.runner.RunWith import org.scalatest.junit.JUnitRunner @@ -28,7 +28,7 @@ import org.scalatest.junit.JUnitRunner * tests parsing of individual lines */ @RunWith(classOf[JUnitRunner]) -class LineParsersTest extends FlatSpec with ShouldMatchers with LineParsers{ +class LineParsersTest extends FlatSpec with Matchers with LineParsers{ "The LineParsers" should "parse horizontal rulers" in { val p = ruler @@ -73,7 +73,7 @@ class LineParsersTest extends FlatSpec with ShouldMatchers with LineParsers{ val p = emptyLine apply (p, "") should equal (new EmptyLine("")) apply (p, " \t ") should equal (new EmptyLine(" \t ")) - evaluating (apply (p, " not empty ")) should produce[IllegalArgumentException] + an [IllegalArgumentException] should be thrownBy(apply (p, " not empty ")) } it should "parse arbitrary lines as OtherLine tokens" in { @@ -86,7 +86,7 @@ class LineParsersTest extends FlatSpec with ShouldMatchers with LineParsers{ apply(p, "> quote") should equal (new BlockQuoteLine("> ", "quote")) apply(p, "> codequote") should equal (new BlockQuoteLine("> ", " codequote")) apply(p, " > codequote") should equal (new BlockQuoteLine(" > ", " codequote")) - evaluating(apply(p, "not a quote")) should produce[IllegalArgumentException] + an [IllegalArgumentException] should be thrownBy(apply(p, "not a quote")) } it should "parse unordered item start lines" in { @@ -99,9 +99,9 @@ class LineParsersTest extends FlatSpec with ShouldMatchers with LineParsers{ apply(p, " * \t foo") should equal (new UItemStartLine(" * \t ", "foo")) apply(p, " * \t foo ") should equal (new UItemStartLine(" * \t ", "foo ")) - evaluating(apply(p, "*foo")) should produce[IllegalArgumentException] - evaluating(apply(p, " * foo")) should produce[IllegalArgumentException] - evaluating(apply(p, "1. foo")) should produce[IllegalArgumentException] + an [IllegalArgumentException] should be thrownBy(apply(p, "*foo")) + an [IllegalArgumentException] should be thrownBy(apply(p, " * foo")) + an [IllegalArgumentException] should be thrownBy(apply(p, "1. foo")) apply(p, "* foo") should equal (new UItemStartLine("* ", "foo")) apply(p, "+ foo") should equal (new UItemStartLine("+ ", "foo")) @@ -119,9 +119,9 @@ class LineParsersTest extends FlatSpec with ShouldMatchers with LineParsers{ apply(p, " 4455. \t foo") should equal (OItemStartLine(" 4455. \t ", "foo")) apply(p, " 9. \t foo ") should equal (OItemStartLine(" 9. \t ", "foo ")) - evaluating(apply(p, "1.foo")) should produce[IllegalArgumentException] - evaluating(apply(p, " 1. foo")) should produce[IllegalArgumentException] - evaluating(apply(p, "* foo")) should produce[IllegalArgumentException] + an [IllegalArgumentException] should be thrownBy(apply(p, "1.foo")) + an [IllegalArgumentException] should be thrownBy(apply(p, " 1. foo")) + an [IllegalArgumentException] should be thrownBy(apply(p, "* foo")) } it should "parse link definitions" in { @@ -142,7 +142,7 @@ class LineParsersTest extends FlatSpec with ShouldMatchers with LineParsers{ val p = linkDefinitionTitle apply(p, " (Optional Title Here) ") should equal ("Optional Title Here") } - + it should "parse openings of fenced code blocks" in { val p = fencedCodeStartOrEnd apply(p, "```") should equal ( diff --git a/core/markdown/src/test/scala/net/liftweb/markdown/LineTokenizerTest.scala b/core/markdown/src/test/scala/net/liftweb/markdown/LineTokenizerTest.scala index bb7d486e04..15f116d698 100644 --- a/core/markdown/src/test/scala/net/liftweb/markdown/LineTokenizerTest.scala +++ b/core/markdown/src/test/scala/net/liftweb/markdown/LineTokenizerTest.scala @@ -19,7 +19,7 @@ package net.liftweb.markdown * Christoph Henkelmann http://henkelmann.eu/ */ -import org.scalatest.matchers.ShouldMatchers +import org.scalatest.Matchers import org.scalatest.FlatSpec import org.junit.runner.RunWith import org.scalatest.junit.JUnitRunner @@ -28,7 +28,7 @@ import org.scalatest.junit.JUnitRunner * Tests the Line Tokenizer that prepares input for parsing. */ @RunWith(classOf[JUnitRunner]) -class LineTokenizerTest extends FlatSpec with ShouldMatchers { +class LineTokenizerTest extends FlatSpec with Matchers { val tokenizer = new LineTokenizer diff --git a/core/markdown/src/test/scala/net/liftweb/markdown/TransformerTest.scala b/core/markdown/src/test/scala/net/liftweb/markdown/TransformerTest.scala index 5d3edbabe6..3ce7173d1b 100644 --- a/core/markdown/src/test/scala/net/liftweb/markdown/TransformerTest.scala +++ b/core/markdown/src/test/scala/net/liftweb/markdown/TransformerTest.scala @@ -19,7 +19,7 @@ package net.liftweb.markdown * Christoph Henkelmann http://henkelmann.eu/ */ -import org.scalatest.matchers.ShouldMatchers +import org.scalatest.Matchers import org.scalatest.FlatSpec import org.junit.runner.RunWith import org.scalatest.junit.JUnitRunner @@ -28,8 +28,8 @@ import org.scalatest.junit.JUnitRunner * Tests the behavior of the complete parser, i.e. all parsing steps together. */ @RunWith(classOf[JUnitRunner]) -class TransformerTest extends FlatSpec with ShouldMatchers with Transformer { - +class TransformerTest extends FlatSpec with Matchers with Transformer { + "The Transformer" should "create xhtml fragments from markdown" in { apply("") should equal ("") apply("\n") should equal ("") @@ -167,7 +167,7 @@ test""" """ ) apply("""* foo - + + bar - baz @@ -264,39 +264,39 @@ else in the doc, define the link:

        apply(" bla\nblub hallo\n\n") should equal ( " bla\nblub hallo\n\n") } - + it should "parse fenced code blocks" in { apply( """``` foobar System.out.println("Hello World!"); - + verbatim xml - + <-not a space-style code line 1. not a 2. list - + ## not a header ``` gotcha: not the end ----------- but this is: -``` -""" +``` +""" ) should equal ( """
        System.out.println("Hello World!");
        -    
        +
         <some> verbatim xml </some>
        -    
        +
             <-not a space-style code line
          1. not a
          2. list
        -    
        +
         ## not a header
         ``` gotcha: not the end
         -----------
         but this is:
         
        -""" +""" ) apply( @@ -305,14 +305,14 @@ System.out.println("Hello World!"); ``` And now to something completely different. old style code -""" +""" ) should equal ( """
        System.out.println("Hello World!");
         

        And now to something completely different.

        old style code
         
        -""" +""" ) apply( @@ -322,7 +322,7 @@ No need to end blocks And now to something completely different. old style code -""" +""" ) should equal ( """
        System.out.println("Hello World!");
         No need to end blocks
        @@ -330,7 +330,7 @@ No need to end blocks
         And now to something completely different.
             old style code
         
        -""" +""" ) apply( @@ -341,7 +341,7 @@ No need to end blocks And now to something completely different. old style code -""" +""" ) should equal ( """

        Some text first

        System.out.println("Hello World!");
        @@ -350,7 +350,7 @@ No need to end blocks
         And now to something completely different.
             old style code
         
        -""" +""" ) } } \ No newline at end of file From 0d71502c2d9d214bc5e2dbb909e146afc34b7255 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 14 Nov 2016 12:44:20 -0500 Subject: [PATCH 1440/1949] Add proper generation of JValue=>JValue function. We provide Cogen implementations for JField and JValue so that JValue=>JValue functions can be correctly generated by scalacheck. This allows us to better test the possibility space of functions that operate on JValue=>JValue transforms. --- .../scala/net/liftweb/json/JValueGen.scala | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/core/json/src/test/scala/net/liftweb/json/JValueGen.scala b/core/json/src/test/scala/net/liftweb/json/JValueGen.scala index e0f7797f7e..873b8f204a 100644 --- a/core/json/src/test/scala/net/liftweb/json/JValueGen.scala +++ b/core/json/src/test/scala/net/liftweb/json/JValueGen.scala @@ -18,13 +18,14 @@ package net.liftweb package json import org.scalacheck._ + import rng.Seed import Gen._ import Arbitrary.arbitrary trait JValueGen { - def genJValue: Gen[JValue] = frequency((5, genSimple), (1, wrap(genArray)), (1, wrap(genObject))) + def genJValue: Gen[JValue] = frequency((5, genSimple), (1, delay(genArray)), (1, delay(genObject))) def genSimple: Gen[JValue] = oneOf( - const(JNull), + const(JNull), arbitrary[Int].map(JInt(_)), arbitrary[Double].map(JDouble(_)), arbitrary[Boolean].map(JBool(_)), @@ -33,12 +34,32 @@ trait JValueGen { def genArray: Gen[JValue] = for (l <- genList) yield JArray(l) def genObject: Gen[JObject] = for (l <- genFieldList) yield JObject(l) + implicit def cogenJValue: Cogen[JValue] = + Cogen[JValue]({ (seed: Seed, jvalue: JValue) => + jvalue match { + case JNothing => implicitly[Cogen[Unit]].perturb(seed, ()) + case JNull => implicitly[Cogen[Unit]].perturb(seed, ()) + case JInt(value) => implicitly[Cogen[BigInt]].perturb(seed, value) + case JDouble(value) => implicitly[Cogen[Double]].perturb(seed, value) + case JString(value) => implicitly[Cogen[String]].perturb(seed, value) + case JBool(value) => implicitly[Cogen[Boolean]].perturb(seed, value) + case JArray(value) => implicitly[Cogen[List[JValue]]].perturb(seed, value) + case JObject(value) => implicitly[Cogen[List[JField]]].perturb(seed, value) + } + }) + implicit def cogenJField: Cogen[JField] = + Cogen[JField]({ (seed: Seed, field: JField) => + Cogen.perturbPair(seed, (field.name -> field.value)) + }) + + val genJValueFn: Gen[JValue=>JValue] = function1(genJValue) + def genList = Gen.containerOfN[List, JValue](listSize, genJValue) def genFieldList = Gen.containerOfN[List, JField](listSize, genField) def genField = for (name <- identifier; value <- genJValue; id <- choose(0, 1000000)) yield JField(name+id, value) def genJValueClass: Gen[Class[_ <: JValue]] = oneOf( - JNull.getClass.asInstanceOf[Class[JValue]], JNothing.getClass.asInstanceOf[Class[JValue]], classOf[JInt], + JNull.getClass.asInstanceOf[Class[JValue]], JNothing.getClass.asInstanceOf[Class[JValue]], classOf[JInt], classOf[JDouble], classOf[JBool], classOf[JString], classOf[JArray], classOf[JObject]) def listSize = choose(0, 5).sample.get @@ -48,8 +69,8 @@ trait NodeGen { import Xml.{XmlNode, XmlElem} import scala.xml.{Node, NodeSeq, Text} - def genXml: Gen[Node] = frequency((2, wrap(genNode)), (3, genElem)) - + def genXml: Gen[Node] = frequency((2, delay(genNode)), (3, genElem)) + def genNode = for { name <- genName node <- Gen.containerOfN[List, Node](children, genXml) map { seq => new XmlNode(name, seq) } From dde3a04ce6e41564546aee1eb6d82900e82b17c0 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 14 Nov 2016 12:45:39 -0500 Subject: [PATCH 1441/1949] Mark JValue functor composition pending until fixed. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Once we implemented proper JValue=>JValue generation, it became evident that JValue’s map does not actually adhere to this property, thus we mark it pending until fixed. Actually fixing the issue would require changing a pretty core aspect of how map behaves, so it’ll have to wait for a future release. --- .../src/test/scala/net/liftweb/json/JsonAstSpec.scala | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/core/json/src/test/scala/net/liftweb/json/JsonAstSpec.scala b/core/json/src/test/scala/net/liftweb/json/JsonAstSpec.scala index b4411a3648..700348107e 100644 --- a/core/json/src/test/scala/net/liftweb/json/JsonAstSpec.scala +++ b/core/json/src/test/scala/net/liftweb/json/JsonAstSpec.scala @@ -20,7 +20,8 @@ package json import org.specs2.mutable.Specification import org.specs2.ScalaCheck import org.scalacheck._ -import org.scalacheck.Prop.{forAll, forAllNoShrink} + import Arbitrary._ + import Prop.{forAll, forAllNoShrink} object JsonAstSpec extends Specification with JValueGen with ScalaCheck { "Functor identity" in { @@ -29,11 +30,12 @@ object JsonAstSpec extends Specification with JValueGen with ScalaCheck { } "Functor composition" in { - val compositionProp = (json: JValue, fa: JValue => JValue, fb: JValue => JValue) => + val compositionProp = (json: JValue, fa: JValue => JValue, fb: JValue => JValue) => { json.map(fb).map(fa) == json.map(fa compose fb) + } forAll(compositionProp) - } + }.pendingUntilFixed("Requires a fundamental change to map; see https://github.com/lift/framework/issues/1816 .") "Monoid identity" in { val identityProp = (json: JValue) => (json ++ JNothing == json) && (JNothing ++ json == json) @@ -219,4 +221,5 @@ object JsonAstSpec extends Specification with JValueGen with ScalaCheck { implicit def arbJValue: Arbitrary[JValue] = Arbitrary(genJValue) implicit def arbJObject: Arbitrary[JObject] = Arbitrary(genObject) + implicit val arbJValueFn: Arbitrary[JValue=>JValue] = Arbitrary(genJValueFn) } From 897e2ebaa27803f5ddfbcfb42a30e268a2392ace Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 16 Nov 2016 15:08:05 -0500 Subject: [PATCH 1442/1949] Bump to 3.1-SNAPSHOT. --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 911912e4d6..4f5e6d87d7 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ import Dependencies._ organization in ThisBuild := "net.liftweb" -version in ThisBuild := "3.0-SNAPSHOT" +version in ThisBuild := "3.1-SNAPSHOT" homepage in ThisBuild := Some(url("http://www.liftweb.net")) From 8009c0607d68df9014b30df48f00f9c17289a5ef Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Fri, 19 Aug 2016 22:35:20 -0400 Subject: [PATCH 1443/1949] Reduce segment churn in Buffer. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We now only add new segments to the buffer’s list of segments when absolutely necessary. In particular, marking the buffer is an indication that we’re interested in reading between the current segment and whenever `substring` is next called. If the buffer is unmarked, we always reuse the same segment. We don’t release a segment until the buffer is no longer in use. And any segments beyond the first that we allocate are reused for future marking operations that require more than one segment. --- .../scala/net/liftweb/json/JsonParser.scala | 40 ++++++++++++++----- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala index 953f32de68..d813ebe930 100644 --- a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala +++ b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala @@ -334,20 +334,29 @@ object JsonParser { /* Buffer used to parse JSON. * Buffer is divided to one or more segments (preallocated in Segments pool). */ - private[json] class Buffer(in: Reader, closeAutomatically: Boolean) { + private[json] final class Buffer(in: Reader, closeAutomatically: Boolean) { var offset = 0 var curMark = -1 var curMarkSegment = -1 var eofIsFailure = false - private[this] var segments: List[Segment] = List(Segments.apply()) + private[this] var segments = scala.collection.mutable.ArrayBuffer(Segments.apply()) private[this] var segment: Array[Char] = segments.head.seg private[this] var cur = 0 // Pointer which points current parsing location private[this] var curSegmentIdx = 0 // Pointer which points current segment - def mark = { curMark = cur; curMarkSegment = curSegmentIdx } - def back = cur = cur-1 + final def mark = { + if (curSegmentIdx > 0) { + segments(0) = segments.remove(curSegmentIdx) + curSegmentIdx = 0 + } + + curMark = cur + curMarkSegment = curSegmentIdx + } + final def back = cur = cur-1 + final def forward = cur = cur+1 - def next: Char = { + final def next: Char = { if (cur >= offset && read < 0) { if (eofIsFailure) throw new ParseException("unexpected eof", null) else EOF } else { @@ -397,11 +406,24 @@ object JsonParser { private[this] def read = { if (offset >= segment.length) { - val newSegment = Segments.apply() offset = 0 - segment = newSegment.seg - segments = segments ::: List(newSegment) - curSegmentIdx = segments.length - 1 + val segmentToUse = + (curMarkSegment: @scala.annotation.switch) match { + case -1 => + curSegmentIdx = 0 + segments(0) + case _ => + curSegmentIdx += 1 + if (curSegmentIdx < segments.length) { + segments(curSegmentIdx) + } else { + val segment = Segments.apply() + segments.append(segment) + segment + } + } + + segment = segmentToUse.seg } val length = in.read(segment, offset, segment.length-offset) From ec125f05cd1980f9b71c556ae72b23b233c9ad71 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Fri, 19 Aug 2016 22:48:09 -0400 Subject: [PATCH 1444/1949] Rework `unquote` for better data sharing. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `Buffer` now provides a unique StringBuilder that’s used during `unquote`. We also rework `substring` to allow for emitting the substring between the marked point and the current point directly into the builder, which reduces the number of array copies we need to do before materializing the full string with escape characters handled. We also rework the next-character match for escaped characters to be optimized to a switch by the compiler. Lastly, we rework the way hex characters are parsed to avoid `Integer.parseInt` and an extra string creation. --- .../scala/net/liftweb/json/JsonParser.scala | 142 ++++++++++++------ 1 file changed, 98 insertions(+), 44 deletions(-) diff --git a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala index d813ebe930..e5901e2acd 100644 --- a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala +++ b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala @@ -17,6 +17,8 @@ package net.liftweb package json +import scala.annotation.switch + /** JSON parser. */ object JsonParser { @@ -83,50 +85,81 @@ object JsonParser { case e: Exception => throw new ParseException("parsing failed", e) } finally { buf.release } } - + + private[this] final val HexChars: Array[Int] = { + val chars = new Array[Int](128) + var i = 0 + while (i < 10) { + chars(i + '0') = i + i += 1 + } + i = 0 + while (i < 16) { + chars(i + 'a') = 10 + i + chars(i + 'A') = 10 + i + i += 1 + } + chars + } + private[json] def unquote(string: String): String = unquote(new JsonParser.Buffer(new java.io.StringReader(string), false)) - - private[json] def unquote(buf: JsonParser.Buffer): String = { - def unquote0(buf: JsonParser.Buffer, base: String): String = { - val s = new java.lang.StringBuilder(base) + + private[this] def unquote(buf: JsonParser.Buffer): String = { + def unquote0(buf: JsonParser.Buffer): String = { + val builder = buf.builder + builder.delete(0, builder.length()) var c = '\\' while (c != '"') { if (c == '\\') { - buf.next match { - case '"' => s.append('"') - case '\\' => s.append('\\') - case '/' => s.append('/') - case 'b' => s.append('\b') - case 'f' => s.append('\f') - case 'n' => s.append('\n') - case 'r' => s.append('\r') - case 't' => s.append('\t') + buf.substring(intoBuilder = true) + (buf.next: @switch) match { + case '"' => builder.append('"') + case '\\' => builder.append('\\') + case '/' => builder.append('/') + case 'b' => builder.append('\b') + case 'f' => builder.append('\f') + case 'n' => builder.append('\n') + case 'r' => builder.append('\r') + case 't' => builder.append('\t') case 'u' => + var byte = 0 + var finalChar = 0 val chars = Array(buf.next, buf.next, buf.next, buf.next) - val codePoint = Integer.parseInt(new String(chars), 16) - s.appendCodePoint(codePoint) - case _ => s.append('\\') + while (byte < 4) { + finalChar = (finalChar << 4) | HexChars(chars(byte).toInt) + byte += 1 + } + builder.appendCodePoint(finalChar.toChar) + case _ => + builder.append('\\') } - } else s.append(c) + buf.mark + } c = buf.next } - s.toString + buf.substring(intoBuilder = true) + builder.toString } buf.eofIsFailure = true buf.mark var c = buf.next + var forcedReturn: String = null while (c != '"') { - if (c == '\\') { - val s = unquote0(buf, buf.substring) - buf.eofIsFailure = false - return s + (c: @switch) match { + case '\\' => + forcedReturn = unquote0(buf) + c = '"' + case _ => + c = buf.next } - c = buf.next } buf.eofIsFailure = false - buf.substring + forcedReturn match { + case null => new String(buf.substring()) + case _ => forcedReturn + } } // FIXME fail fast to prevent infinite loop, see @@ -335,6 +368,8 @@ object JsonParser { * Buffer is divided to one or more segments (preallocated in Segments pool). */ private[json] final class Buffer(in: Reader, closeAutomatically: Boolean) { + final val builder = new java.lang.StringBuilder(32) + var offset = 0 var curMark = -1 var curMarkSegment = -1 @@ -366,31 +401,50 @@ object JsonParser { } } - def substring = { - if (curSegmentIdx == curMarkSegment) new String(segment, curMark, cur-curMark-1) - else { // slower path for case when string is in two or more segments - var parts: List[(Int, Int, Array[Char])] = Nil - var i = curSegmentIdx - while (i >= curMarkSegment) { + private[this] final val emptyArray = new Array[Char](0) + final def substring(intoBuilder: Boolean = false) = { + if (curSegmentIdx == curMarkSegment) { + val substringLength = cur - curMark - 1 + if (intoBuilder) { + builder.append(segment, curMark, substringLength) + emptyArray + } else if (substringLength == 0) { + emptyArray + } else { + val array = new Array[Char](substringLength) + System.arraycopy(segment, curMark, array, 0, substringLength) + array + } + } else { // slower path for case when string is in two or more segments + val segmentCount = curSegmentIdx - curMarkSegment + 1 + val substringLength = segmentCount * Segments.segmentSize - curMark - (Segments.segmentSize - cur) - 1 + val chars = + if (intoBuilder) { + emptyArray + } else { + new Array[Char](substringLength) + } + + var i = curMarkSegment + var offset = 0 + while (i <= curSegmentIdx) { val s = segments(i).seg val start = if (i == curMarkSegment) curMark else 0 val end = if (i == curSegmentIdx) cur else s.length+1 - parts = (start, end, s) :: parts - i = i-1 - } - val len = parts.map(p => p._2 - p._1 - 1).foldLeft(0)(_ + _) - val chars = new Array[Char](len) - i = 0 - var pos = 0 - - while (i < parts.size) { - val (start, end, b) = parts(i) val partLen = end-start-1 - System.arraycopy(b, start, chars, pos, partLen) - pos = pos + partLen + if (intoBuilder) { + builder.append(s, start, partLen) + } else { + System.arraycopy(s, start, chars, offset, partLen) + } + offset += partLen i = i+1 } - new String(chars) + + curMarkSegment = -1 + curMark = -1 + + chars } } From e2619d72bb778d3b00ade5c91acb35e466a8dd56 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Fri, 19 Aug 2016 22:51:40 -0400 Subject: [PATCH 1445/1949] Build up arrays and objects with ListBuffer. Before, we were building up JObject fields and JArray entries in immutable data structures and in reverse, so we had to reverse them when they were completed. We now temporarily use a ListBuffer, which allows constant-time mutable append and cheap conversion to our final List data type, which we do once the array or object is done being built up. --- .../scala/net/liftweb/json/JsonParser.scala | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala index e5901e2acd..f2d13835be 100644 --- a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala +++ b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala @@ -171,34 +171,32 @@ object JsonParser { else d.doubleValue } + private[this] case class IntermediateJObject(fields: scala.collection.mutable.ListBuffer[JField]) + private[this] case class IntermediateJArray(bits: scala.collection.mutable.ListBuffer[JValue]) + private val astParser = (p: Parser) => { val vals = new ValStack(p) var token: Token = null var root: Option[JValue] = None - // This is a slightly faster way to correct order of fields and arrays than using 'map'. - def reverse(v: JValue): JValue = v match { - case JObject(l) => JObject((l.map { field => field.copy(value = reverse(field.value)) }).reverse) - case JArray(l) => JArray(l.map(reverse).reverse) - case x => x - } - def closeBlock(v: Any) { @inline def toJValue(x: Any) = x match { case json: JValue => json + case other: IntermediateJObject => JObject(other.fields.result) + case other: IntermediateJArray => JArray(other.bits.result) case _ => p.fail("unexpected field " + x) } vals.peekOption match { case Some(JField(name: String, value)) => vals.pop(classOf[JField]) - val obj = vals.peek(classOf[JObject]) - vals.replace(JObject(JField(name, toJValue(v)) :: obj.obj)) - case Some(o: JObject) => - vals.replace(JObject(vals.peek(classOf[JField]) :: o.obj)) - case Some(a: JArray) => vals.replace(JArray(toJValue(v) :: a.arr)) + val obj = vals.peek(classOf[IntermediateJObject]) + obj.fields.append(JField(name, toJValue(v))) + case Some(o: IntermediateJObject) => + o.fields.append(vals.peek(classOf[JField])) + case Some(a: IntermediateJArray) => a.bits.append(toJValue(v)) case Some(x) => p.fail("expected field, array or object but got " + x) - case None => root = Some(reverse(toJValue(v))) + case None => root = Some(toJValue(v)) } } @@ -207,9 +205,9 @@ object JsonParser { vals.peekAny match { case JField(name, value) => vals.pop(classOf[JField]) - val obj = vals.peek(classOf[JObject]) - vals.replace(JObject(JField(name, v) :: obj.obj)) - case a: JArray => vals.replace(JArray(v :: a.arr)) + val obj = vals.peek(classOf[IntermediateJObject]) + obj.fields.append(JField(name,v)) + case a: IntermediateJArray => a.bits.append(v) case other => p.fail("expected field or array but got " + other) } else { vals.push(v) @@ -220,7 +218,7 @@ object JsonParser { do { token = p.nextToken token match { - case OpenObj => vals.push(JObject(Nil)) + case OpenObj => vals.push(IntermediateJObject(scala.collection.mutable.ListBuffer())) case FieldStart(name) => vals.push(JField(name, null)) case StringVal(x) => newValue(JString(x)) case IntVal(x) => newValue(JInt(x)) @@ -228,8 +226,8 @@ object JsonParser { case BoolVal(x) => newValue(JBool(x)) case NullVal => newValue(JNull) case CloseObj => closeBlock(vals.popAny) - case OpenArr => vals.push(JArray(Nil)) - case CloseArr => closeBlock(vals.pop(classOf[JArray])) + case OpenArr => vals.push(IntermediateJArray(scala.collection.mutable.ListBuffer())) + case CloseArr => closeBlock(vals.popAny) case End => } } while (token != End) From c91201226be6e938190c7dd35ae0bbd12291572c Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Fri, 19 Aug 2016 22:55:39 -0400 Subject: [PATCH 1446/1949] Use ArrayDeque for ValStack and blocks stack. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since both stacks sees a reasonably large amount of churn, LinkedList’s allocation of a node on each push can get costly compared to ArrayDeque’s relatively small number of array allocations. --- .../main/scala/net/liftweb/json/JsonParser.scala | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala index f2d13835be..c199d1fb38 100644 --- a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala +++ b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala @@ -235,18 +235,21 @@ object JsonParser { root getOrElse JNothing } - private val EOF = (-1).asInstanceOf[Char] + private[this] final val EOF: Char = (-1).asInstanceOf[Char] private class ValStack(parser: Parser) { - import java.util.LinkedList - private[this] val stack = new LinkedList[Any]() + import java.util.ArrayDeque + private[this] val stack = new ArrayDeque[Any](32) def popAny = stack.poll def pop[A](expectedType: Class[A]) = convert(stack.poll, expectedType) def push(v: Any) = stack.addFirst(v) def peekAny = stack.peek def peek[A](expectedType: Class[A]) = convert(stack.peek, expectedType) - def replace[A](newTop: Any) = stack.set(0, newTop) + def replace[A](newTop: Any) = { + stack.pop + stack.push(newTop) + } private def convert[A](x: Any, expectedType: Class[A]): A = { if (x == null) parser.fail("expected object or array") @@ -258,9 +261,9 @@ object JsonParser { } class Parser(buf: Buffer) { - import java.util.LinkedList + import java.util.ArrayDeque - private[this] val blocks = new LinkedList[BlockMode]() + private[this] val blocks = new ArrayDeque[BlockMode](32) private[this] var fieldNameMode = true def fail(msg: String) = throw new ParseException(msg + "\nNear: " + buf.near, null) From cf5b13edd177e6d6c0e54f44d1d47378eb150c1e Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Fri, 19 Aug 2016 22:55:52 -0400 Subject: [PATCH 1447/1949] Optimize isDelimiter to use a switch. --- .../src/main/scala/net/liftweb/json/JsonParser.scala | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala index c199d1fb38..d5b17101e2 100644 --- a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala +++ b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala @@ -271,7 +271,14 @@ object JsonParser { /** Parse next Token from stream. */ def nextToken: Token = { - def isDelimiter(c: Char) = c == ' ' || c == '\n' || c == ',' || c == '\r' || c == '\t' || c == '}' || c == ']' + def isDelimiter(c: Char) = { + (c: @switch) match { + case ' ' | '\n' | ',' | '\r' | '\t' | '}' | ']' => + true + case _ => + false + } + } def parseString: String = try { From 022830559d737c98cec28b23d2f10483e5ccdbb8 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Fri, 19 Aug 2016 22:59:38 -0400 Subject: [PATCH 1448/1949] Optimize json parseValue. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before we would instantiate a StringBuilder when we wanted to parse a value and append characters to it as desired. Now, we use the buffer’s marking functionality to mark the beginning of the value and then simply increment the current position in the buffer until we’re ready to wrap up. Then we do a single substring() operation that equates to a single array copy, and fork that over to double or int value conversion. We also stop using Character.isDigit, which is both slow and incorrect (only 0-9 are accepted digits for JSON numbers---other Unicode digits are not), and optimize the three cases (decimal/exponent, digit/sign, everything else) to a three-part switch. --- .../scala/net/liftweb/json/JsonParser.scala | 38 ++++++++++++------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala index d5b17101e2..5260fcd9b6 100644 --- a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala +++ b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala @@ -291,23 +291,33 @@ object JsonParser { def parseValue(first: Char) = { var wasInt = true var doubleVal = false - val s = new StringBuilder - s.append(first) + val buf = this.buf + + buf.back + buf.mark while (wasInt) { val c = buf.next - if (c == EOF) { - wasInt = false - } else if (c == '.' || c == 'e' || c == 'E') { - doubleVal = true - s.append(c) - } else if (!(Character.isDigit(c) || c == '.' || c == 'e' || c == 'E' || c == '-' || c == '+')) { - wasInt = false - buf.back - } else s.append(c) + (c: @switch) match { + case '.' | 'e' | 'E' => + doubleVal = true + case '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '-' | '+' => + // continue + case _ => + wasInt = false + if (c != EOF) { + buf.back + } + } + } + buf.forward + val value = buf.substring() + buf.back + (doubleVal: @switch) match { + case true => + DoubleVal(BigDecimal(new String(value)).doubleValue) + case false => + IntVal(BigInt(new String(value))) } - val value = s.toString - if (doubleVal) DoubleVal(parseDouble(value)) - else IntVal(BigInt(value)) } while (true) { From efb0682db9ccfc0f6a21b3d4490352af61b60aba Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Fri, 19 Aug 2016 23:00:59 -0400 Subject: [PATCH 1449/1949] Use a switch in the main nextToken body. We stop using Character.isDigit here as well, since it's both slower and incorrect, and make a couple of tweaks to optimize the case/match statement into a switch on the next character. --- .../scala/net/liftweb/json/JsonParser.scala | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala index 5260fcd9b6..3ee861585e 100644 --- a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala +++ b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala @@ -271,15 +271,6 @@ object JsonParser { /** Parse next Token from stream. */ def nextToken: Token = { - def isDelimiter(c: Char) = { - (c: @switch) match { - case ' ' | '\n' | ',' | '\r' | '\t' | '}' | ']' => - true - case _ => - false - } - } - def parseString: String = try { unquote(buf) @@ -321,10 +312,7 @@ object JsonParser { } while (true) { - buf.next match { - case c if EOF == c => - buf.automaticClose - return End + (buf.next: @switch) match { case '{' => blocks.addFirst(OBJECT) fieldNameMode = true @@ -366,11 +354,19 @@ object JsonParser { fieldNameMode = true blocks.poll return CloseArr - case c if Character.isDigit(c) || c == '-' => + case c @ ('0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '-') => fieldNameMode = true return parseValue(c) - case c if isDelimiter(c) => - case c => fail("unknown token " + c) + case ' ' | '\n' | ',' | '\r' | '\t' => + // ignore + case c => + c match { + case `EOF` => + buf.automaticClose + return End + case _ => + fail("unknown token " + c) + } } } buf.automaticClose From 4ec7ee0e562f9a3975c2cb4d0644a690b79f1fb4 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Fri, 19 Aug 2016 23:02:17 -0400 Subject: [PATCH 1450/1949] Bubble unquote exception causes. --- core/json/src/main/scala/net/liftweb/json/JsonParser.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala index 3ee861585e..2e29ce2fc4 100644 --- a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala +++ b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala @@ -266,7 +266,7 @@ object JsonParser { private[this] val blocks = new ArrayDeque[BlockMode](32) private[this] var fieldNameMode = true - def fail(msg: String) = throw new ParseException(msg + "\nNear: " + buf.near, null) + def fail(msg: String, cause: Exception = null) = throw new ParseException(msg + "\nNear: " + buf.near, cause) /** Parse next Token from stream. */ @@ -276,7 +276,7 @@ object JsonParser { unquote(buf) } catch { case p: ParseException => throw p - case _: Exception => fail("unexpected string end") + case cause: Exception => fail("unexpected string end", cause) } def parseValue(first: Char) = { From e8eaaba807eabb0602b0cff45dfcdb8d64c710b1 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 21 Aug 2016 12:03:37 -0400 Subject: [PATCH 1451/1949] Rip out bad-double parsing bug fix. A version of JVM 1.6 had an issue where a particular double construction could trigger the JVM to hang. We had a workaround to that issue that required parsing doubles with BigDecimal and such. That bug is long since fixed in JVMs, both with a patch on 1.6 and forward into the newer versions. Additionally, Lift 3 is no longer built for Java 6. As a result, we rip out BigDecimal and go back to the simpler String.toDouble construcation. --- .../src/main/scala/net/liftweb/json/JsonParser.scala | 9 ++------- .../src/test/scala/net/liftweb/json/ParserBugs.scala | 5 ----- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala index 2e29ce2fc4..b4f84893a7 100644 --- a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala +++ b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala @@ -162,13 +162,8 @@ object JsonParser { } } - // FIXME fail fast to prevent infinite loop, see - // http://www.exploringbinary.com/java-hangs-when-converting-2-2250738585072012e-308/ - private val BrokenDouble = BigDecimal("2.2250738585072012e-308") private[json] def parseDouble(s: String) = { - val d = BigDecimal(s) - if (d == BrokenDouble) sys.error("Error parsing 2.2250738585072012e-308") - else d.doubleValue + s.toDouble } private[this] case class IntermediateJObject(fields: scala.collection.mutable.ListBuffer[JField]) @@ -305,7 +300,7 @@ object JsonParser { buf.back (doubleVal: @switch) match { case true => - DoubleVal(BigDecimal(new String(value)).doubleValue) + DoubleVal(parseDouble(new String(value))) case false => IntVal(BigInt(new String(value))) } diff --git a/core/json/src/test/scala/net/liftweb/json/ParserBugs.scala b/core/json/src/test/scala/net/liftweb/json/ParserBugs.scala index 9370111c4c..e2389dfa05 100644 --- a/core/json/src/test/scala/net/liftweb/json/ParserBugs.scala +++ b/core/json/src/test/scala/net/liftweb/json/ParserBugs.scala @@ -26,11 +26,6 @@ object ParserBugs extends Specification { parseOpt(""" {"x":"\uffff"} """).isDefined mustEqual true } - "Does not hang when parsing 2.2250738585072012e-308" in { - (allCatch.opt(parse(""" [ 2.2250738585072012e-308 ] """)) mustEqual None) and - (allCatch.opt(parse(""" [ 22.250738585072012e-309 ] """)) mustEqual None) - } - "Does not allow colon at start of array (1039)" in { parseOpt("""[:"foo", "bar"]""") mustEqual None } From 7b88078a26fa61da298824498e953d89e2ae3a91 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 16 Nov 2016 09:52:47 -0500 Subject: [PATCH 1452/1949] Move segment size example to be run sequentially after others. The segment size spec changes the segment size to make sure that parsing still works. However, the segment size is now used when calculating the size of array to allocate when taking substrings of a buffer. So, while running an entire parse with the same segment size works fine, changing the segment size mid-parse does not. Because the segment size example ran concurrently with the other parsing examples, it could trigger failures in those examples and in itself. We now run it sequentially after all the others have finished, so that it can do its testing in isolation. The result... Works! --- .../scala/net/liftweb/json/JsonParserSpec.scala | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/core/json/src/test/scala/net/liftweb/json/JsonParserSpec.scala b/core/json/src/test/scala/net/liftweb/json/JsonParserSpec.scala index 4222db769d..28d3e58df7 100644 --- a/core/json/src/test/scala/net/liftweb/json/JsonParserSpec.scala +++ b/core/json/src/test/scala/net/liftweb/json/JsonParserSpec.scala @@ -42,12 +42,6 @@ object JsonParserSpec extends Specification with JValueGen with ScalaCheck { forAll(genJValue)(parsing) } - "Buffer size does not change parsing result" in { - val bufSize = Gen.choose(2, 64) - val parsing = (x: JValue, s1: Int, s2: Int) => { parseVal(x, s1) == parseVal(x, s2) } - forAll(genObject, bufSize, bufSize)(parsing) - } - "Parsing is thread safe" in { import java.util.concurrent._ @@ -92,6 +86,14 @@ object JsonParserSpec extends Specification with JValueGen with ScalaCheck { json mustEqual JArray(JString("hello") :: Nil) } + sequential + + "Segment size does not change parsing result" in { + val bufSize = Gen.choose(2, 64) + val parsing = (x: JValue, s1: Int, s2: Int) => { parseVal(x, s1) == parseVal(x, s2) } + forAll(genObject, bufSize, bufSize)(parsing) + } + implicit def arbJValue: Arbitrary[JValue] = Arbitrary(genObject) private def parseVal(json: JValue, bufSize: Int) = { From 575071150a6a32ba18fbf7978cce49414558c837 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 16 Nov 2016 10:05:58 -0500 Subject: [PATCH 1453/1949] Sprinkle in a bunch of comments. --- .../scala/net/liftweb/json/JsonParser.scala | 49 +++++++++++++------ 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala index b4f84893a7..26d78b7d0f 100644 --- a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala +++ b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala @@ -50,18 +50,18 @@ object JsonParser { * @param closeAutomatically true (default) if the Reader is automatically closed on EOF * @throws ParseException is thrown if parsing fails */ - def parse(s: Reader, closeAutomatically: Boolean = true): JValue = + def parse(s: Reader, closeAutomatically: Boolean = true): JValue = parse(new Buffer(s, closeAutomatically)) /** Return parsed JSON. */ - def parseOpt(s: String): Option[JValue] = + def parseOpt(s: String): Option[JValue] = try { parse(s).toOpt } catch { case e: Exception => None } /** Return parsed JSON. * @param closeAutomatically true (default) if the Reader is automatically closed on EOF */ - def parseOpt(s: Reader, closeAutomatically: Boolean = true): Option[JValue] = + def parseOpt(s: Reader, closeAutomatically: Boolean = true): Option[JValue] = try { parse(s, closeAutomatically).toOpt } catch { case e: Exception => None } /** Parse in pull parsing style. @@ -102,7 +102,7 @@ object JsonParser { chars } - private[json] def unquote(string: String): String = + private[json] def unquote(string: String): String = unquote(new JsonParser.Buffer(new java.io.StringReader(string), false)) private[this] def unquote(buf: JsonParser.Buffer): String = { @@ -166,6 +166,8 @@ object JsonParser { s.toDouble } + // Intermediate objects and arrays which can be grown mutably for performance. + // These are finalized into immutable JObject and JArray. private[this] case class IntermediateJObject(fields: scala.collection.mutable.ListBuffer[JField]) private[this] case class IntermediateJArray(bits: scala.collection.mutable.ListBuffer[JValue]) @@ -174,6 +176,9 @@ object JsonParser { var token: Token = null var root: Option[JValue] = None + // At the end of an object, if we're looking at an intermediate form of an + // object or array, gather up all their component parts and create the final + // object or array. def closeBlock(v: Any) { @inline def toJValue(x: Any) = x match { case json: JValue => json @@ -187,7 +192,7 @@ object JsonParser { vals.pop(classOf[JField]) val obj = vals.peek(classOf[IntermediateJObject]) obj.fields.append(JField(name, toJValue(v))) - case Some(o: IntermediateJObject) => + case Some(o: IntermediateJObject) => o.fields.append(vals.peek(classOf[JField])) case Some(a: IntermediateJArray) => a.bits.append(toJValue(v)) case Some(x) => p.fail("expected field, array or object but got " + x) @@ -258,6 +263,8 @@ object JsonParser { class Parser(buf: Buffer) { import java.util.ArrayDeque + // Maintains our current nesting context in the form of BlockMode, which + // indicates if each context is an array or object. private[this] val blocks = new ArrayDeque[BlockMode](32) private[this] var fieldNameMode = true @@ -266,7 +273,7 @@ object JsonParser { /** Parse next Token from stream. */ def nextToken: Token = { - def parseString: String = + def parseString: String = try { unquote(buf) } catch { @@ -279,6 +286,8 @@ object JsonParser { var doubleVal = false val buf = this.buf + // Back up and mark the buffer so that we can extract a substring after + // that contains the whole value. buf.back buf.mark while (wasInt) { @@ -291,13 +300,13 @@ object JsonParser { case _ => wasInt = false if (c != EOF) { - buf.back + buf.back // don't include the last character } } } - buf.forward + buf.forward // substring is exclusive to the last index val value = buf.substring() - buf.back + buf.back // back up so our current pointer is in the right place (doubleVal: @switch) match { case true => DoubleVal(parseDouble(new String(value))) @@ -377,9 +386,12 @@ object JsonParser { * Buffer is divided to one or more segments (preallocated in Segments pool). */ private[json] final class Buffer(in: Reader, closeAutomatically: Boolean) { - final val builder = new java.lang.StringBuilder(32) + // Reused by the parser when appropriate, allows for a single builder to be + // used throughout the parse process, and to be written to directly from the + // substring method, so as to avoid allocating new builders when avoidable. + private[json] final val builder = new java.lang.StringBuilder(32) - var offset = 0 + var offset = 0 // how far into the current segment we've read data var curMark = -1 var curMarkSegment = -1 var eofIsFailure = false @@ -388,6 +400,8 @@ object JsonParser { private[this] var cur = 0 // Pointer which points current parsing location private[this] var curSegmentIdx = 0 // Pointer which points current segment + // Mark the current point so that future substring calls will extract the + // value from this point to whatever point the buffer has advanced to. final def mark = { if (curSegmentIdx > 0) { segments(0) = segments.remove(curSegmentIdx) @@ -400,6 +414,7 @@ object JsonParser { final def back = cur = cur-1 final def forward = cur = cur+1 + // Read the next character; reads new data from the reader if necessary. final def next: Char = { if (cur >= offset && read < 0) { if (eofIsFailure) throw new ParseException("unexpected eof", null) else EOF @@ -411,6 +426,9 @@ object JsonParser { } private[this] final val emptyArray = new Array[Char](0) + // Slices from the last marked point to the current index. If intoBuilder is + // true, appends it to the buffer's builder and returns an empty array. If + // false, slices it into a new array and returns that array. final def substring(intoBuilder: Boolean = false) = { if (curSegmentIdx == curMarkSegment) { val substringLength = cur - curMark - 1 @@ -467,10 +485,12 @@ object JsonParser { private[JsonParser] def automaticClose = if (closeAutomatically) in.close + // Reads the next available block from the reader. Returns -1 if there's + // nothing more to read. private[this] def read = { if (offset >= segment.length) { offset = 0 - val segmentToUse = + val segmentToUse = (curMarkSegment: @scala.annotation.switch) match { case -1 => curSegmentIdx = 0 @@ -498,7 +518,8 @@ object JsonParser { } } - /* A pool of preallocated char arrays. + /* + * A pool of preallocated char arrays. */ private[json] object Segments { import java.util.concurrent.ArrayBlockingQueue @@ -518,7 +539,7 @@ object JsonParser { private[this] def acquire: Segment = { val curCount = segmentCount.get - val createNew = + val createNew = if (segments.size == 0 && curCount < maxNumOfSegments) segmentCount.compareAndSet(curCount, curCount + 1) else false From 623096ec76fa5508576a7c2da0493cf061280b62 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 16 Nov 2016 14:44:21 -0500 Subject: [PATCH 1454/1949] Drop an @inline that didn't make a difference. --- core/json/src/main/scala/net/liftweb/json/JsonParser.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala index 26d78b7d0f..60e6d9de78 100644 --- a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala +++ b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala @@ -180,7 +180,7 @@ object JsonParser { // object or array, gather up all their component parts and create the final // object or array. def closeBlock(v: Any) { - @inline def toJValue(x: Any) = x match { + def toJValue(x: Any) = x match { case json: JValue => json case other: IntermediateJObject => JObject(other.fields.result) case other: IntermediateJArray => JArray(other.bits.result) From b57b21c90a05d34dd138beead7d585737f4a3b5f Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Fri, 18 Nov 2016 16:49:08 -0500 Subject: [PATCH 1455/1949] Clarify hex char extraction a bit. Few comments, and some magic numbers go away. --- .../src/main/scala/net/liftweb/json/JsonParser.scala | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala index 60e6d9de78..a4a65fcb3e 100644 --- a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala +++ b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala @@ -86,6 +86,10 @@ object JsonParser { } finally { buf.release } } + // JSON hex unicode strings (\u12AF) are translated into characters through + // this array. Each number in the array corresponds to the 4-bit value that + // one number in the hex string will represent. These are combined when + // reading the unicode string. private[this] final val HexChars: Array[Int] = { val chars = new Array[Int](128) var i = 0 @@ -101,6 +105,8 @@ object JsonParser { } chars } + // The size of one hex character in bits. + private[this] final val hexCharSize = 4 // in bits private[json] def unquote(string: String): String = unquote(new JsonParser.Buffer(new java.io.StringReader(string), false)) @@ -126,8 +132,8 @@ object JsonParser { var byte = 0 var finalChar = 0 val chars = Array(buf.next, buf.next, buf.next, buf.next) - while (byte < 4) { - finalChar = (finalChar << 4) | HexChars(chars(byte).toInt) + while (byte < chars.length) { + finalChar = (finalChar << hexCharSize) | HexChars(chars(byte).toInt) byte += 1 } builder.appendCodePoint(finalChar.toChar) From b55274e03316de3f2e4267bb10a79793a9b2e98e Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Fri, 18 Nov 2016 16:49:28 -0500 Subject: [PATCH 1456/1949] Eliminate some weird unnecessary style. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some changes that seemed performance-important but ultimately weren’t. --- .../main/scala/net/liftweb/json/JsonParser.scala | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala index a4a65fcb3e..47e0bb626f 100644 --- a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala +++ b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala @@ -162,9 +162,11 @@ object JsonParser { } } buf.eofIsFailure = false - forcedReturn match { - case null => new String(buf.substring()) - case _ => forcedReturn + + if (forcedReturn == null) { + new String(buf.substring()) + } else { + forcedReturn } } @@ -408,7 +410,7 @@ object JsonParser { // Mark the current point so that future substring calls will extract the // value from this point to whatever point the buffer has advanced to. - final def mark = { + def mark = { if (curSegmentIdx > 0) { segments(0) = segments.remove(curSegmentIdx) curSegmentIdx = 0 @@ -417,11 +419,11 @@ object JsonParser { curMark = cur curMarkSegment = curSegmentIdx } - final def back = cur = cur-1 - final def forward = cur = cur+1 + def back = cur = cur-1 + def forward = cur = cur+1 // Read the next character; reads new data from the reader if necessary. - final def next: Char = { + def next: Char = { if (cur >= offset && read < 0) { if (eofIsFailure) throw new ParseException("unexpected eof", null) else EOF } else { From d3cf6dc7267cc1ea670de0d121cf99cb83cee5e2 Mon Sep 17 00:00:00 2001 From: Piotr Dyraga Date: Mon, 28 Nov 2016 21:01:27 +0100 Subject: [PATCH 1457/1949] Added S.sessionFuture as an alias to LAFutureWithSession.withCurrentSession --- .../net/liftweb/http/LAFutureWithSession.scala | 4 ++-- .../src/main/scala/net/liftweb/http/S.scala | 15 ++++++++++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LAFutureWithSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LAFutureWithSession.scala index 132afd55cd..a044cf1d72 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LAFutureWithSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LAFutureWithSession.scala @@ -7,10 +7,10 @@ import net.liftweb.common.{EmptyBox, Failure, Full} object LAFutureWithSession { /** - * Creates `LAFuture` instance aware of current request and session. Each `LAFuture` returned by chained + * Creates `LAFuture` instance aware of the current request and session. Each `LAFuture` returned by chained * transformation method (e.g. `map`, `flatMap`) will be also request/session-aware. However, it's * important to bear in mind that initial session or request are not propagated to chained methods. It's required - * that current execution thread for chained method has its own request or session available if reading/writing + * that current execution thread for chained method has request or session available in scope if reading/writing * some data to it as a part of chained method execution. */ def withCurrentSession[T](task: => T, scheduler: LAScheduler = LAScheduler): LAFuture[T] = { diff --git a/web/webkit/src/main/scala/net/liftweb/http/S.scala b/web/webkit/src/main/scala/net/liftweb/http/S.scala index f867bc993d..3bb4617da6 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/S.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/S.scala @@ -17,13 +17,12 @@ package net.liftweb package http -import java.util.{Locale, TimeZone, ResourceBundle} +import java.util.{Locale, ResourceBundle, TimeZone} import scala.collection.mutable.{HashMap, ListBuffer} import xml._ - import common._ -import actor.LAFuture +import actor.{LAFuture, LAScheduler} import util._ import Helpers._ import js._ @@ -1404,6 +1403,16 @@ trait S extends HasParams with Loggable with UserAgentCalculator { */ def session: Box[LiftSession] = Box.legacyNullTest(_sessionInfo.value) + /** + * Creates `LAFuture` instance aware of the current request and session. Each `LAFuture` returned by chained + * transformation method (e.g. `map`, `flatMap`) will be also request/session-aware. However, it's + * important to bear in mind that initial session or request are not propagated to chained methods. It's required + * that current execution thread for chained method has request or session available in scope if reading/writing + * some data to it as a part of chained method execution. + */ + def sessionFuture[T](task: => T, scheduler: LAScheduler = LAScheduler): LAFuture[T] = + LAFutureWithSession.withCurrentSession(task, scheduler) + /** * Log a query for the given request. The query log can be tested to see * if queries for the particular page rendering took too long. The query log From 6bf9b1a516480d489478504aa826d59a646d065b Mon Sep 17 00:00:00 2001 From: Piotr Dyraga Date: Mon, 28 Nov 2016 21:08:13 +0100 Subject: [PATCH 1458/1949] Drop semicolons from specs Use standard scala formatting instead --- .../liftweb/http/FutureWithSessionSpec.scala | 30 +++++++++++++++---- .../http/LAFutureWithSessionSpec.scala | 20 ++++++++++--- 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/web/webkit/src/test/scala/net/liftweb/http/FutureWithSessionSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/FutureWithSessionSpec.scala index c26dac3681..a99a24ab94 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/FutureWithSessionSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/FutureWithSessionSpec.scala @@ -104,8 +104,14 @@ class FutureWithSessionSpec extends WebSpec with ThrownMessages { val future = FutureWithSession.withCurrentSession("d") val mapped = future - .flatMap { s => val out = s + SessionVar1.is; Future(out) } - .flatMap { s => val out = s + SessionVar2.is; Future(out) } + .flatMap { s => + val out = s + SessionVar1.is + Future(out) + } + .flatMap { s => + val out = s + SessionVar2.is + Future(out) + } mapped.value must eventually(beEqualTo(Some(Success("def")))) } @@ -116,8 +122,14 @@ class FutureWithSessionSpec extends WebSpec with ThrownMessages { val future = FutureWithSession.withCurrentSession("d") val mapped = future - .flatMap { s => val out = s + ReqVar1.is; Future(out) } - .flatMap { s => val out = s + ReqVar2.is; Future(out) } + .flatMap { s => + val out = s + ReqVar1.is + Future(out) + } + .flatMap { s => + val out = s + ReqVar2.is + Future(out) + } mapped.value must eventually(beEqualTo(Some(Success("def")))) } @@ -217,7 +229,10 @@ class FutureWithSessionSpec extends WebSpec with ThrownMessages { SessionVar2("j") val future = FutureWithSession.withCurrentSession(throw new Exception("failed")) - .recoverWith { case e: Exception => val out = e.getMessage + " " + SessionVar1.is; Future(out) } + .recoverWith { case e: Exception => + val out = e.getMessage + " " + SessionVar1.is + Future(out) + } .map(_ + SessionVar2.is) future.value must eventually(beEqualTo(Some(Success("failed ij")))) @@ -228,7 +243,10 @@ class FutureWithSessionSpec extends WebSpec with ThrownMessages { ReqVar2("l") val future = FutureWithSession.withCurrentSession(throw new Exception("failed")) - .recoverWith { case e: Exception => val out = e.getMessage + " " + ReqVar1.is; Future(out) } + .recoverWith { case e: Exception => + val out = e.getMessage + " " + ReqVar1.is + Future(out) + } .map(_ + ReqVar2.is) future.value must eventually(beEqualTo(Some(Success("failed kl")))) diff --git a/web/webkit/src/test/scala/net/liftweb/http/LAFutureWithSessionSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/LAFutureWithSessionSpec.scala index 874dae6437..48cf2daa3b 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/LAFutureWithSessionSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/LAFutureWithSessionSpec.scala @@ -235,8 +235,14 @@ class LAFutureWithSessionSpec extends WebSpec with ThrownMessages { val future = LAFutureWithSession.withCurrentSession("d") val mapped = future - .flatMap { s => val out = s + SessionVar1.is; LAFuture.build(out) } - .flatMap { s => val out = s + SessionVar2.is; LAFuture.build(out) } + .flatMap { s => + val out = s + SessionVar1.is + LAFuture.build(out) + } + .flatMap { s => + val out = s + SessionVar2.is + LAFuture.build(out) + } mapped.get(timeout) must_== Full("def") } @@ -247,8 +253,14 @@ class LAFutureWithSessionSpec extends WebSpec with ThrownMessages { val future = LAFutureWithSession.withCurrentSession("d") val mapped = future - .flatMap { s => val out = s + ReqVar1.is; LAFuture.build(out) } - .flatMap { s => val out = s + ReqVar2.is; LAFuture.build(out) } + .flatMap { s => + val out = s + ReqVar1.is + LAFuture.build(out) + } + .flatMap { s => + val out = s + ReqVar2.is + LAFuture.build(out) + } mapped.get(timeout) must_== Full("def") } From b0f2bf59f42771b09e0df235a8fe689170dfa439 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Tue, 6 Dec 2016 07:58:09 -0500 Subject: [PATCH 1459/1949] Bump Scala 2.12 dependency to 2.12.1. 2.12.1 fixes a compile issue in lift-actor with 2.12.0 that caused the compiler to crash. --- build.sbt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index 20dc715d4e..41e397d61c 100644 --- a/build.sbt +++ b/build.sbt @@ -12,9 +12,9 @@ startYear in ThisBuild := Some(2006) organizationName in ThisBuild := "WorldWide Conferencing, LLC" -scalaVersion in ThisBuild := "2.12.0" +scalaVersion in ThisBuild := "2.12.1" -crossScalaVersions in ThisBuild := Seq("2.12.0", "2.11.7") +crossScalaVersions in ThisBuild := Seq("2.12.1", "2.11.7") libraryDependencies in ThisBuild <++= scalaVersion {sv => Seq(specs2, specs2Matchers, specs2Mock, scalacheck, scalatest) } From 9283170c3fdc99fc45ea797f9f55a3c56e66cee5 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Tue, 6 Dec 2016 07:58:51 -0500 Subject: [PATCH 1460/1949] @xuwei-k published Squeryl 0.9.5-7 for 2.12; downgrade back to it. There were many breaking API changes in Squeryl 0.9.7, and rather than block the 2.12 publish on it, @xuwei-k was kind enough to publish a 2.12 build of the original dependency. We downgrade to it and will undertake a conversion to Squery 0.9.7 at a later time. --- project/Dependencies.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 86e327edc7..02a5b588e2 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -42,7 +42,7 @@ object Dependencies { lazy val scalap: ModuleMap = "org.scala-lang" % "scalap" % _ lazy val scala_compiler: ModuleMap = "org.scala-lang" % "scala-compiler" % _ lazy val scalaz7_core = "org.scalaz" % "scalaz-core" % "7.2.7" cross CVMappingAll - lazy val squeryl = "org.squeryl" % "squeryl" % "0.9.7" cross CVMappingAll + lazy val squeryl = "org.squeryl" % "squeryl" % "0.9.5-7" cross CVMappingAll lazy val slf4j_api = "org.slf4j" % "slf4j-api" % slf4jVersion lazy val scala_xml = "org.scala-lang.modules" %% "scala-xml" % "1.0.5" lazy val rhino = "org.mozilla" % "rhino" % "1.7.7.1" From ab9b8bc4e1eb0d2f9ee94eb0514e1faa74a178aa Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Tue, 6 Dec 2016 09:44:29 -0500 Subject: [PATCH 1461/1949] Standardize on sendCometMessage, provide typed variants. This also includes some minor fixes to documentation that probably aren't worth their own commit. --- .../scala/net/liftweb/http/LiftSession.scala | 52 ++++++++++++++++--- 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index 381069c18c..23908a9cd2 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -2326,13 +2326,13 @@ class LiftSession(private[http] val _contextPath: String, val underlyingId: Stri * the CometActor is instantiated. If the CometActor already exists * in the session, the message will be sent immediately. If the CometActor * is not yet instantiated, the message will be sent to the CometActor - * as part of setup (@see queueCometMessage) if it is created as part + * as part of setup (see queueCometMessage) if it is created as part * of the current HTTP request/response cycle. * * @param theType the type of the CometActor * @param msg the message to send to the CometActor */ - def sendCometActorMessage(theType: String, msg: Any) { + def sendCometMessage(theType: String, msg: Any): Unit = { testStatefulFeature { findComet(theType).foreach(_ ! msg) queueCometMessage(theType, msg) @@ -2344,7 +2344,23 @@ class LiftSession(private[http] val _contextPath: String, val underlyingId: Stri * the CometActor is instantiated. If the CometActor already exists * in the session, the message will be sent immediately. If the CometActor * is not yet instantiated, the message will be sent to the CometActor - * as part of setup (@see queueCometMessage) regardless of if it is created as part + * as part of setup (see queueCometMessage) if it is created as part + * of the current HTTP request/response cycle. + * + * @param msg the message to send to the CometActor + */ + def sendCometMessage[T](msg: Any)(implicit cometManifest: Manifest[T]): Unit = { + val castClass = cometManifest.runtimeClass.asInstanceOf[Class[T]] + val typeName = castClass.getSimpleName + sendCometMessage(typeName, msg) + } + + /** + * This method will send a message to a CometActor, whether or not + * the CometActor is instantiated. If the CometActor already exists + * in the session, the message will be sent immediately. If the CometActor + * is not yet instantiated, the message will be sent to the CometActor + * as part of setup (see queueCometMessage) regardless of if it is created as part * of the current HTTP request/response cycle. We plan to restrict this to * the current request/response cycle in the future (as that is the intended beavior). * @@ -2352,7 +2368,7 @@ class LiftSession(private[http] val _contextPath: String, val underlyingId: Stri * @param name the optional name of the CometActor * @param msg the message to send to the CometActor */ - def sendCometActorMessage(theType: String, name: Box[String], msg: Any) { + def sendCometMessage(theType: String, name: Box[String], msg: Any): Unit = { testStatefulFeature { findComet(theType, name) match { case Full(a) => a ! msg @@ -2362,7 +2378,29 @@ class LiftSession(private[http] val _contextPath: String, val underlyingId: Stri } /** - * Allows you to send messages to a CometActor that may or may not be set up yet + * This method will send a message to a CometActor, whether or not + * the CometActor is instantiated. If the CometActor already exists + * in the session, the message will be sent immediately. If the CometActor + * is not yet instantiated, the message will be sent to the CometActor + * as part of setup (see queueCometMessage) regardless of if it is created as part + * of the current HTTP request/response cycle. We plan to restrict this to + * the current request/response cycle in the future (as that is the intended beavior). + * + * @param name the optional name of the CometActor + * @param msg the message to send to the CometActor + */ + def sendCometMessage[T](name: Box[String], msg: Any)(implicit cometManifest: Manifest[T]): Unit = { + val castClass = cometManifest.runtimeClass.asInstanceOf[Class[T]] + val typeName = castClass.getSimpleName + sendCometMessage(typeName, name, msg) + } + + @deprecated("Please switch to using sendCometMessage.", "3.1") + def sendCometActorMessage(theType: String, name: Box[String], msg: Any): Unit = + sendCometMessage(theType, name, msg) + + /** + * Queue a message for a comet that is not started yet. */ def queueCometMessage(cometType: String, msg: Any) { testStatefulFeature { @@ -2371,7 +2409,7 @@ class LiftSession(private[http] val _contextPath: String, val underlyingId: Stri } /** - * Allows you to send messages to a CometActor that may or may not be set up yet + * Queue a message for a comet that is not started yet. */ def queueCometMessage(cometType: String, cometName: Box[String], msg: Any) { testStatefulFeature { @@ -2380,7 +2418,7 @@ class LiftSession(private[http] val _contextPath: String, val underlyingId: Stri } /** - * Allows you to send messages to a CometActor that may or may not be set up yet + * Queue a message for a comet that is not started yet. */ @deprecated("Please use queueCometMessage instead.", "3.1") def setupComet(cometType: String, cometName: Box[String], msg: Any) { From 16628de3bb5a5ba49c77cc44b9889112d7face15 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Tue, 6 Dec 2016 09:46:40 -0500 Subject: [PATCH 1462/1949] Swap out various usages of the old name. --- .../scala/net/liftweb/http/NamedCometActorSnippet.scala | 2 +- .../src/test/scala/net/liftweb/http/LiftSessionSpec.scala | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/NamedCometActorSnippet.scala b/web/webkit/src/main/scala/net/liftweb/http/NamedCometActorSnippet.scala index e43943c8dd..afdb99afba 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/NamedCometActorSnippet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/NamedCometActorSnippet.scala @@ -49,7 +49,7 @@ trait NamedCometActorSnippet { * to add the comet actor to the page */ final def render(xhtml: NodeSeq): NodeSeq = { - for (sess <- S.session) sess.sendCometActorMessage( + for (sess <- S.session) sess.sendCometMessage( cometClass, Full(name), CometName(name) ) {xhtml} diff --git a/web/webkit/src/test/scala/net/liftweb/http/LiftSessionSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/LiftSessionSpec.scala index 576c0ae32c..755ca6fbb8 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/LiftSessionSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/LiftSessionSpec.scala @@ -52,7 +52,7 @@ object LiftSessionSpec extends Specification with BeforeEach { val sendingMessages = 1 to 20 sendingMessages.foreach { message => - session.sendCometActorMessage(cometName, Full(cometName), message) + session.sendCometMessage(cometName, Full(cometName), message) } session.findOrCreateComet[TestCometActor](Full(cometName), NodeSeq.Empty, Map.empty).map { comet => @@ -71,11 +71,11 @@ object LiftSessionSpec extends Specification with BeforeEach { val cometName = "Comet1" // Spin up two comets: one with a name and one without - session.sendCometActorMessage(cometType, Full(cometName), NoOp) - session.sendCometActorMessage(cometType, Empty, NoOp) + session.sendCometMessage(cometType, Full(cometName), NoOp) + session.sendCometMessage(cometType, Empty, NoOp) // Send a message to both - session.sendCometActorMessage(cometType, 1) + session.sendCometMessage(cometType, 1) // Ensure both process the message session.findOrCreateComet[TestCometActor](Full(cometName), NodeSeq.Empty, Map.empty).map { comet => From 8b28650cbb7e7919209da9ef302707797aca97a3 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 7 Dec 2016 07:58:58 -0500 Subject: [PATCH 1463/1949] Group some imports properly, h/t codacy. --- .../test/scala/net/liftweb/markdown/BlockParsersTest.scala | 5 ++--- .../test/scala/net/liftweb/markdown/LineParsersTest.scala | 5 ++--- .../test/scala/net/liftweb/markdown/LineTokenizerTest.scala | 3 +-- .../test/scala/net/liftweb/markdown/TransformerTest.scala | 5 ++--- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/core/markdown/src/test/scala/net/liftweb/markdown/BlockParsersTest.scala b/core/markdown/src/test/scala/net/liftweb/markdown/BlockParsersTest.scala index 24769d5776..9e78b83354 100644 --- a/core/markdown/src/test/scala/net/liftweb/markdown/BlockParsersTest.scala +++ b/core/markdown/src/test/scala/net/liftweb/markdown/BlockParsersTest.scala @@ -21,8 +21,7 @@ package net.liftweb.markdown import org.junit.runner.RunWith import org.scalatest.junit.JUnitRunner -import org.scalatest.Matchers -import org.scalatest.FlatSpec +import org.scalatest.{Matchers,FlatSpec} import xml.{Group, NodeSeq} /** @@ -52,4 +51,4 @@ class BlockParsersTest extends FlatSpec with Matchers with BlockParsers{ apply(p, List(new CodeLine(" ", "code"))) should equal (new CodeLine(" ", "code")) an [IllegalArgumentException] should be thrownBy(apply(p, List(new OtherLine("foo")))) } -} \ No newline at end of file +} diff --git a/core/markdown/src/test/scala/net/liftweb/markdown/LineParsersTest.scala b/core/markdown/src/test/scala/net/liftweb/markdown/LineParsersTest.scala index 0edfb27d4c..4ab214944a 100644 --- a/core/markdown/src/test/scala/net/liftweb/markdown/LineParsersTest.scala +++ b/core/markdown/src/test/scala/net/liftweb/markdown/LineParsersTest.scala @@ -19,8 +19,7 @@ package net.liftweb.markdown * Christoph Henkelmann http://henkelmann.eu/ */ -import org.scalatest.Matchers -import org.scalatest.FlatSpec +import org.scalatest.{Matchers,FlatSpec} import org.junit.runner.RunWith import org.scalatest.junit.JUnitRunner @@ -154,4 +153,4 @@ class LineParsersTest extends FlatSpec with Matchers with LineParsers{ apply(p, " ``` \t java \t ") should equal ( new ExtendedFencedCode(" ``` \t ", "java \t ")) } -} \ No newline at end of file +} diff --git a/core/markdown/src/test/scala/net/liftweb/markdown/LineTokenizerTest.scala b/core/markdown/src/test/scala/net/liftweb/markdown/LineTokenizerTest.scala index 15f116d698..e22b667f21 100644 --- a/core/markdown/src/test/scala/net/liftweb/markdown/LineTokenizerTest.scala +++ b/core/markdown/src/test/scala/net/liftweb/markdown/LineTokenizerTest.scala @@ -19,8 +19,7 @@ package net.liftweb.markdown * Christoph Henkelmann http://henkelmann.eu/ */ -import org.scalatest.Matchers -import org.scalatest.FlatSpec +import org.scalatest.{Matchers,FlatSpec} import org.junit.runner.RunWith import org.scalatest.junit.JUnitRunner diff --git a/core/markdown/src/test/scala/net/liftweb/markdown/TransformerTest.scala b/core/markdown/src/test/scala/net/liftweb/markdown/TransformerTest.scala index 3ce7173d1b..3e7d29baf5 100644 --- a/core/markdown/src/test/scala/net/liftweb/markdown/TransformerTest.scala +++ b/core/markdown/src/test/scala/net/liftweb/markdown/TransformerTest.scala @@ -19,8 +19,7 @@ package net.liftweb.markdown * Christoph Henkelmann http://henkelmann.eu/ */ -import org.scalatest.Matchers -import org.scalatest.FlatSpec +import org.scalatest.{Matchers,FlatSpec} import org.junit.runner.RunWith import org.scalatest.junit.JUnitRunner @@ -353,4 +352,4 @@ And now to something completely different. """ ) } -} \ No newline at end of file +} From 6de40043fbeddb107467e6b0b5518786768cec79 Mon Sep 17 00:00:00 2001 From: Tim Nelson Date: Sat, 10 Dec 2016 08:03:38 -0600 Subject: [PATCH 1464/1949] Upgrade mongo-java-driver to v3.4.0 --- .../mongodb/record/field/MongoMapField.scala | 2 +- .../scala/net/liftweb/mongodb/Mongo.scala | 60 +------------------ .../scala/net/liftweb/mongodb/MongoMeta.scala | 32 +--------- .../src/test/resources/logging.properties | 2 +- .../mongodb/MongoDirectMongoClient.scala | 6 +- .../net/liftweb/mongodb/MongoDirectSpec.scala | 17 +++--- project/Dependencies.scala | 2 +- 7 files changed, 18 insertions(+), 103 deletions(-) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoMapField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoMapField.scala index c9102dc373..d441656234 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoMapField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoMapField.scala @@ -91,7 +91,7 @@ class MongoMapField[OwnerType <: BsonRecord[OwnerType], MapValueType](rec: Owner def asDBObject: DBObject = { val dbo = new BasicDBObject value.keys.foreach { k => - dbo.put(k.toString, value.getOrElse(k, "")) + value.get(k).foreach(v => dbo.put(k.toString, v.asInstanceOf[Object])) } dbo } diff --git a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/Mongo.scala b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/Mongo.scala index e55458bdf4..a2367a35bf 100644 --- a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/Mongo.scala +++ b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/Mongo.scala @@ -33,7 +33,7 @@ object MongoDB { /** * HashMap of Mongo instance and db name tuples, keyed by ConnectionIdentifier */ - private val dbs = new ConcurrentHashMap[ConnectionIdentifier, (Mongo, String)] + private val dbs = new ConcurrentHashMap[ConnectionIdentifier, (MongoClient, String)] /** * Define a MongoClient db using a MongoClient instance. @@ -42,17 +42,6 @@ object MongoDB { dbs.put(name, (mngo, dbName)) } - /** - * Define and authenticate a Mongo db using a MongoClient instance. - */ - @deprecated("Credentials are now passed in via MongoClient", "3.0") - def defineDbAuth(name: ConnectionIdentifier, mngo: MongoClient, dbName: String, username: String, password: String) { - if (!mngo.getDB(dbName).authenticate(username, password.toCharArray)) - throw new MongoException("Authorization failed: "+mngo.toString) - - dbs.put(name, (mngo, dbName)) - } - /** * Get a DB reference */ @@ -121,53 +110,6 @@ object MongoDB { f(coll) } - /** - * Executes function {@code f} with the mongo db named {@code name}. Uses the same socket - * for the entire function block. Allows multiple operations on the same thread/socket connection - * and the use of getLastError. - * See: http://docs.mongodb.org/ecosystem/drivers/java-concurrency/ - */ - @deprecated("No longer needed. See mongo-java-drivers's JavaDocs for details", "3.0") - def useSession[T](name: ConnectionIdentifier)(f: (DB) => T): T = { - - val db = getDb(name) match { - case Some(mongo) => mongo - case _ => throw new MongoException("Mongo not found: "+name.toString) - } - - // start the request - db.requestStart - try { - f(db) - } - finally { - // end the request - db.requestDone - } - } - - /** - * Same as above except uses DefaultConnectionIdentifier - */ - @deprecated("No longer needed. See mongo-java-drivers's JavaDocs for details", "3.0") - def useSession[T](f: (DB) => T): T = { - - val db = getDb(DefaultConnectionIdentifier) match { - case Some(mongo) => mongo - case _ => throw new MongoException("Mongo not found: "+DefaultConnectionIdentifier.toString) - } - - // start the request - db.requestStart - try { - f(db) - } - finally { - // end the request - db.requestDone - } - } - /** * Calls close on each MongoClient instance and clears the HashMap. */ diff --git a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/MongoMeta.scala b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/MongoMeta.scala index cd9c71ab14..023b864e55 100644 --- a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/MongoMeta.scala +++ b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/MongoMeta.scala @@ -117,42 +117,16 @@ trait MongoMeta[BaseDocument] extends JsonFormats { /* drop this document collection */ def drop: Unit = useColl { coll => coll.drop } - /* - * Ensure an index exists - */ - @deprecated("use createIndex(JObject) instead.", "2.6") - def ensureIndex(keys: JObject): Unit = - useColl { coll => coll.createIndex(JObjectParser.parse(keys)) } - - /* - * Ensure an index exists and make unique - */ - @deprecated("use createIndex(JObject, Boolean) instead.", "2.6") - def ensureIndex(keys: JObject, unique: Boolean): Unit = { - val options = new BasicDBObject - if (unique) options.put("unique", true) - useColl { coll => - coll.createIndex(JObjectParser.parse(keys), options) - } - } - def createIndex(keys: JObject, unique: Boolean = false): Unit = { val options = new BasicDBObject - if (unique) options.put("unique", true) + if (unique) { + options.put("unique", true: java.lang.Boolean) + } useColl { coll => coll.createIndex(JObjectParser.parse(keys), options) } } - /* - * Ensure an index exists with options - */ - @deprecated("use createIndex(JObject, JObject) instead.", "2.6") - def ensureIndex(keys: JObject, opts: JObject): Unit = - useColl { coll => - coll.createIndex(JObjectParser.parse(keys), JObjectParser.parse(opts)) - } - def createIndex(keys: JObject, opts: JObject): Unit = useColl { coll => coll.createIndex(JObjectParser.parse(keys), JObjectParser.parse(opts)) diff --git a/persistence/mongodb/src/test/resources/logging.properties b/persistence/mongodb/src/test/resources/logging.properties index 734570d69d..9e6ae16de7 100644 --- a/persistence/mongodb/src/test/resources/logging.properties +++ b/persistence/mongodb/src/test/resources/logging.properties @@ -13,4 +13,4 @@ java.util.logging.ConsoleHandler.level = INFO java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter # Set the default logging level for the named logger -com.mongodb.level = SEVERE +org.mongodb.driver.level = WARNING diff --git a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDirectMongoClient.scala b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDirectMongoClient.scala index 9397ac8491..483fcbae41 100644 --- a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDirectMongoClient.scala +++ b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDirectMongoClient.scala @@ -50,15 +50,15 @@ class MongoDirectMongoClientSpec extends Specification with MongoTestKit { doc.put("name", "MongoSession") doc.put("type", "db") - doc.put("count", 1) + doc.put("count", 1: java.lang.Integer) doc2.put("name", "MongoSession") doc2.put("type", "db") - doc2.put("count", 1) + doc2.put("count", 1: java.lang.Integer) doc3.put("name", "MongoDB") doc3.put("type", "db") - doc3.put("count", 1) + doc3.put("count", 1: java.lang.Integer) // save the docs to the db coll.save(doc) diff --git a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDirectSpec.scala b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDirectSpec.scala index 170545a1a9..1296fb3d25 100644 --- a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDirectSpec.scala +++ b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDirectSpec.scala @@ -47,12 +47,12 @@ class MongoDirectSpec extends Specification with MongoTestKit { doc.put("name", "MongoDB") doc.put("type", "database") - doc.put("count", 1) + doc.put("count", 1: java.lang.Integer) val info = new BasicDBObject - info.put("x", 203) - info.put("y", 102) + info.put("x", 203: java.lang.Integer) + info.put("y", 102: java.lang.Integer) doc.put("info", info) @@ -68,7 +68,7 @@ class MongoDirectSpec extends Specification with MongoTestKit { // upsert doc.put("type", "document") - doc.put("count", 2) + doc.put("count", 2: java.lang.Integer) val q = new BasicDBObject("name", "MongoDB") // the query to select the document(s) to update val o = doc // the new object to update with, replaces the entire document, except possibly _id val upsert = false // if the database should create the element if it does not exist @@ -128,8 +128,7 @@ class MongoDirectSpec extends Specification with MongoTestKit { cur.count must_== 100 // get a single document with a query ( i = 71 ) - val query = new BasicDBObject - query.put("i", 71) + val query = new BasicDBObject("i", 71) val cur2 = coll.find(query) cur2.count must_== 1 @@ -218,15 +217,15 @@ class MongoDirectSpec extends Specification with MongoTestKit { doc.put("name", "MongoSession") doc.put("type", "db") - doc.put("count", 1) + doc.put("count", 1: java.lang.Integer) doc2.put("name", "MongoSession") doc2.put("type", "db") - doc2.put("count", 1) + doc2.put("count", 1: java.lang.Integer) doc3.put("name", "MongoDB") doc3.put("type", "db") - doc3.put("count", 1) + doc3.put("count", 1: java.lang.Integer) // save the docs to the db Helpers.tryo(coll.save(doc, WriteConcern.SAFE)).toOption must beSome diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 1093334bc5..8d5fa09149 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -36,7 +36,7 @@ object Dependencies { lazy val joda_time = "joda-time" % "joda-time" % "2.9.2" lazy val joda_convert = "org.joda" % "joda-convert" % "1.8.1" lazy val htmlparser = "nu.validator.htmlparser" % "htmlparser" % "1.4" - lazy val mongo_java_driver = "org.mongodb" % "mongo-java-driver" % "2.14.0" + lazy val mongo_java_driver = "org.mongodb" % "mongo-java-driver" % "3.4.0" lazy val paranamer = "com.thoughtworks.paranamer" % "paranamer" % "2.8" lazy val scalajpa = "org.scala-libs" % "scalajpa" % "1.5" cross CVMappingAll lazy val scalap: ModuleMap = "org.scala-lang" % "scalap" % _ From fe154cc76093a604b2cfdf8bf09970f3223361b5 Mon Sep 17 00:00:00 2001 From: Tim Nelson Date: Sat, 10 Dec 2016 15:53:45 -0600 Subject: [PATCH 1465/1949] Use longer variable names in function --- .../net/liftweb/mongodb/record/field/MongoMapField.scala | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoMapField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoMapField.scala index d441656234..7497314fbb 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoMapField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoMapField.scala @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 WorldWide Conferencing, LLC + * Copyright 2010-2016 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. @@ -90,8 +90,10 @@ class MongoMapField[OwnerType <: BsonRecord[OwnerType], MapValueType](rec: Owner */ def asDBObject: DBObject = { val dbo = new BasicDBObject - value.keys.foreach { k => - value.get(k).foreach(v => dbo.put(k.toString, v.asInstanceOf[Object])) + value.keys.foreach { key => + value.get(key).foreach { innerValue => + dbo.put(key.toString, innerValue.asInstanceOf[Object]) + } } dbo } From 16ea3041867c4bf6e232e44433e1c886ed03e699 Mon Sep 17 00:00:00 2001 From: Piotr Dyraga Date: Sat, 10 Dec 2016 23:44:32 +0100 Subject: [PATCH 1466/1949] Break up case statements to have results in a next line --- .../src/main/scala/net/liftweb/actor/LAFuture.scala | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala b/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala index 2d0ae40895..a176345406 100644 --- a/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala +++ b/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala @@ -113,10 +113,13 @@ class LAFuture[T](val scheduler: LAScheduler = LAScheduler, context: Box[LAFutur onComplete(v => v match { case Full(v) => Box.tryo(contextFn(v)) match { - case Full(successfullyComputedFuture) => successfullyComputedFuture.onComplete(v2 => result.complete(v2)) - case e: EmptyBox => result.complete(e) + case Full(successfullyComputedFuture) => + successfullyComputedFuture.onComplete(v2 => result.complete(v2)) + case e: EmptyBox => + result.complete(e) } - case e: EmptyBox => result.complete(e) + case e: EmptyBox => + result.complete(e) }) result } From 7eba92265659b95d023b96f8accf98b5c458e279 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Fri, 23 Dec 2016 14:51:22 -0500 Subject: [PATCH 1467/1949] Fix cross-compile issues on master. Scala 2.12 added a few methods to the Future interface. After trying a number of clever work-arounds using macros, the best way to smooth the gap that I could find was to throw an UnsupportedOperationException if either of these new methods are invoked. This is the "least bad" solution I could come up with given the reality of the situation. I would be very greatful for any other options as throwing these exceptions is kind of lame. --- .../net/liftweb/http/FutureWithSession.scala | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/web/webkit/src/main/scala/net/liftweb/http/FutureWithSession.scala b/web/webkit/src/main/scala/net/liftweb/http/FutureWithSession.scala index dfa9e6324a..2aa9ccf006 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/FutureWithSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/FutureWithSession.scala @@ -85,6 +85,24 @@ private[http] class FutureWithSession[T](private[this] val delegate: Future[T]) new FutureWithSession(delegate.transform(s => sessionSuccessFn(s), f => sessionFailureFn(f))) } + /** + * This operation has to exist for Lift to compile for 2.12, but doesn't exist in 2.11 so it will + * throw an exception. + */ + @scala.throws(classOf[UnsupportedOperationException]) + def transform[S](f: Try[T] => Try[S])(implicit executor: ExecutionContext): Future[S] = { + throw new UnsupportedOperationException("This transform method isn't supported since it doesn't exist in Scala 2.11.") + } + + /** + * This operation has to exist for Lift to compile for 2.12, but doesn't exist in 2.11 so it will + * throw an exception. + */ + @scala.throws(classOf[UnsupportedOperationException]) + def transformWith[S](f: Try[T] => Future[S])(implicit executor: ExecutionContext): Future[S] = { + throw new UnsupportedOperationException("The trhansformWith method isn't supported since it doesn't existin in Scala 2.11.") + } + override def zip[U](that: Future[U]): Future[(T, U)] = { new FutureWithSession(delegate.zip(that)) } From 660d38a858545dd0c01e9fbd27893498e052a5f1 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Fri, 23 Dec 2016 15:18:37 -0500 Subject: [PATCH 1468/1949] Add caching of the ivy folder to Travis instructions. --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 75d37a00f9..64788ea643 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,8 +2,11 @@ language: scala cache: directories: - '$HOME/node_modules' + - $HOME/.ivy2 + services: - mongodb + jdk: - oraclejdk8 From 339674a52508f9c4a6670631d12840839d1c6ca7 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Fri, 23 Dec 2016 15:20:11 -0500 Subject: [PATCH 1469/1949] Add proper support for multiple scala versions. By removing the support for the lift_26 branch we can use Travis's build matrix support for cross compiling. This would permit, for example, us to continue publishing snapshots for 2.11.8 if the 2.12.1 build is failing. This also will catch some more edge cases with tests. Previously tests were only running under 2.12. Now, tests for 2.11 and 2.12 will have to pass to get their respective artifact snapshots to publish. --- .travis.yml | 5 +++++ travis.sh | 10 ++-------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 64788ea643..4a3e060a82 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,9 @@ language: scala + +scala: + - 2.11.8 + - 2.12.1 + cache: directories: - '$HOME/node_modules' diff --git a/travis.sh b/travis.sh index d7def40ef1..3197ad20f2 100755 --- a/travis.sh +++ b/travis.sh @@ -2,7 +2,7 @@ set -ev -./liftsh test +./liftsh ++$TRAVIS_SCALA_VERSION test ./npmsh @@ -10,13 +10,7 @@ if [ "${TRAVIS_PULL_REQUEST}" = "false" ]; then mkdir -p ~/.sbt/0.13/ openssl aes-256-cbc -K $encrypted_a177bbd76133_key -iv $encrypted_a177bbd76133_iv -in .credentials.enc -out ~/.sbt/0.13/.credentials -d - if [ "${TRAVIS_BRANCH}" = "master" ]; then - ./liftsh publish - ./liftsh ++2.11.7 publish - elif [ "${TRAVIS_BRANCH}" = "lift_26" ]; then - ./liftsh ++2.10.4 "project lift-framework-pre-111" publish - ./liftsh ++2.11.1 publish - fi + ./liftsh ++$TRAVIS_SCALA_VERSION publish rm ~/.sbt/0.13/.credentials fi From e2552c6676c294b871a9158fffdea1fd91d28a2f Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Fri, 23 Dec 2016 15:25:46 -0500 Subject: [PATCH 1470/1949] Use Travis's built-in sbt. Using liftsh will cause us to waste time downloading and setting up our own sbt when Travis already has one in the image they're executing. --- travis.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/travis.sh b/travis.sh index 3197ad20f2..ae3411828f 100755 --- a/travis.sh +++ b/travis.sh @@ -2,7 +2,7 @@ set -ev -./liftsh ++$TRAVIS_SCALA_VERSION test +sbt ++$TRAVIS_SCALA_VERSION test ./npmsh @@ -10,7 +10,7 @@ if [ "${TRAVIS_PULL_REQUEST}" = "false" ]; then mkdir -p ~/.sbt/0.13/ openssl aes-256-cbc -K $encrypted_a177bbd76133_key -iv $encrypted_a177bbd76133_iv -in .credentials.enc -out ~/.sbt/0.13/.credentials -d - ./liftsh ++$TRAVIS_SCALA_VERSION publish + sbt ++$TRAVIS_SCALA_VERSION publish rm ~/.sbt/0.13/.credentials fi From c3b51ccfe21fdc1eb1377c6fedb5feeeb3af974c Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Fri, 23 Dec 2016 15:41:58 -0500 Subject: [PATCH 1471/1949] Implement the workaround for scala/scala-dev#249 --- travis.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/travis.sh b/travis.sh index ae3411828f..b7c3ac06c5 100755 --- a/travis.sh +++ b/travis.sh @@ -10,7 +10,9 @@ if [ "${TRAVIS_PULL_REQUEST}" = "false" ]; then mkdir -p ~/.sbt/0.13/ openssl aes-256-cbc -K $encrypted_a177bbd76133_key -iv $encrypted_a177bbd76133_iv -in .credentials.enc -out ~/.sbt/0.13/.credentials -d - sbt ++$TRAVIS_SCALA_VERSION publish + # Include the no-java-comments work-around so that publishing for Scala 2.12 will work correctly. + # See: https://github.com/scala/scala-dev/issues/249 + sbt ++$TRAVIS_SCALA_VERSION "set scalacOptions in (Compile, doc) += \"-no-java-comments\"" publish rm ~/.sbt/0.13/.credentials fi From 44ad57dcd0438a1a2f393471da3ee62cf5a6d029 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 24 Dec 2016 17:21:44 -0500 Subject: [PATCH 1472/1949] Remove the workaround that didn't work well --- travis.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/travis.sh b/travis.sh index b7c3ac06c5..ae3411828f 100755 --- a/travis.sh +++ b/travis.sh @@ -10,9 +10,7 @@ if [ "${TRAVIS_PULL_REQUEST}" = "false" ]; then mkdir -p ~/.sbt/0.13/ openssl aes-256-cbc -K $encrypted_a177bbd76133_key -iv $encrypted_a177bbd76133_iv -in .credentials.enc -out ~/.sbt/0.13/.credentials -d - # Include the no-java-comments work-around so that publishing for Scala 2.12 will work correctly. - # See: https://github.com/scala/scala-dev/issues/249 - sbt ++$TRAVIS_SCALA_VERSION "set scalacOptions in (Compile, doc) += \"-no-java-comments\"" publish + sbt ++$TRAVIS_SCALA_VERSION publish rm ~/.sbt/0.13/.credentials fi From 89834a69c3da4d934d936f9854d5bd8bb02f9914 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 24 Dec 2016 17:29:19 -0500 Subject: [PATCH 1473/1949] Actual work-around for scala/scala-dev#249. --- project/Build.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/project/Build.scala b/project/Build.scala index 6bbeb2e458..96780284d9 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -56,6 +56,7 @@ object BuildDef extends Build { lazy val framework = liftProject("lift-framework", file(".")) .aggregate(liftProjects: _*) + .settings(scalacOptions in (Compile, doc) += "-no-java-comments") //workaround for scala/scala-dev#249 .settings(aggregatedSetting(sources in(Compile, doc)), aggregatedSetting(dependencyClasspath in(Compile, doc)), publishArtifact := false) @@ -74,6 +75,7 @@ object BuildDef extends Build { lazy val actor = coreProject("actor") .dependsOn(common) + .settings(scalacOptions in (Compile, doc) += "-no-java-comments") //workaround for scala/scala-dev#249 .settings(description := "Simple Actor", parallelExecution in Test := false) @@ -129,6 +131,7 @@ object BuildDef extends Build { lazy val webkit = webProject("webkit") .dependsOn(util, testkit % "provided") + .settings(scalacOptions in (Compile, doc) += "-no-java-comments") //workaround for scala/scala-dev#249 .settings(libraryDependencies ++= Seq(mockito_all, jquery, jasmineCore, jasmineAjax)) .settings(yuiCompressor.Plugin.yuiSettings: _*) .settings(description := "Webkit Library", From 8c57d8ac8c2b104afb3fcd32145be702788fdd7f Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Tue, 27 Dec 2016 20:16:32 -0500 Subject: [PATCH 1474/1949] Change how we test segment size changes. We were testing segment size changes by using mutable state on a global Segments singleton. With the new parsing approach, which aggressively uses mutability to increase performance, this was causing derivative concurrency failures in many places. We now change the way to test different segment sizes by putting an interface in front of the segment pool and allowing it to be customized on a per-Buffer basis, and making the spec instantiate the Buffer with a different segment pool while testing. This functionality is currently still entirely private in the json package, but can be exposed in the future if we deem it useful. For now, its sole purpose is to confirm that buffer size can be changed without adverse effects. --- .../scala/net/liftweb/json/JsonParser.scala | 33 +++++++++++-------- .../net/liftweb/json/JsonParserSpec.scala | 21 +++++------- 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala index 47e0bb626f..2a4be9a06a 100644 --- a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala +++ b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala @@ -77,7 +77,7 @@ object JsonParser { */ def parse[A](s: Reader, p: Parser => A): A = p(new Parser(new Buffer(s, false))) - private def parse(buf: Buffer): JValue = { + private[json] def parse(buf: Buffer): JValue = { try { astParser(new Parser(buf)) } catch { @@ -391,9 +391,9 @@ object JsonParser { } /* Buffer used to parse JSON. - * Buffer is divided to one or more segments (preallocated in Segments pool). + * Buffer is divided to one or more segments (preallocated in segmentPool). */ - private[json] final class Buffer(in: Reader, closeAutomatically: Boolean) { + private[json] final class Buffer(in: Reader, closeAutomatically: Boolean, segmentPool: SegmentPool = Segments) { // Reused by the parser when appropriate, allows for a single builder to be // used throughout the parse process, and to be written to directly from the // substring method, so as to avoid allocating new builders when avoidable. @@ -403,7 +403,7 @@ object JsonParser { var curMark = -1 var curMarkSegment = -1 var eofIsFailure = false - private[this] var segments = scala.collection.mutable.ArrayBuffer(Segments.apply()) + private[this] var segments = scala.collection.mutable.ArrayBuffer(segmentPool.apply()) private[this] var segment: Array[Char] = segments.head.seg private[this] var cur = 0 // Pointer which points current parsing location private[this] var curSegmentIdx = 0 // Pointer which points current segment @@ -452,7 +452,7 @@ object JsonParser { } } else { // slower path for case when string is in two or more segments val segmentCount = curSegmentIdx - curMarkSegment + 1 - val substringLength = segmentCount * Segments.segmentSize - curMark - (Segments.segmentSize - cur) - 1 + val substringLength = segmentCount * segmentPool.segmentSize - curMark - (segmentPool.segmentSize - cur) - 1 val chars = if (intoBuilder) { emptyArray @@ -485,11 +485,11 @@ object JsonParser { def near = { val start = (cur - 20) max 0 - val len = ((cur + 1) min Segments.segmentSize) - start + val len = ((cur + 1) min segmentPool.segmentSize) - start new String(segment, start, len) } - def release = segments.foreach(Segments.release) + def release = segments.foreach(segmentPool.release) private[JsonParser] def automaticClose = if (closeAutomatically) in.close @@ -508,7 +508,7 @@ object JsonParser { if (curSegmentIdx < segments.length) { segments(curSegmentIdx) } else { - val segment = Segments.apply() + val segment = segmentPool.apply() segments.append(segment) segment } @@ -526,14 +526,16 @@ object JsonParser { } } - /* - * A pool of preallocated char arrays. - */ - private[json] object Segments { + private[json] trait SegmentPool { + def apply(): Segment + def release(segment: Segment): Unit + def segmentSize: Int + } + + private[json] class ArrayBlockingSegmentPool(override val segmentSize: Int) extends SegmentPool { import java.util.concurrent.ArrayBlockingQueue import java.util.concurrent.atomic.AtomicInteger - private[json] var segmentSize = 1000 private[this] val maxNumOfSegments = 10000 private[this] var segmentCount = new AtomicInteger(0) private[this] val segments = new ArrayBlockingQueue[Segment](maxNumOfSegments) @@ -561,6 +563,11 @@ object JsonParser { } } + /* + * A pool of preallocated char arrays. + */ + private object Segments extends ArrayBlockingSegmentPool(1000) + sealed trait Segment { val seg: Array[Char] } diff --git a/core/json/src/test/scala/net/liftweb/json/JsonParserSpec.scala b/core/json/src/test/scala/net/liftweb/json/JsonParserSpec.scala index 28d3e58df7..aaff95208f 100644 --- a/core/json/src/test/scala/net/liftweb/json/JsonParserSpec.scala +++ b/core/json/src/test/scala/net/liftweb/json/JsonParserSpec.scala @@ -17,12 +17,13 @@ package net.liftweb package json +import java.io.StringReader + import org.specs2.mutable.Specification import org.specs2.ScalaCheck import org.scalacheck.{Arbitrary, Gen} import org.scalacheck.Prop._ - /** * System under specification for JSON Parser. */ @@ -86,8 +87,6 @@ object JsonParserSpec extends Specification with JValueGen with ScalaCheck { json mustEqual JArray(JString("hello") :: Nil) } - sequential - "Segment size does not change parsing result" in { val bufSize = Gen.choose(2, 64) val parsing = (x: JValue, s1: Int, s2: Int) => { parseVal(x, s1) == parseVal(x, s2) } @@ -97,13 +96,11 @@ object JsonParserSpec extends Specification with JValueGen with ScalaCheck { implicit def arbJValue: Arbitrary[JValue] = Arbitrary(genObject) private def parseVal(json: JValue, bufSize: Int) = { - val existingSize = JsonParser.Segments.segmentSize - try { - JsonParser.Segments.segmentSize = bufSize - JsonParser.Segments.clear - JsonParser.parse(compactRender(json)) - } finally { - JsonParser.Segments.segmentSize = existingSize - } + val segmentPool = new JsonParser.ArrayBlockingSegmentPool(bufSize) + JsonParser.parse(new JsonParser.Buffer( + new StringReader(compactRender(json)), + false, + segmentPool + )) } -} +} \ No newline at end of file From 93b262ef2e7b4b3ed79ad7a8a67a435ba359bb2d Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 7 Jan 2017 10:28:34 -0500 Subject: [PATCH 1475/1949] Email notifications on build jobs go to committers list --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index 4a3e060a82..dcdeb8e6e7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,3 +36,8 @@ notifications: on_success: always on_failure: always on_start: never + email: + recipients: + - lift-committers@googlegroups.com + on_success: change + on_failure: always From f1dfc73154b155cfc5a051d8a5ba03304e3be68c Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 7 Jan 2017 10:45:02 -0500 Subject: [PATCH 1476/1949] Fix cross compiling between 2.11 and 2.12. --- project/Build.scala | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index 96780284d9..2c860c3b5b 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -56,7 +56,12 @@ object BuildDef extends Build { lazy val framework = liftProject("lift-framework", file(".")) .aggregate(liftProjects: _*) - .settings(scalacOptions in (Compile, doc) += "-no-java-comments") //workaround for scala/scala-dev#249 + .settings(scalacOptions in (Compile, doc) ++= Seq(scalaVersion.value).flatMap { + case v if v.startsWith("2.12") => + Seq("-no-java-comments") + case _ => + Seq() + }) //workaround for scala/scala-dev#249 .settings(aggregatedSetting(sources in(Compile, doc)), aggregatedSetting(dependencyClasspath in(Compile, doc)), publishArtifact := false) @@ -75,7 +80,12 @@ object BuildDef extends Build { lazy val actor = coreProject("actor") .dependsOn(common) - .settings(scalacOptions in (Compile, doc) += "-no-java-comments") //workaround for scala/scala-dev#249 + .settings(scalacOptions in (Compile, doc) ++= Seq(scalaVersion.value).flatMap { + case v if v.startsWith("2.12") => + Seq("-no-java-comments") + case _ => + Seq() + }) //workaround for scala/scala-dev#249 .settings(description := "Simple Actor", parallelExecution in Test := false) @@ -131,7 +141,12 @@ object BuildDef extends Build { lazy val webkit = webProject("webkit") .dependsOn(util, testkit % "provided") - .settings(scalacOptions in (Compile, doc) += "-no-java-comments") //workaround for scala/scala-dev#249 + .settings(scalacOptions in (Compile, doc) ++= Seq(scalaVersion.value).flatMap { + case v if v.startsWith("2.12") => + Seq("-no-java-comments") + case _ => + Seq() + }) //workaround for scala/scala-dev#249 .settings(libraryDependencies ++= Seq(mockito_all, jquery, jasmineCore, jasmineAjax)) .settings(yuiCompressor.Plugin.yuiSettings: _*) .settings(description := "Webkit Library", From 4c9ec9cc7a61ba76ff5853394020250e48a51ec2 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 7 Jan 2017 10:53:22 -0500 Subject: [PATCH 1477/1949] Minor scaladoc changes that were requested --- .../src/main/scala/net/liftweb/http/LiftSession.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index a85b2c42a5..825fc7ae67 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -2327,7 +2327,7 @@ class LiftSession(private[http] val _contextPath: String, val underlyingId: Stri * the CometActor is instantiated. If the CometActor already exists * in the session, the message will be sent immediately. If the CometActor * is not yet instantiated, the message will be sent to the CometActor - * as part of setup (see queueCometMessage) if it is created as part + * as part of setup `[[queueCometMessage]]` if it is created as part * of the current HTTP request/response cycle. * * @param theType the type of the CometActor @@ -2345,7 +2345,7 @@ class LiftSession(private[http] val _contextPath: String, val underlyingId: Stri * the CometActor is instantiated. If the CometActor already exists * in the session, the message will be sent immediately. If the CometActor * is not yet instantiated, the message will be sent to the CometActor - * as part of setup (see queueCometMessage) if it is created as part + * as part of setup `[[queueCometMessage]]` if it is created as part * of the current HTTP request/response cycle. * * @param msg the message to send to the CometActor @@ -2361,7 +2361,7 @@ class LiftSession(private[http] val _contextPath: String, val underlyingId: Stri * the CometActor is instantiated. If the CometActor already exists * in the session, the message will be sent immediately. If the CometActor * is not yet instantiated, the message will be sent to the CometActor - * as part of setup (see queueCometMessage) regardless of if it is created as part + * as part of setup `[[queueCometMessage]]` regardless of if it is created as part * of the current HTTP request/response cycle. We plan to restrict this to * the current request/response cycle in the future (as that is the intended beavior). * @@ -2383,7 +2383,7 @@ class LiftSession(private[http] val _contextPath: String, val underlyingId: Stri * the CometActor is instantiated. If the CometActor already exists * in the session, the message will be sent immediately. If the CometActor * is not yet instantiated, the message will be sent to the CometActor - * as part of setup (see queueCometMessage) regardless of if it is created as part + * as part of setup `[[queueCometMessage]]` regardless of if it is created as part * of the current HTTP request/response cycle. We plan to restrict this to * the current request/response cycle in the future (as that is the intended beavior). * From 8885cbf755d62fc39841e062859d1924d85d9639 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 7 Jan 2017 11:09:49 -0500 Subject: [PATCH 1478/1949] Make sendCometMessage overloads reference the original in scaladoc --- .../scala/net/liftweb/http/LiftSession.scala | 28 ++++++------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index 825fc7ae67..87ce72e020 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -2341,12 +2341,8 @@ class LiftSession(private[http] val _contextPath: String, val underlyingId: Stri } /** - * This method will send a message to a CometActor, whether or not - * the CometActor is instantiated. If the CometActor already exists - * in the session, the message will be sent immediately. If the CometActor - * is not yet instantiated, the message will be sent to the CometActor - * as part of setup `[[queueCometMessage]]` if it is created as part - * of the current HTTP request/response cycle. + * Similar behavior to [[LiftSession#sendCometMessage(theType:String,msg:Any):Unit the main sendCometMessage]], + * except that the type argument is taken as a type parameter instead of a string. * * @param msg the message to send to the CometActor */ @@ -2357,13 +2353,9 @@ class LiftSession(private[http] val _contextPath: String, val underlyingId: Stri } /** - * This method will send a message to a CometActor, whether or not - * the CometActor is instantiated. If the CometActor already exists - * in the session, the message will be sent immediately. If the CometActor - * is not yet instantiated, the message will be sent to the CometActor - * as part of setup `[[queueCometMessage]]` regardless of if it is created as part - * of the current HTTP request/response cycle. We plan to restrict this to - * the current request/response cycle in the future (as that is the intended beavior). + * Similar behavior to [[LiftSession#sendCometMessage(theType:String,msg:Any):Unit the main sendCometMessage]], + * except that this version will limit based on the name of the comet. Providing `name` as `Empty`, + * will specifically select comets with no name. * * @param theType the type of the CometActor * @param name the optional name of the CometActor @@ -2379,13 +2371,9 @@ class LiftSession(private[http] val _contextPath: String, val underlyingId: Stri } /** - * This method will send a message to a CometActor, whether or not - * the CometActor is instantiated. If the CometActor already exists - * in the session, the message will be sent immediately. If the CometActor - * is not yet instantiated, the message will be sent to the CometActor - * as part of setup `[[queueCometMessage]]` regardless of if it is created as part - * of the current HTTP request/response cycle. We plan to restrict this to - * the current request/response cycle in the future (as that is the intended beavior). + * Similar behavior to [[LiftSession#sendCometMessage(theType:String,msg:Any):Unit the main sendCometMessage]], + * except that this version will limit based on the name of the comet and it takes its type selector + * as a type parameter. * * @param name the optional name of the CometActor * @param msg the message to send to the CometActor From 1dd31f0459c472ace5b33e365ab2d2404c4efe2d Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 11 Jan 2017 15:21:16 -0500 Subject: [PATCH 1479/1949] Split FutureWithSession into 2.11 and 2.12 versions. This adds proper support for transform(Try) and transformWith to 2.12, while leaving 2.11 without unsupported operation exceptions. Also split the specs so that we can test the two new 2.12 methods. --- project/Build.scala | 15 +- .../net/liftweb/http/FutureWithSession.scala | 128 ++++++ .../net/liftweb/http/FutureWithSession.scala | 16 +- .../liftweb/http/FutureWithSessionSpec.scala | 0 .../liftweb/http/FutureWithSessionSpec.scala | 391 ++++++++++++++++++ 5 files changed, 535 insertions(+), 15 deletions(-) create mode 100644 web/webkit/src/main/scala_2.11/net/liftweb/http/FutureWithSession.scala rename web/webkit/src/main/{scala => scala_2.12}/net/liftweb/http/FutureWithSession.scala (89%) rename web/webkit/src/test/{scala => scala_2.11}/net/liftweb/http/FutureWithSessionSpec.scala (100%) create mode 100644 web/webkit/src/test/scala_2.12/net/liftweb/http/FutureWithSessionSpec.scala diff --git a/project/Build.scala b/project/Build.scala index 2c860c3b5b..5d7f1941e3 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -141,11 +141,12 @@ object BuildDef extends Build { lazy val webkit = webProject("webkit") .dependsOn(util, testkit % "provided") - .settings(scalacOptions in (Compile, doc) ++= Seq(scalaVersion.value).flatMap { - case v if v.startsWith("2.12") => + .settings(scalacOptions in (Compile, doc) ++= { + if (scalaVersion.value.startsWith("2.12")) { Seq("-no-java-comments") - case _ => + } else { Seq() + } }) //workaround for scala/scala-dev#249 .settings(libraryDependencies ++= Seq(mockito_all, jquery, jasmineCore, jasmineAjax)) .settings(yuiCompressor.Plugin.yuiSettings: _*) @@ -158,6 +159,14 @@ object BuildDef extends Build { initialize in Test <<= (sourceDirectory in Test) { src => System.setProperty("net.liftweb.webapptest.src.test.webapp", (src / "webapp").absString) }, + unmanagedSourceDirectories in Compile <+= (sourceDirectory in Compile, scalaBinaryVersion) { + (sourceDirectory, binaryVersion) => + sourceDirectory / ("scala_" + binaryVersion) + }, + unmanagedSourceDirectories in Test <+= (sourceDirectory in Test, scalaBinaryVersion) { + (sourceDirectory, binaryVersion) => + sourceDirectory / ("scala_" + binaryVersion) + }, (compile in Compile) <<= (compile in Compile) dependsOn (WebKeys.assets), /** * This is to ensure that the tests in net.liftweb.webapptest run last diff --git a/web/webkit/src/main/scala_2.11/net/liftweb/http/FutureWithSession.scala b/web/webkit/src/main/scala_2.11/net/liftweb/http/FutureWithSession.scala new file mode 100644 index 0000000000..dfa9e6324a --- /dev/null +++ b/web/webkit/src/main/scala_2.11/net/liftweb/http/FutureWithSession.scala @@ -0,0 +1,128 @@ +package net.liftweb.http + +import net.liftweb.common.Full + +import scala.concurrent.duration.Duration +import scala.concurrent.{CanAwait, ExecutionContext, Future} +import scala.util.Try + +/** + * Decorates `Future` instance to allow access to session and request resources. Should be created with + * `FutureWithSession.withCurrentSession` that takes the current Lift request and session and make it available + * to all transformation methods and initial `Future` execution body. Each transformation method returns + * `FutureWithSession`, thus, they can be all chained together. + * + * It's important to bear in mind that each chained method requires current thread's `LiftSession` to be available. + * `FutureWithSession` does _not_ propagate initial session or request to all chained methods. + * + * @see FutureWithSession.withCurrentSession + * + * @param delegate original `Future` instance that will be enriched with session and request access + */ +private[http] class FutureWithSession[T](private[this] val delegate: Future[T]) extends Future[T] { + + import FutureWithSession.withCurrentSession + + override def isCompleted: Boolean = delegate.isCompleted + + override def value: Option[Try[T]] = delegate.value + + override def result(atMost: Duration)(implicit permit: CanAwait): T = delegate.result(atMost) + + override def ready(atMost: Duration)(implicit permit: CanAwait) = { + delegate.ready(atMost) + this + } + + override def onComplete[U](f: (Try[T]) => U)(implicit executor:ExecutionContext): Unit = { + val sessionFn = withCurrentSession(f) + delegate.onComplete(sessionFn) + } + + override def map[S](f: T => S)(implicit executor: ExecutionContext): FutureWithSession[S] = { + val sessionFn = withCurrentSession(f) + new FutureWithSession(delegate.map(sessionFn)) + } + + override def flatMap[S](f: T => Future[S])(implicit executor: ExecutionContext): FutureWithSession[S] = { + val sessionFn = withCurrentSession(f) + new FutureWithSession(delegate.flatMap(sessionFn)) + } + + override def andThen[U](pf: PartialFunction[Try[T], U])(implicit executor: ExecutionContext): FutureWithSession[T] = { + val sessionFn = withCurrentSession(pf) + new FutureWithSession(delegate.andThen { + case t => sessionFn(t) + }) + } + + override def failed: FutureWithSession[Throwable] = { + new FutureWithSession(delegate.failed) + } + + override def fallbackTo[U >: T](that: Future[U]): FutureWithSession[U] = { + new FutureWithSession[U](delegate.fallbackTo(that)) + } + + override def recover[U >: T](pf: PartialFunction[Throwable, U])(implicit executor: ExecutionContext): FutureWithSession[U] = { + val sessionFn = withCurrentSession(pf) + new FutureWithSession(delegate.recover { + case t => sessionFn(t) + }) + } + + override def recoverWith[U >: T](pf: PartialFunction[Throwable, Future[U]])(implicit executor: ExecutionContext): FutureWithSession[U] = { + val sessionFn = withCurrentSession(pf) + new FutureWithSession(delegate.recoverWith { + case t => sessionFn(t) + }) + } + + override def transform[S](s: T => S, f: Throwable => Throwable)(implicit executor: ExecutionContext): Future[S] = { + val sessionSuccessFn = withCurrentSession(s) + val sessionFailureFn = withCurrentSession(f) + + new FutureWithSession(delegate.transform(s => sessionSuccessFn(s), f => sessionFailureFn(f))) + } + + override def zip[U](that: Future[U]): Future[(T, U)] = { + new FutureWithSession(delegate.zip(that)) + } +} + +object FutureWithSession { + + /** + * Creates `Future` instance aware of current request and session. Each `Future` returned by chained + * transformation method (e.g. `map`, `flatMap`) will be also request/session-aware. However, it's + * important to bear in mind that initial request and session are not propagated to chained methods. + * It's required that current execution thread for chained method has its own request/session available + * if reading/writing some data to it as a part of chained method execution. + */ + def withCurrentSession[T](task: => T)(implicit executionContext: ExecutionContext): Future[T] = { + FutureWithSession(task) + } + + private def apply[T](task: => T)(implicit executionContext: ExecutionContext): FutureWithSession[T] = { + S.session match { + case Full(_) => + val sessionFn = withCurrentSession(() => task) + new FutureWithSession(Future[T](sessionFn())) + + case _ => + new FutureWithSession(Future.failed[T]( + new IllegalStateException("LiftSession not available in this thread context") + )) + } + } + + private def withCurrentSession[T](task: () => T): () => T = { + val session = S.session openOrThrowException "LiftSession not available in this thread context" + session.buildDeferredFunction(task) + } + + private def withCurrentSession[A,T](task: (A)=>T): (A)=>T = { + val session = S.session openOrThrowException "LiftSession not available in this thread context" + session.buildDeferredFunction(task) + } +} diff --git a/web/webkit/src/main/scala/net/liftweb/http/FutureWithSession.scala b/web/webkit/src/main/scala_2.12/net/liftweb/http/FutureWithSession.scala similarity index 89% rename from web/webkit/src/main/scala/net/liftweb/http/FutureWithSession.scala rename to web/webkit/src/main/scala_2.12/net/liftweb/http/FutureWithSession.scala index 2aa9ccf006..7477d7083e 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/FutureWithSession.scala +++ b/web/webkit/src/main/scala_2.12/net/liftweb/http/FutureWithSession.scala @@ -85,22 +85,14 @@ private[http] class FutureWithSession[T](private[this] val delegate: Future[T]) new FutureWithSession(delegate.transform(s => sessionSuccessFn(s), f => sessionFailureFn(f))) } - /** - * This operation has to exist for Lift to compile for 2.12, but doesn't exist in 2.11 so it will - * throw an exception. - */ - @scala.throws(classOf[UnsupportedOperationException]) def transform[S](f: Try[T] => Try[S])(implicit executor: ExecutionContext): Future[S] = { - throw new UnsupportedOperationException("This transform method isn't supported since it doesn't exist in Scala 2.11.") + val sessionFn = withCurrentSession(f) + new FutureWithSession(delegate.transform(sessionFn)) } - /** - * This operation has to exist for Lift to compile for 2.12, but doesn't exist in 2.11 so it will - * throw an exception. - */ - @scala.throws(classOf[UnsupportedOperationException]) def transformWith[S](f: Try[T] => Future[S])(implicit executor: ExecutionContext): Future[S] = { - throw new UnsupportedOperationException("The trhansformWith method isn't supported since it doesn't existin in Scala 2.11.") + val sessionFn = withCurrentSession(f) + new FutureWithSession(delegate.transformWith(sessionFn)) } override def zip[U](that: Future[U]): Future[(T, U)] = { diff --git a/web/webkit/src/test/scala/net/liftweb/http/FutureWithSessionSpec.scala b/web/webkit/src/test/scala_2.11/net/liftweb/http/FutureWithSessionSpec.scala similarity index 100% rename from web/webkit/src/test/scala/net/liftweb/http/FutureWithSessionSpec.scala rename to web/webkit/src/test/scala_2.11/net/liftweb/http/FutureWithSessionSpec.scala diff --git a/web/webkit/src/test/scala_2.12/net/liftweb/http/FutureWithSessionSpec.scala b/web/webkit/src/test/scala_2.12/net/liftweb/http/FutureWithSessionSpec.scala new file mode 100644 index 0000000000..cbbe0bd015 --- /dev/null +++ b/web/webkit/src/test/scala_2.12/net/liftweb/http/FutureWithSessionSpec.scala @@ -0,0 +1,391 @@ +package net.liftweb.http + +import net.liftweb.common.Empty +import net.liftweb.mockweb.WebSpec +import org.specs2.matcher.ThrownMessages + +import scala.concurrent.Future +import scala.concurrent.ExecutionContext.Implicits.global +import scala.util.{Failure, Success} + +class FutureWithSessionSpec extends WebSpec with ThrownMessages { + + sequential + + object SessionVar1 extends SessionVar[String]("Uninitialized1") + object SessionVar2 extends SessionVar[String]("Uninitialized2") + + object ReqVar1 extends RequestVar[String]("Uninitialized1") + object ReqVar2 extends RequestVar[String]("Uninitialized2") + + "FutureWithSession" should { + + "fail if session is not available" in { + val future = FutureWithSession.withCurrentSession("kaboom") + + future.value must eventually(beSome(beFailedTry[String].withThrowable[IllegalStateException]( + "LiftSession not available in this thread context" + ))) + } + + "succeed with original value if session is available" withSFor "/" in { + val future = FutureWithSession.withCurrentSession("works!") + + future.value must eventually(beEqualTo(Some(Success("works!")))) + } + + "have access to session variables in Future task" withSFor "/" in { + SessionVar1("dzien dobry") + + val future = FutureWithSession.withCurrentSession(SessionVar1.is) + + future.value must eventually(beEqualTo(Some(Success("dzien dobry")))) + } + + "have access to request variables in Future task" withSFor "/" in { + ReqVar1("guten tag") + + val future = FutureWithSession.withCurrentSession(ReqVar1.is) + + future.value must eventually(beEqualTo(Some(Success("guten tag")))) + } + + "have access to session variables in onComplete()" withSFor "/" in { + // workaround for a possible race condition in SessionVar + // https://groups.google.com/forum/#!topic/liftweb/V1pWy14Wl3A + SessionVar1.is + + val future = FutureWithSession.withCurrentSession("thorgal") + future.onComplete { + case Success(v) => SessionVar1(v) + case Failure(reason) => ko("Future execution failed: " + reason) + } + + SessionVar1.is must eventually(beEqualTo("thorgal")) + } + + "have access to request variables in onComplete()" withSFor "/" in { + // workaround for a possible race condition in AnyVarTrait + // https://groups.google.com/forum/#!topic/liftweb/V1pWy14Wl3A + ReqVar1.is + + val future = FutureWithSession.withCurrentSession("thor") + future.onComplete { + case Success(v) => ReqVar1(v) + case Failure(reason) => ko("Future execution failed: " + reason) + } + + ReqVar1.is must eventually(beEqualTo("thor")) + } + + "have access to session variables in chains of map()" withSFor "/" in { + SessionVar1("b") + SessionVar2("c") + + val future = FutureWithSession.withCurrentSession("a") + val mapped = future.map(_ + SessionVar1.is).map(_ + SessionVar2.is) + + mapped.value must eventually(beEqualTo(Some(Success("abc")))) + } + + "have access to request variables in chains of map()" withSFor "/" in { + ReqVar1("b") + ReqVar2("c") + + val future = FutureWithSession.withCurrentSession("a") + val mapped = future.map(_ + ReqVar1.is).map(_ + ReqVar2.is) + + mapped.value must eventually(beEqualTo(Some(Success("abc")))) + } + + "have access to session variables in chains of flatMap()" withSFor "/" in { + SessionVar1("e") + SessionVar2("f") + + val future = FutureWithSession.withCurrentSession("d") + val mapped = future + .flatMap { s => + val out = s + SessionVar1.is + Future(out) + } + .flatMap { s => + val out = s + SessionVar2.is + Future(out) + } + + mapped.value must eventually(beEqualTo(Some(Success("def")))) + } + + "have access to request variables in chains of flatMap()" withSFor "/" in { + ReqVar1("e") + ReqVar2("f") + + val future = FutureWithSession.withCurrentSession("d") + val mapped = future + .flatMap { s => + val out = s + ReqVar1.is + Future(out) + } + .flatMap { s => + val out = s + ReqVar2.is + Future(out) + } + + mapped.value must eventually(beEqualTo(Some(Success("def")))) + } + + "have access to session variables in chains of andThen()" withSFor "/" in { + // workaround for a possible race condition in AnyVarTrait + // https://groups.google.com/forum/#!topic/liftweb/V1pWy14Wl3A + SessionVar1.is + SessionVar2.is + + val future = FutureWithSession.withCurrentSession("rambo") + .andThen { case Success(v) => SessionVar1(v) } + .andThen { case Success(v) => SessionVar2(v) } + + SessionVar1.is must eventually(beEqualTo("rambo")) + SessionVar2.is must eventually(beEqualTo("rambo")) + future.value must eventually(beEqualTo(Some(Success("rambo")))) + } + + "have access to request variables in chains of andThen()" withSFor "/" in { + // workaround for a possible race condition in AnyVarTrait + // https://groups.google.com/forum/#!topic/liftweb/V1pWy14Wl3A + ReqVar1.is + ReqVar2.is + + val future = FutureWithSession.withCurrentSession("conan") + .andThen { case Success(v) => ReqVar1(v) } + .andThen { case Success(v) => ReqVar2(v) } + + ReqVar1.is must eventually(beEqualTo("conan")) + ReqVar2.is must eventually(beEqualTo("conan")) + future.value must eventually(beEqualTo(Some(Success("conan")))) + } + + "have access to session variables in failed projection" withSFor "/" in { + SessionVar1("on purpose") + + val future = FutureWithSession.withCurrentSession(throw new Exception("failed")).failed.collect { + case e: Exception => e.getMessage + " " + SessionVar1.is + } + + future.value must eventually(beEqualTo(Some(Success("failed on purpose")))) + } + + "have access to request variables in failed projection" withSFor "/" in { + ReqVar1("on purpose") + + val future = FutureWithSession.withCurrentSession(throw new Exception("failed")).failed.collect { + case e: Exception => e.getMessage + " " + ReqVar1.is + } + + future.value must eventually(beEqualTo(Some(Success("failed on purpose")))) + } + + "have access to session variables in fallbackTo() result" withSFor "/" in { + SessionVar1("result") + + val future = FutureWithSession.withCurrentSession(throw new Exception("failed")) + .fallbackTo(Future("fallback")).map(_ + " " + SessionVar1.is) + + future.value must eventually(beEqualTo(Some(Success("fallback result")))) + } + + "have access to request variables in fallbackTo() result" withSFor "/" in { + ReqVar1("result") + + val future = FutureWithSession.withCurrentSession(throw new Exception("failed")) + .fallbackTo(Future("fallback")).map(_ + " " + ReqVar1.is) + + future.value must eventually(beEqualTo(Some(Success("fallback result")))) + } + + "have access to session variables with recover()" withSFor "/" in { + SessionVar1("g") + SessionVar2("h") + + val future = FutureWithSession.withCurrentSession(throw new Exception("failed")) + .recover { case e: Exception => e.getMessage + " " + SessionVar1.is } + .map(_ + SessionVar2.is) + + future.value must eventually(beEqualTo(Some(Success("failed gh")))) + } + + "have access to request variables with recover()" withSFor "/" in { + ReqVar1("g") + ReqVar2("h") + + val future = FutureWithSession.withCurrentSession(throw new Exception("failed")) + .recover { case e: Exception => e.getMessage + " " + ReqVar1.is } + .map(_ + ReqVar2.is) + + future.value must eventually(beEqualTo(Some(Success("failed gh")))) + } + + "have access to session variables with recoverWith()" withSFor "/" in { + SessionVar1("i") + SessionVar2("j") + + val future = FutureWithSession.withCurrentSession(throw new Exception("failed")) + .recoverWith { case e: Exception => + val out = e.getMessage + " " + SessionVar1.is + Future(out) + } + .map(_ + SessionVar2.is) + + future.value must eventually(beEqualTo(Some(Success("failed ij")))) + } + + "have access to request variables with recoverWith()" withSFor "/" in { + ReqVar1("k") + ReqVar2("l") + + val future = FutureWithSession.withCurrentSession(throw new Exception("failed")) + .recoverWith { case e: Exception => + val out = e.getMessage + " " + ReqVar1.is + Future(out) + } + .map(_ + ReqVar2.is) + + future.value must eventually(beEqualTo(Some(Success("failed kl")))) + } + + "have access to session variables with transform()" withSFor "/" in { + SessionVar1("john") + SessionVar2("rambo") + + val future = FutureWithSession.withCurrentSession("something") + .transform(s => throw new Exception(SessionVar1.is), identity[Throwable]) + .transform(identity[String], t => new Exception(t.getMessage + " " + SessionVar2.is)) + .recover { case e: Exception => e.getMessage } + + future.value must eventually(beEqualTo(Some(Success("john rambo")))) + } + + "have access to request variables with transform()" withSFor "/" in { + ReqVar1("chuck") + ReqVar2("norris") + + val future = FutureWithSession.withCurrentSession("something") + .transform(s => throw new Exception(ReqVar1.is), identity[Throwable]) + .transform(identity[String], t => new Exception(t.getMessage + " " + ReqVar2.is)) + .recover { case e: Exception => e.getMessage } + + future.value must eventually(beEqualTo(Some(Success("chuck norris")))) + } + + "have access to session variables with Try-based transform()" withSFor "/" in { + SessionVar1("john") + SessionVar2("rambo") + + val future = FutureWithSession.withCurrentSession("something") + .transform(s => throw new Exception(SessionVar1.is)) + .transform({ + case Success(_) => + throw new Exception("Nooope.") + case Failure(exception) => + Failure(new Exception(exception.getMessage + " " + SessionVar2.is)) + }) + .recover { case e: Exception => e.getMessage } + + future.value must eventually(beEqualTo(Some(Success("john rambo")))) + } + + "have access to request variables with Try-based transform()" withSFor "/" in { + ReqVar1("chuck") + ReqVar2("norris") + + val future = FutureWithSession.withCurrentSession("something") + .transform(s => throw new Exception(ReqVar1.is), identity[Throwable]) + .transform({ + case Success(_) => + throw new Exception("Nooope.") + case Failure(exception) => + Failure(new Exception(exception.getMessage + " " + ReqVar2.is)) + }) + .recover { case e: Exception => e.getMessage } + + future.value must eventually(beEqualTo(Some(Success("chuck norris")))) + } + + "have access to session variables with transformWith()" withSFor "/" in { + SessionVar1("john") + SessionVar2("rambo") + + val future = FutureWithSession.withCurrentSession("something") + .transformWith { s => + val thingie = SessionVar1.is + + Future(thingie) + }.transformWith({ + case Success(message) => + throw new Exception(message + " " + SessionVar2.is) + case Failure(_) => + throw new Exception("Nooope.") + }) + .recover { case e: Exception => e.getMessage } + + future.value must eventually(beEqualTo(Some(Success("john rambo")))) + } + + "have access to request variables with transformWith()" withSFor "/" in { + ReqVar1("chuck") + ReqVar2("norris") + + val future = FutureWithSession.withCurrentSession("something") + .transformWith { s => + val thingie = ReqVar1.is + + Future(thingie) + }.transformWith({ + case Success(message) => + throw new Exception(message + " " + ReqVar2.is) + case Failure(_) => + throw new Exception("Nooope.") + }) + .recover { case e: Exception => e.getMessage } + + future.value must eventually(beEqualTo(Some(Success("chuck norris")))) + } + + "yield another session aware future with zip()" withSFor "/" in { + ReqVar1("a") + SessionVar1("hero") + + val future = FutureWithSession.withCurrentSession("gotham") + .zip(Future("needs")) + .collect { case (one, two) => one + two + ReqVar1.is + SessionVar1.is } + + future.value must eventually(beEqualTo(Some(Success("gothamneedsahero")))) + } + + "not leak out initial session between threads with their own sessions" in { + val session1 = new LiftSession("Test session 1", "", Empty) + val session2 = new LiftSession("Test session 2", "", Empty) + val session3 = new LiftSession("Test session 3", "", Empty) + + S.initIfUninitted(session1)(SessionVar1("one")) + S.initIfUninitted(session2)(SessionVar1("two")) + S.initIfUninitted(session3)(SessionVar1("three")) + + val future = S.initIfUninitted(session1)(FutureWithSession.withCurrentSession("zero")) + + S.initIfUninitted(session2) { + val mapped = future.map(v => SessionVar1.is) + mapped.value must eventually(beEqualTo(Some(Success("two")))) + } + + S.initIfUninitted(session3) { + val mapped = future.map(v => SessionVar1.is) + mapped.value must eventually(beEqualTo(Some(Success("three")))) + } + + S.initIfUninitted(session1) { + val mapped = future.map(v => SessionVar1.is) + mapped.value must eventually(beEqualTo(Some(Success("one")))) + } + } + } +} From 3d7735e173893c2da6533baf5dca862b1954a28f Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 28 Jan 2017 10:47:04 -0500 Subject: [PATCH 1480/1949] Clean up PaginatorSnippet scaladocs a little. --- .../scala/net/liftweb/http/Paginator.scala | 46 ++++++++----------- 1 file changed, 19 insertions(+), 27 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/Paginator.scala b/web/webkit/src/main/scala/net/liftweb/http/Paginator.scala index bb901b4247..d3c0801872 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Paginator.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Paginator.scala @@ -24,7 +24,7 @@ import util._ import S.? /** - * Base class for things that require pagination. Implements a contract + * Base class for things that require pagination. Implements a contract * for supplying the correct number of browsable pages etc * * @tparam T the type of item being paginated @@ -71,9 +71,9 @@ trait Paginator[T] extends Loggable { } /** - * In many situations you'll want to sort things in your paginated view. + * In many situations you'll want to sort things in your paginated view. * SortedPaginator is a specialized paginator for doing such tasks. - * + * * T: The type of the elements, accessed via def page within the listing snippet * C: The type of the columns, used to specify sorting * @@ -117,19 +117,11 @@ trait SortedPaginator[T, C] extends Paginator[T] { } /** - * This is the paginator snippet. It provides page - * navigation and column sorting links. - * View XHTML is as follows: - * nav prefix (prefix is configurable by overriding def navPrefix) - * - <nav:first/> - a link to the first page - * - <nav:prev/> - a link to the previous page - * - <nav:allpages/> - individual links to all pages. The contents of this node are used to separate page links. - * - <nav:next/> - a link to the next page - * - <nav:last/> - a link to the last page - * - <nav:records/> - a description of which records are currently being displayed - * - <nav:recordsFrom/> - the first record number being displayed - * - <nav:recordsTo/> - the last record number being displayed - * - <nav:recordsCount/> - the total number of records on all pages + * This is the paginator snippet. It provides page navigation and column sorting + * links. + * + * The values for the pagination are bound according to the classes specified in + * the [[paginate]] method, using a CSS selector transform. * * @author nafg and Timothy Perrett */ @@ -150,7 +142,7 @@ trait PaginatorSnippet[T] extends Paginator[T] { * The "last page" link text */ def lastXml: NodeSeq = Text(?(">>")) - + /** * How to display the page's starting record */ @@ -162,7 +154,7 @@ trait PaginatorSnippet[T] extends Paginator[T] { /** * The status displayed when using <nav:records/> in the template. */ - def currentXml: NodeSeq = + def currentXml: NodeSeq = if(count==0) Text(S.?("paginator.norecords")) else @@ -226,14 +218,14 @@ trait PaginatorSnippet[T] extends Paginator[T] { * code. * * Classes used to bind: - * - `first`: link to go back to the first page (populated by `firstXml`) - * - `prev`: link to go to previous page (populated by `prevXml`) - * - `all-pages`: container for all pages (populated by `pagesXml`) - * - `zoomed-pages`: container for `zoomedPages` (populated by `pagesXml`) - * - `next`: link to go to next page (populated by `nextXml`) - * - `last`: link to go to last page (populated by `lastXml`) + * - `first`: link to go back to the first page (populated by `[[firstXml]]`) + * - `prev`: link to go to previous page (populated by `[[prevXml]]`) + * - `all-pages`: container for all pages (populated by `[[pagesXml]]`) + * - `zoomed-pages`: container for `zoomedPages` (populated by `[[pagesXml]]`) + * - `next`: link to go to next page (populated by `[[nextXml]]`) + * - `last`: link to go to last page (populated by `[[lastXml]]`) * - `records`: currently visible records + total count (populated by - * `currentXml`) + * `[[currentXml]]`) * - `records-start`: start of currently visible records * - `records-end`: end of currently visible records * - `records-count`: total records count @@ -325,8 +317,8 @@ trait SortedPaginatorSnippet[T, C] extends SortedPaginator[T, C] with PaginatorS } /** - * Sort your paginated views by using lifts functions mapping. - * The only down side with this style is that your links are session + * Sort your paginated views by using lifts functions mapping. + * The only down side with this style is that your links are session * specific and non-bookmarkable. * If you mix this trait in to a StatefulSnippet, it should work out the box. * Otherwise, implement 'registerThisSnippet.' From 09b4262a874861b40784b6bb85d1553291437ccc Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 28 Jan 2017 10:47:17 -0500 Subject: [PATCH 1481/1949] Add 2.6-to-3.0 migration docs for Paginator. --- docs/migration/2.6-to-3.0-paginator.adoc | 161 +++++++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 docs/migration/2.6-to-3.0-paginator.adoc diff --git a/docs/migration/2.6-to-3.0-paginator.adoc b/docs/migration/2.6-to-3.0-paginator.adoc new file mode 100644 index 0000000000..8ba796632e --- /dev/null +++ b/docs/migration/2.6-to-3.0-paginator.adoc @@ -0,0 +1,161 @@ +:idprefix: +:idseparator: - +:toc: right +:toclevels: 2 + += Migrating `Paginator` from Lift 2.6 to Lift 3.0 + +The 2.x series of Lift brought a lot of change and innovation. In particular, +while it started with an approach based on the `bind` helper that transformed +namespaced XHTML elements into the values that the developer wanted to display, +it progressed to an HTML5-based approach built around CSS selector transforms. +As of Lift 3.0, CSS selector transforms are the only supported transforms, so +as to keep the core of the framework relatively lean and encourage proper HTML +usage. + +One of the Lift components that leveraged the `bind`-style transforms heavily +was `Paginator`. As of Lift 3.0, it's been reworked to use CSS selector +transforms instead, which means you'll be moving from placeholder elements that +use XML namespaces to regular HTML elements with appropriate classes. + +== Bind Points + +In the old `Paginator`, bind points were elements with the namespace `navPrefix` +and various names, e.g. `nav:first` for the link pointing to the first page. +Lift 3.0 instead looks for certain CSS classes in elements. Here's a mapping +from old `nav:*` elements to CSS classes: + +.Lift 2.6 wizard element to Lift 3.0 CSS class mapping +|========================= +| Lift 2.6 Element | Lift 3.0 CSS Class + +| `nav:first` | `first` + +| `nav:prev` | `prev` + +| `nav:allpages` | `all-pages` + +| `nav:zoomedpages` | `zoomed-pages` + +| `nav:next` | `next` + +| `nav:last` | `last` + +| `nav:records` | `records` + +| `nav:recordsFrom` | `records-start` + +| `nav:recordsTo` | `records-end` + +| `nav:recordsCount` | `records-count` +|========================= + +Generally speaking, you can annotate the container element or the element that +will have a given value directly with the class of the content it should +contain, rather than needing an extra container with the class like the old +`nav:*` elements. For example, where before you had: + +[.lift-26] +.Lift 2.6 simple pagination example +```html + +
          +
        • + + +
        • +
        +
        +``` + +In Lift 3.0, you can remove all the `nav:*` elements and instead use the classes +to mark the appropriate elements: + +[.lift-30] +.Lift 3.0 simple pagination example +```html +
          +
        • First
        • + + +
        • Last
        • +
        +``` + +There are a couple of special cases, however, due to the way that the paginator +snippet is structured. + +=== `all-pages` and `zoomed-pages` + +The `all-pages` element is meant to contain a link to each available page, with +some delimiter between them. For example, you might want pages to look like this: + +.Simple pagination layout +``` + << < 1 | 2 | 3 | 4 | 5 > >> +``` + +Where `3` is the current page and each page number except for 3 is a link. In +Lift 2.6, you might write this: + +[.lift-26] +.Lift 2.6 pagination example with all pages +```html + +
          +
        • + +
        • + | +
        • + +
        • +
        +
        +``` + +The content of the `nav:allpages` is then used as the delimiter. + +In Lift 3.0, the content of the element marked with class `all-pages` is +similarly used as the delimiter, so you would write: + +[.lift-30] +.Lift 3.0 pagination example with all pages +```html +
          +
        • First
        • + +
        • |
        • + +
        • Last
        • +
        +``` + +In your paginator snippet, you can override the way any individual page number +link is created by overriding the `pageXml` method---this is the same in Lift +3.0 as in 2.6. + +`zoomed-pages` works the same way, except zoomed pages doesn't show all pages, +instead showing a subset that is scaled (e.g., it might show 1, 2, 3, 4, 5, 6, +7, 8, 9, 10, 20 if 50 pages are available) to give access deeper in the page. +The content of that element is also the delimiter, so in Lift 3.0 you might use +it as such: + +[.lift-30] +.Lift 3.0 pagination example with zoomed pages +```html +
          +
        • First
        • + +
        • |
        • + +
        • Last
        • +
        +``` + +== Further Help + +That's it! All the other elements work as they did in 2.6, except you don't need +weird XML elements in your containers; the CSS classes are enough. If you run +into any issues porting your screen over to Lift 3.0's `Paginator`, please ask +on the Lift mailing list and you should find willing helpers. From f8c730f081877f1db67ef28a1c9235ef10100666 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 28 Jan 2017 23:36:09 -0500 Subject: [PATCH 1482/1949] Make extract a bit easier to read. --- core/json/src/main/scala/net/liftweb/json/Extraction.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/json/src/main/scala/net/liftweb/json/Extraction.scala b/core/json/src/main/scala/net/liftweb/json/Extraction.scala index dd74525db6..3647be3311 100644 --- a/core/json/src/main/scala/net/liftweb/json/Extraction.scala +++ b/core/json/src/main/scala/net/liftweb/json/Extraction.scala @@ -199,7 +199,8 @@ object Extraction { private def extract0(json: JValue, clazz: Class[_], typeArgs: Seq[Class[_]]) (implicit formats: Formats): Any = { - extract0(json, mkMapping(clazz, typeArgs)) + val mapping = mkMapping(clazz, typeArgs) + extract0(json, mapping) } def extract(json: JValue, target: TypeInfo)(implicit formats: Formats): Any = From 307a17d4b9e7bee723c70302292a789cf97ee3f2 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 28 Jan 2017 23:36:21 -0500 Subject: [PATCH 1483/1949] Remove some merge boo boos. --- .../test/scala/net/liftweb/json/ExtractionBugs.scala | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/core/json/src/test/scala/net/liftweb/json/ExtractionBugs.scala b/core/json/src/test/scala/net/liftweb/json/ExtractionBugs.scala index a1fe272c89..0067ca9f1e 100644 --- a/core/json/src/test/scala/net/liftweb/json/ExtractionBugs.scala +++ b/core/json/src/test/scala/net/liftweb/json/ExtractionBugs.scala @@ -122,16 +122,4 @@ object ExtractionBugs extends Specification { val deserialized = parse(serialized).extract[Holder2] deserialized must_== holder } - - case class Response(data: List[Map[String, Int]]) - - case class OptionOfInt(opt: Option[Int]) - - case class PMap(m: Map[String, List[String]]) - - case class ManyConstructors(id: Long, name: String, lastName: String, email: String) { - def this() = this(0, "John", "Doe", "") - def this(name: String) = this(0, name, "Doe", "") - def this(name: String, email: String) = this(0, name, "Doe", email) - } } From b73d6601a8ffe655026bc34819effd326ebde2f3 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 28 Jan 2017 23:36:33 -0500 Subject: [PATCH 1484/1949] Correctly map tuples to the HCol concept. The mapping engine here determines what we actually try to instantiate when we see incoming JSON and we're trying to create a concrete series of objects through extraction. Here, we indicate to the rest of the system that if we're looking for a tuple in the instantiated type we'll expect the JSON representation to be an array of some sort. --- core/json/src/main/scala/net/liftweb/json/Extraction.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/json/src/main/scala/net/liftweb/json/Extraction.scala b/core/json/src/main/scala/net/liftweb/json/Extraction.scala index 3647be3311..ec9bb1b306 100644 --- a/core/json/src/main/scala/net/liftweb/json/Extraction.scala +++ b/core/json/src/main/scala/net/liftweb/json/Extraction.scala @@ -192,6 +192,9 @@ object Extraction { Col(TypeInfo(clazz, None), mkMapping(typeArgs.head, typeArgs.tail)) } else if (clazz == classOf[Map[_, _]]) { Dict(mkMapping(typeArgs.tail.head, typeArgs.tail.tail)) + } else if (tuple_?(clazz)) { + val childMappings = typeArgs.map(c => mkMapping(c, Nil)).toList + HCol(TypeInfo(clazz, None), childMappings) } else { mappingOf(clazz, typeArgs) } From 128f3feeccdd51aa3003dc3f8a449135560b03a8 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 28 Jan 2017 23:46:03 -0500 Subject: [PATCH 1485/1949] Retain the ability to interpret old tuples. Before my changes, tuples were serialized as follows: {"_1": "value", "_2": "value2"} After my changes, tuples are serialized like this: ["value", "value2"] But we still will stumble on a lot of tuples that have been written out to some permanent storage in the old format. Now, if we expect there to be a tuple in a particular object but we found a JObject representation we will convert it to a JArray under the hood and recursively re-invoke the newTuple function. This will only apply if all the keys in the object start with an underscore, which should both be sufficiently cheap and sufficiently idicitive of a tuple since we're expecting a tuple in the field we're trying to inflate anyway. --- core/json/src/main/scala/net/liftweb/json/Extraction.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/json/src/main/scala/net/liftweb/json/Extraction.scala b/core/json/src/main/scala/net/liftweb/json/Extraction.scala index ec9bb1b306..5a1d99a4b3 100644 --- a/core/json/src/main/scala/net/liftweb/json/Extraction.scala +++ b/core/json/src/main/scala/net/liftweb/json/Extraction.scala @@ -331,7 +331,7 @@ object Extraction { } } - def newTuple(root: JValue, mappings: List[Mapping]) = { + def newTuple(root: JValue, mappings: List[Mapping]): Any = { root match { case JArray(items) if items.length >= 1 && items.length <= 22 => val builtItems: Seq[Object] = items.zip(mappings).map({ @@ -343,6 +343,9 @@ object Extraction { val typedTupleConstructor = tupleClass.getConstructors()(0) typedTupleConstructor.newInstance(builtItems: _*) + case JObject(items) if items.forall(_.name.startsWith("_")) => + newTuple(JArray(items.map(_.value)), mappings) + case JArray(items) => throw new IllegalArgumentException("Cannot create a tuple of length " + items.length) From 799a0ef4c809384c94d4d67ba959594cb4306b93 Mon Sep 17 00:00:00 2001 From: n4to4 Date: Sat, 11 Feb 2017 18:55:31 +0900 Subject: [PATCH 1486/1949] Fix broken links --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b97d12434a..56318a2358 100644 --- a/README.md +++ b/README.md @@ -100,11 +100,11 @@ Please note that the modules project does accept pull requests. #### examples -The [examples](https://github.com/lift/examples) repository contains the source code for several example Lift applications, including [demo.liftweb.com](http://demo.liftweb.com/). +The [examples](https://github.com/lift/examples) repository contains the source code for several example Lift applications, including [demo.liftweb.com](http://demo.liftweb.net/). ## Building Lift -If you simply want to use Lift in your project, add Lift as a dependency to your build system or [download the JAR files directly](www.liftweb.net/download). +If you simply want to use Lift in your project, add Lift as a dependency to your build system or [download the JAR files directly](https://www.liftweb.net/download). If you wish to build Lift from source, check out this repository and use the included `liftsh` script to build some or all of the components you want. From ccbfd94eba0c81277c7ab26b3d477cdae6f1bb32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20=C5=BBebrowski?= Date: Tue, 24 Jan 2017 19:56:59 +0100 Subject: [PATCH 1487/1949] Mongo async support limit scope of privates in MongoAsync CR fixes use upsert parameter --- contributors.md | 6 ++ .../liftweb/mongodb/record/BsonRecord.scala | 30 ++++++ .../mongodb/record/MongoMetaRecord.scala | 71 ++++++++++++--- .../liftweb/mongodb/record/MongoRecord.scala | 10 ++ .../record/field/BsonRecordField.scala | 10 +- .../mongodb/record/field/JObjectField.scala | 6 ++ .../record/field/JsonObjectField.scala | 5 +- .../record/field/MongoCaseClassField.scala | 27 +++++- .../record/field/MongoFieldFlavor.scala | 2 +- .../mongodb/record/field/MongoListField.scala | 18 ++++ .../mongodb/record/field/MongoMapField.scala | 28 ++++++ .../mongodb/record/MongoFieldSpec.scala | 19 +++- .../net/liftweb/mongodb/JObjectParser.scala | 6 ++ .../net/liftweb/mongodb/MongoAsync.scala | 91 +++++++++++++++++++ .../net/liftweb/mongodb/MongoCodecs.scala | 14 +++ project/Build.scala | 2 +- project/Dependencies.scala | 3 +- 17 files changed, 321 insertions(+), 27 deletions(-) create mode 100644 persistence/mongodb/src/main/scala/net/liftweb/mongodb/MongoAsync.scala create mode 100644 persistence/mongodb/src/main/scala/net/liftweb/mongodb/MongoCodecs.scala diff --git a/contributors.md b/contributors.md index f8faa1c88d..bd3a301604 100644 --- a/contributors.md +++ b/contributors.md @@ -245,3 +245,9 @@ John Marshall ### Email: ### john at themillhousegroup dot com + +### Name: ### +Marek Żebrowski + +### Email: ### +marek.zebrowski at gmail dot com diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/BsonRecord.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/BsonRecord.scala index 87504cc298..ee8dc0023d 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/BsonRecord.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/BsonRecord.scala @@ -28,6 +28,7 @@ import net.liftweb.record.field._ import com.mongodb._ import java.util.prefs.BackingStoreException +import org.bson.Document /** Specialized Record that can be encoded and decoded from BSON (DBObject) */ trait BsonRecord[MyType <: BsonRecord[MyType]] extends Record[MyType] { @@ -41,6 +42,8 @@ trait BsonRecord[MyType <: BsonRecord[MyType]] extends Record[MyType] { */ def asDBObject: DBObject = meta.asDBObject(this) + def asDocument: Document = meta.asDocument(this) + /** * Set the fields of this record from the given DBObject */ @@ -88,6 +91,18 @@ trait BsonMetaRecord[BaseRecord <: BsonRecord[BaseRecord]] extends MetaRecord[Ba dbo.get } + def asDocument(inst: BaseRecord): Document = { + val dbo = new Document() // use this so regex patterns can be stored. + + for { + field <- fields(inst) + dbValue <- fieldDbValue(field) + } { dbo.append(field.name, dbValue) } + + dbo + } + + /** * Return the value of a field suitable to be put in a DBObject */ @@ -147,4 +162,19 @@ trait BsonMetaRecord[BaseRecord <: BsonRecord[BaseRecord]] extends MetaRecord[Ba inst.fields.foreach(_.resetDirty) } } + + def setFieldsFromDocument(inst: BaseRecord, doc: Document): Unit = { + for (k <- doc.keySet; field <- inst.fieldByName(k.toString)) { + field.setFromAny(doc.get(k.toString)) + } + inst.runSafe { + inst.fields.foreach(_.resetDirty) + } + } + + def fromDocument(doc: Document): BaseRecord = { + val inst: BaseRecord = createRecord + setFieldsFromDocument(inst, doc) + inst + } } diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/MongoMetaRecord.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/MongoMetaRecord.scala index 7278c9e394..d282a6f862 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/MongoMetaRecord.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/MongoMetaRecord.scala @@ -18,25 +18,21 @@ package net.liftweb package mongodb package record -import java.util.{Calendar, UUID} -import java.util.regex.Pattern - -import scala.collection.JavaConversions._ +import java.util.UUID +import com.mongodb._ +import com.mongodb.async.SingleResultCallback +import com.mongodb.client.model.UpdateOptions +import com.mongodb.client.result.UpdateResult import net.liftweb.common._ -import net.liftweb.json.{Formats, JsonParser} import net.liftweb.json.JsonAST._ -import net.liftweb.mongodb._ -import net.liftweb.mongodb.record.field._ -import net.liftweb.record.{MandatoryTypedField, MetaRecord, Record} -import net.liftweb.record.FieldHelpers.expectedA -import net.liftweb.record.field._ -import net.liftweb.util.ConnectionIdentifier - -import com.mongodb._ -import com.mongodb.util.JSON +import net.liftweb.record.MandatoryTypedField +import org.bson.Document import org.bson.types.ObjectId +import scala.collection.JavaConversions._ +import scala.concurrent.{Future, Promise} + trait MongoMetaRecord[BaseRecord <: MongoRecord[BaseRecord]] extends BsonMetaRecord[BaseRecord] with MongoMeta[BaseRecord] { @@ -65,6 +61,10 @@ trait MongoMetaRecord[BaseRecord <: MongoRecord[BaseRecord]] */ def useDb[T](f: DB => T): T = MongoDB.use(connectionIdentifier)(f) + def useCollAsync[T](f: com.mongodb.async.client.MongoCollection[Document] => T): T = { + MongoAsync.useCollection[T](connectionIdentifier, collectionName)(f) + } + /** * Delete the instance from backing store */ @@ -263,6 +263,18 @@ trait MongoMetaRecord[BaseRecord <: MongoRecord[BaseRecord]] } } + def insertAsync(inst:BaseRecord): Future[Boolean] = { + useCollAsync { coll => + val cb = new SingleBooleanVoidCallback( () => { + foreachCallback(inst, _.afterSave) + inst.allFields.foreach { _.resetDirty } + }) + foreachCallback(inst, _.beforeSave) + coll.insertOne(inst.asDocument, cb) + cb.future + } + } + /** * Save the instance in the appropriate backing store */ @@ -279,6 +291,37 @@ trait MongoMetaRecord[BaseRecord <: MongoRecord[BaseRecord]] db.getCollection(collectionName).save(inst.asDBObject, concern) } + /** + * replaces document with new one with given id. if `upsert` is set to true inserts new document + * in similar way as save() from sync api + * + */ + def replaceOneAsync(inst: BaseRecord, upsert: Boolean, concern: WriteConcern) = { + useCollAsync { coll => + val p = Promise[BaseRecord] + val doc: Document = inst.asDocument + val options = new UpdateOptions().upsert(upsert) + foreachCallback(inst, _.beforeSave) + val filter = new org.bson.Document("_id", doc.get("_id")) + coll.withWriteConcern(concern).replaceOne(filter, doc, options, new SingleResultCallback[UpdateResult] { + override def onResult(result: UpdateResult, t: Throwable) = { + if (t == null) { + val upsertedId = result.getUpsertedId + if (upsertedId != null && upsertedId.isObjectId) { + inst.fieldByName("_id").foreach(fld => fld.setFromAny(upsertedId.asObjectId().getValue)) + } + foreachCallback(inst, _.afterSave) + inst.allFields.foreach { _.resetDirty } + p.success(inst) + } else { + p.failure(t) + } + } + }) + p.future + } + } + /** * Insert multiple records */ diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/MongoRecord.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/MongoRecord.scala index 1f9988ed6d..3b0541c7a4 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/MongoRecord.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/MongoRecord.scala @@ -25,6 +25,7 @@ import com.mongodb.{BasicDBObject, DBObject, DBRef, WriteConcern} import org.bson.types.ObjectId import common.{Full, Box} +import scala.concurrent.Future trait MongoRecord[MyType <: MongoRecord[MyType]] extends BsonRecord[MyType] { self: MyType => @@ -53,6 +54,15 @@ trait MongoRecord[MyType <: MongoRecord[MyType]] extends BsonRecord[MyType] { this } + /** + * Inserts record and returns Future that completes when mongo driver finishes operation + */ + def insertAsync():Future[Boolean] = { + runSafe { + meta.insertAsync(this) + } + } + /** * Save the instance and return the instance */ diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala index 2edca6b11f..1d0adb583f 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala @@ -24,9 +24,9 @@ import http.js.JsExp import http.js.JE.JsNull import json.JsonAST._ import json.Printer - import net.liftweb.record._ import com.mongodb._ +import org.bson.Document import scala.xml._ @@ -62,6 +62,7 @@ class BsonRecordField[OwnerType <: BsonRecord[OwnerType], SubRecordType <: BsonR def setFromAny(in: Any): Box[SubRecordType] = in match { case dbo: DBObject => setBox(Full(valueMeta.fromDBObject(dbo))) + case dbo: org.bson.Document => setBox(Full(valueMeta.fromDocument(dbo))) case _ => genericSetFromAny(in) } @@ -103,4 +104,11 @@ class BsonRecordListField[OwnerType <: BsonRecord[OwnerType], SubRecordType <: B }))) case other => setBox(FieldHelpers.expectedA("JArray", other)) } + + override def setFromDocumentList(list: java.util.List[Document]): Box[List[SubRecordType]] = { + setBox(Full(list.map{ doc => + valueMeta.fromDocument(doc) + }.toList)) + } + } diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala index 987a820862..76f0378bc8 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala @@ -28,6 +28,7 @@ import net.liftweb.record.{Field, FieldHelpers, MandatoryTypedField} import scala.xml.NodeSeq import com.mongodb._ +import org.bson.Document class JObjectField[OwnerType <: BsonRecord[OwnerType]](rec: OwnerType) extends Field[JObject, OwnerType] @@ -48,6 +49,7 @@ with MongoFieldFlavor[JObject] { def setFromAny(in: Any): Box[JObject] = in match { case dbo: DBObject => setBox(setFromDBObject(dbo)) + case doc: Document => setBox(setFromDocument(doc)) case jv: JObject => setBox(Full(jv)) case Some(jv: JObject) => setBox(Full(jv)) case Full(jv: JObject) => setBox(Full(jv)) @@ -73,4 +75,8 @@ with MongoFieldFlavor[JObject] { def setFromDBObject(obj: DBObject): Box[JObject] = Full(JObjectParser.serialize(obj)(owner.meta.formats).asInstanceOf[JObject]) + + def setFromDocument(obj: Document): Box[JObject] = + Full(JObjectParser.serialize(obj)(owner.meta.formats).asInstanceOf[JObject]) + } diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JsonObjectField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JsonObjectField.scala index 8ec10d931d..0fa2b7005f 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JsonObjectField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JsonObjectField.scala @@ -17,16 +17,14 @@ package record package field import scala.xml.{NodeSeq, Text} - import net.liftweb.common.{Box, Empty, Failure, Full} - import net.liftweb.http.js.JE.{JsNull, Str} import net.liftweb.json.JsonAST._ import net.liftweb.json.{JsonParser, Printer} import net.liftweb.record.{Field, FieldHelpers, MandatoryTypedField, Record} import net.liftweb.util.Helpers.tryo - import com.mongodb.DBObject +import org.bson.Document abstract class JsonObjectField[OwnerType <: BsonRecord[OwnerType], JObjectType <: JsonObject[JObjectType]] (rec: OwnerType, valueMeta: JsonObjectMeta[JObjectType]) @@ -83,5 +81,6 @@ abstract class JsonObjectField[OwnerType <: BsonRecord[OwnerType], JObjectType < // set this field's value using a DBObject returned from Mongo. def setFromDBObject(dbo: DBObject): Box[JObjectType] = setFromJValue(JObjectParser.serialize(dbo).asInstanceOf[JObject]) + } diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoCaseClassField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoCaseClassField.scala index 43397eb4bc..67aaa09ef6 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoCaseClassField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoCaseClassField.scala @@ -28,8 +28,8 @@ import net.liftweb.util.Helpers import net.liftweb.json._ import reflect.Manifest import net.liftweb.http.js.JsExp - - +import org.bson.Document +import scala.collection.JavaConverters._ class MongoCaseClassField[OwnerType <: Record[OwnerType],CaseType](rec: OwnerType)( implicit mf: Manifest[CaseType]) extends Field[CaseType, OwnerType] with MandatoryTypedField[CaseType] with MongoFieldFlavor[CaseType] { // override this for custom formats @@ -58,6 +58,11 @@ class MongoCaseClassField[OwnerType <: Record[OwnerType],CaseType](rec: OwnerTyp JObjectParser.parse(asJValue.asInstanceOf[JObject]) } + def setFromDocument(doc: Document): Box[CaseType] = { + val jv = JObjectParser.serialize(doc) + setFromJValue(jv) + } + def setFromDBObject(dbo: DBObject): Box[CaseType] = { val jvalue = JObjectParser.serialize(dbo) setFromJValue(jvalue) @@ -69,6 +74,7 @@ class MongoCaseClassField[OwnerType <: Record[OwnerType],CaseType](rec: OwnerTyp def setFromAny(in: Any): Box[CaseType] = in match { case dbo: DBObject => setFromDBObject(dbo) + case doc: org.bson.Document => setFromDocument(doc) case c if mf.runtimeClass.isInstance(c) => setBox(Full(c.asInstanceOf[CaseType])) case Full(c) if mf.runtimeClass.isInstance(c) => setBox(Full(c.asInstanceOf[CaseType])) case null|None|Empty => setBox(defaultValueBox) @@ -101,6 +107,11 @@ class MongoCaseClassListField[OwnerType <: Record[OwnerType],CaseType](rec: Owne case _ => setBox(Empty) } + def setFromDocumentList(list: java.util.List[Document]): Box[MyType] = { + val objs = list.asScala.map{ d => JObjectParser.serialize(d) } + setFromJValue(JArray(objs.toList)) + } + def asDBObject: DBObject = { val dbl = new BasicDBList @@ -121,6 +132,18 @@ class MongoCaseClassListField[OwnerType <: Record[OwnerType],CaseType](rec: Owne def setFromAny(in: Any): Box[MyType] = in match { case dbo: DBObject => setFromDBObject(dbo) case list@c::xs if mf.runtimeClass.isInstance(c) => setBox(Full(list.asInstanceOf[MyType])) + case jlist: java.util.List[_] => { + if (!jlist.isEmpty) { + val elem = jlist.get(0) + if (elem.isInstanceOf[org.bson.Document]) { + setFromDocumentList(jlist.asInstanceOf[java.util.List[org.bson.Document]]) + } else { + setBox(Full(jlist.asScala.toList.asInstanceOf[MyType])) + } + } else { + setBox(Full(Nil)) + } + } case _ => setBox(Empty) } diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoFieldFlavor.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoFieldFlavor.scala index 12cb8e7118..33a6abf4cd 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoFieldFlavor.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoFieldFlavor.scala @@ -23,8 +23,8 @@ import net.liftweb.common.{Box, Empty, Failure, Full} import net.liftweb.http.js.JE.{JsNull, JsRaw} import net.liftweb.json.Printer import net.liftweb.json.JsonAST._ - import com.mongodb.DBObject +import org.bson.Document /** * Describes common aspects related to Mongo fields diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoListField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoListField.scala index b7125da796..a45896c32c 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoListField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoListField.scala @@ -30,6 +30,7 @@ import net.liftweb.record.{Field, FieldHelpers, MandatoryTypedField, Record} import util.Helpers._ import com.mongodb._ +import org.bson.Document import org.bson.types.ObjectId import org.joda.time.DateTime @@ -71,6 +72,18 @@ class MongoListField[OwnerType <: BsonRecord[OwnerType], ListType: Manifest](rec case list@c::xs if mf.runtimeClass.isInstance(c) => setBox(Full(list.asInstanceOf[MyType])) case Some(list@c::xs) if mf.runtimeClass.isInstance(c) => setBox(Full(list.asInstanceOf[MyType])) case Full(list@c::xs) if mf.runtimeClass.isInstance(c) => setBox(Full(list.asInstanceOf[MyType])) + case jlist: java.util.List[_] => { + if(!jlist.isEmpty) { + val elem = jlist.get(0) + if(elem.isInstanceOf[org.bson.Document]) { + setFromDocumentList(jlist.asInstanceOf[java.util.List[org.bson.Document]]) + } else { + setBox(Full(jlist.toList.asInstanceOf[MyType])) + } + } else { + setBox(Full(Nil)) + } + } case s: String => setFromString(s) case Some(s: String) => setFromString(s) case Full(s: String) => setFromString(s) @@ -147,6 +160,11 @@ class MongoListField[OwnerType <: BsonRecord[OwnerType], ListType: Manifest](rec // set this field's value using a DBObject returned from Mongo. def setFromDBObject(dbo: DBObject): Box[MyType] = setBox(Full(dbo.asInstanceOf[BasicDBList].toList.asInstanceOf[MyType])) + + def setFromDocumentList(list: java.util.List[Document]): Box[MyType] = { + throw new RuntimeException("Warning, , setting Document as field with no conversion, probably not something you want to do") + } + } /* diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoMapField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoMapField.scala index 7497314fbb..ab44be8255 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoMapField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoMapField.scala @@ -29,6 +29,7 @@ import net.liftweb.record._ import net.liftweb.util.Helpers.tryo import com.mongodb._ +import org.bson.Document /** * Note: setting optional_? = false will result in incorrect equals behavior when using setFromJValue @@ -46,6 +47,7 @@ class MongoMapField[OwnerType <: BsonRecord[OwnerType], MapValueType](rec: Owner def setFromAny(in: Any): Box[Map[String, MapValueType]] = { in match { case dbo: DBObject => setFromDBObject(dbo) + case doc: Document => setFromDocument(doc) case map: Map[_, _] => setBox(Full(map.asInstanceOf[Map[String, MapValueType]])) case Some(map: Map[_, _]) => setBox(Full(map.asInstanceOf[Map[String, MapValueType]])) case Full(map: Map[_, _]) => setBox(Full(map.asInstanceOf[Map[String, MapValueType]])) @@ -108,5 +110,31 @@ class MongoMapField[OwnerType <: BsonRecord[OwnerType], MapValueType](rec: Owner } )) } + + // set this field's value using a bson.Document returned from Mongo. + def setFromDocument(doc: Document): Box[Map[String, MapValueType]] = { + import scala.collection.JavaConverters._ + val map: Map[String, MapValueType] = doc.asScala.map { + case (k, v) => k -> v.asInstanceOf[MapValueType] + } (scala.collection.breakOut) + + setBox { + Full(map) + } + } + + def asDocument: Document = { + import scala.collection.JavaConverters._ + val map: Map[String, AnyRef] = { + value.keys.map { + k => k -> value.getOrElse(k, "") + .asInstanceOf[AnyRef] + } (scala.collection.breakOut) + } + + new Document(map.asJava) + } + + } diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala index ea93e11299..369ae583c2 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala @@ -25,9 +25,7 @@ import org.bson.types.ObjectId import org.specs2.mutable._ import org.specs2.specification._ import org.specs2.execute.AsResult - import org.joda.time.DateTime - import common._ import json._ import mongodb.BsonDSL._ @@ -37,10 +35,11 @@ import http.js.JE._ import http.js.JsExp import net.liftweb.record._ import common.Box._ + import xml.{Elem, NodeSeq, Text} -import util.{Helpers, FieldError} +import util.{FieldError, Helpers} import Helpers._ - +import org.bson.Document import org.bson.types.ObjectId /** @@ -553,6 +552,18 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundEach { } } + "MongoMapField" should { + "create itself from bson doc" in { + import scala.collection.JavaConversions._ + val rec = MapTestRecord.createRecord + val map = Map("a" -> "4", "b" -> "5", "c" -> "6") + val doc = new Document(map) + rec.mandatoryStringMapField.setFromDocument(doc) + rec.mandatoryStringMapField.value must_== map + rec.mandatoryStringMapField.asDocument must_== doc + } + } + "BsonRecordField" should { "function correctly" in { val rec = SubRecordTestRecord.createRecord diff --git a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/JObjectParser.scala b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/JObjectParser.scala index ab2e4b430b..19b78f7ece 100644 --- a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/JObjectParser.scala +++ b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/JObjectParser.scala @@ -28,6 +28,7 @@ import net.liftweb.util.SimpleInjector import com.mongodb.{BasicDBObject, BasicDBList, DBObject} import org.bson.types.ObjectId +import org.bson.Document object JObjectParser extends SimpleInjector { /** @@ -65,6 +66,11 @@ object JObjectParser extends SimpleInjector { JField(f.toString, serialize(x.get(f.toString))(formats)) } ) + case x: Document => JObject( + x.keySet.toList.map { f => + JField(f.toString, serialize(x.get(f.toString), formats)) + } + ) case x => { JNothing } diff --git a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/MongoAsync.scala b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/MongoAsync.scala new file mode 100644 index 0000000000..0c6a92cd9e --- /dev/null +++ b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/MongoAsync.scala @@ -0,0 +1,91 @@ +package net.liftweb.mongodb + +import java.util.concurrent.ConcurrentHashMap + +import com.mongodb.MongoException +import com.mongodb.async.client.{MongoClient, MongoCollection, MongoDatabase} +import com.mongodb.async.SingleResultCallback +import net.liftweb.util.ConnectionIdentifier +import org.bson.Document +import org.bson.codecs.{IntegerCodec, LongCodec, ObjectIdCodec, StringCodec} +import org.bson.codecs.configuration.CodecRegistries + +import scala.concurrent.Promise + +private[mongodb] class SingleBooleanVoidCallback(f: () => Unit) extends SingleResultCallback[Void] { + private[this] val p = Promise[Boolean]() + override def onResult(result: java.lang.Void, t: Throwable): Unit = { + if (t == null) { + f() + p.success(true) + } + else p.failure(t) + } + def future = p.future +} +/* + Async version of MongoClient + */ +object MongoAsync { + + /* + * HashMap of Mongo instance and db name tuples, keyed by ConnectionIdentifier + */ + private[this] val dbs = new ConcurrentHashMap[ConnectionIdentifier, (MongoClient, String)] + val codecRegistry = CodecRegistries.fromRegistries(com.mongodb.MongoClient.getDefaultCodecRegistry(), + CodecRegistries.fromCodecs(new LongPrimitiveCodec, new IntegerPrimitiveCodec) + ) + + /** + * Define a Mongo db using a MongoClient instance. + */ + def defineDb(name: ConnectionIdentifier, mngo: MongoClient, dbName: String) { + dbs.put(name, (mngo, dbName)) + } + + /* + * Get a DB reference + */ + def getDb(name: ConnectionIdentifier): Option[MongoDatabase] = dbs.get(name) match { + case null => None + case (mngo, db) => Some(mngo.getDatabase(db).withCodecRegistry(codecRegistry)) + } + + + def use[T](ci: ConnectionIdentifier)(f: (MongoDatabase) => T): T = { + val db = getDb(ci) match { + case Some(mongo) => mongo + case _ => throw new MongoException("Mongo not found: "+ci.toString) + } + f(db) + } + + /** + * Executes function {@code f} with the mongo named {@code name} and collection names {@code collectionName}. + * Gets a collection for you. + */ + def useCollection[T](name: ConnectionIdentifier, collectionName: String)(f: (MongoCollection[Document]) => T): T = { + val coll = getCollection(name, collectionName) match { + case Some(collection) => collection + case _ => throw new MongoException("Mongo not found: "+collectionName+". ConnectionIdentifier: "+name.toString) + } + + f(coll) + } + + /* +* Get a Mongo collection. Gets a Mongo db first. +*/ + private[this] def getCollection(name: ConnectionIdentifier, collectionName: String): Option[MongoCollection[Document]] = getDb(name) match { + case Some(mongo) if mongo != null => Some(mongo.getCollection(collectionName)) + case _ => None + } + + def closeAll(): Unit = { + import scala.collection.JavaConverters._ + dbs.values.asScala.foreach { case (mngo, _) => + mngo.close() + } + dbs.clear() + } +} diff --git a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/MongoCodecs.scala b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/MongoCodecs.scala new file mode 100644 index 0000000000..d87d1b09e5 --- /dev/null +++ b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/MongoCodecs.scala @@ -0,0 +1,14 @@ +package net.liftweb.mongodb + +import org.bson.codecs._ + +/** + * additional codecs to register for primitive types + */ +class LongPrimitiveCodec extends LongCodec { + override def getEncoderClass() = java.lang.Long.TYPE +} + +class IntegerPrimitiveCodec extends IntegerCodec { + override def getEncoderClass() = java.lang.Integer.TYPE +} diff --git a/project/Build.scala b/project/Build.scala index 5d7f1941e3..62b3359a43 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -224,7 +224,7 @@ object BuildDef extends Build { persistenceProject("mongodb") .dependsOn(json_ext, util) .settings(parallelExecution in Test := false, - libraryDependencies += mongo_driver, + libraryDependencies ++= Seq(mongo_java_driver, mongo_java_driver_async), initialize in Test <<= (resourceDirectory in Test) { rd => System.setProperty("java.util.logging.config.file", (rd / "logging.properties").absolutePath) }) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index a120899061..80084971eb 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -36,7 +36,8 @@ object Dependencies { lazy val joda_time = "joda-time" % "joda-time" % "2.9.2" lazy val joda_convert = "org.joda" % "joda-convert" % "1.8.1" lazy val htmlparser = "nu.validator.htmlparser" % "htmlparser" % "1.4" - lazy val mongo_java_driver = "org.mongodb" % "mongo-java-driver" % "3.4.0" + lazy val mongo_java_driver = "org.mongodb" % "mongodb-driver" % "3.4.1" + lazy val mongo_java_driver_async = "org.mongodb" % "mongodb-driver-async" % "3.4.1" lazy val paranamer = "com.thoughtworks.paranamer" % "paranamer" % "2.8" lazy val scalajpa = "org.scala-libs" % "scalajpa" % "1.5" cross CVMappingAll lazy val scalap: ModuleMap = "org.scala-lang" % "scalap" % _ From a827404cf133573df4de287cf5c748248933c5e6 Mon Sep 17 00:00:00 2001 From: Tim Nelson Date: Sun, 5 Mar 2017 06:44:35 -0600 Subject: [PATCH 1488/1949] Added default params and return type to replaceOneAsync. Added MongoRules.defaultWriteConcern. --- .../net/liftweb/mongodb/record/MongoMetaRecord.scala | 6 +++--- .../liftweb/mongodb/record/field/BsonRecordField.scala | 4 ++-- .../net/liftweb/mongodb/record/field/JObjectField.scala | 2 +- .../net/liftweb/mongodb/record/field/MongoListField.scala | 4 ++-- .../scala/net/liftweb/mongodb/record/MongoFieldSpec.scala | 2 +- .../src/main/scala/net/liftweb/mongodb/MongoRules.scala | 8 +++++++- 6 files changed, 16 insertions(+), 10 deletions(-) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/MongoMetaRecord.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/MongoMetaRecord.scala index d282a6f862..333074d0db 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/MongoMetaRecord.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/MongoMetaRecord.scala @@ -1,5 +1,5 @@ /* - * Copyright 2010-2014 WorldWide Conferencing, LLC + * Copyright 2010-2017 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. @@ -296,13 +296,13 @@ trait MongoMetaRecord[BaseRecord <: MongoRecord[BaseRecord]] * in similar way as save() from sync api * */ - def replaceOneAsync(inst: BaseRecord, upsert: Boolean, concern: WriteConcern) = { + def replaceOneAsync(inst: BaseRecord, upsert: Boolean = true, concern: WriteConcern = MongoRules.defaultWriteConcern.vend): Future[BaseRecord] = { useCollAsync { coll => val p = Promise[BaseRecord] val doc: Document = inst.asDocument val options = new UpdateOptions().upsert(upsert) foreachCallback(inst, _.beforeSave) - val filter = new org.bson.Document("_id", doc.get("_id")) + val filter = new Document("_id", doc.get("_id")) coll.withWriteConcern(concern).replaceOne(filter, doc, options, new SingleResultCallback[UpdateResult] { override def onResult(result: UpdateResult, t: Throwable) = { if (t == null) { diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala index 1d0adb583f..5ec8ec45d9 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala @@ -1,5 +1,5 @@ /* - * Copyright 2011 WorldWide Conferencing, LLC + * Copyright 2011-2017 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. @@ -62,7 +62,7 @@ class BsonRecordField[OwnerType <: BsonRecord[OwnerType], SubRecordType <: BsonR def setFromAny(in: Any): Box[SubRecordType] = in match { case dbo: DBObject => setBox(Full(valueMeta.fromDBObject(dbo))) - case dbo: org.bson.Document => setBox(Full(valueMeta.fromDocument(dbo))) + case dbo: Document => setBox(Full(valueMeta.fromDocument(dbo))) case _ => genericSetFromAny(in) } diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala index 76f0378bc8..954bb9e955 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala @@ -1,5 +1,5 @@ /* - * Copyright 2010-2012 WorldWide Conferencing, LLC + * Copyright 2010-2017 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. diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoListField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoListField.scala index a45896c32c..8e4d962b7a 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoListField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoListField.scala @@ -1,5 +1,5 @@ /* - * Copyright 2010-2014 WorldWide Conferencing, LLC + * Copyright 2010-2017 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. @@ -162,7 +162,7 @@ class MongoListField[OwnerType <: BsonRecord[OwnerType], ListType: Manifest](rec setBox(Full(dbo.asInstanceOf[BasicDBList].toList.asInstanceOf[MyType])) def setFromDocumentList(list: java.util.List[Document]): Box[MyType] = { - throw new RuntimeException("Warning, , setting Document as field with no conversion, probably not something you want to do") + throw new RuntimeException("Warning, setting Document as field with no conversion, probably not something you want to do") } } diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala index 369ae583c2..e7439db0a6 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala @@ -1,5 +1,5 @@ /* - * Copyright 2006-2015 WorldWide Conferencing, LLC + * Copyright 2006-2017 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. diff --git a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/MongoRules.scala b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/MongoRules.scala index b574974421..1c8e4c4bd4 100644 --- a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/MongoRules.scala +++ b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/MongoRules.scala @@ -1,5 +1,5 @@ /** - * Copyright 2014 WorldWide Conferencing, LLC + * Copyright 2014-2017 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. @@ -17,6 +17,8 @@ package mongodb import util.{ConnectionIdentifier, SimpleInjector} import util.Helpers._ +import com.mongodb.WriteConcern + object MongoRules extends SimpleInjector { private def defaultCollectionNameFunc(conn: ConnectionIdentifier, name: String): String = { charSplit(name, '.').last.toLowerCase+"s" @@ -33,4 +35,8 @@ object MongoRules extends SimpleInjector { * RecordRules.collectionName.default.set((_,name) => StringHelpers.snakify(name)) */ val collectionName = new Inject[(ConnectionIdentifier, String) => String](defaultCollectionNameFunc _) {} + + /** The default WriteConcern used in some places. + */ + val defaultWriteConcern = new Inject[WriteConcern](WriteConcern.ACKNOWLEDGED) {} } From b09c030340c3e1ced5024b0e81d45257c9cf307e Mon Sep 17 00:00:00 2001 From: Tim Nelson Date: Sun, 5 Mar 2017 06:59:36 -0600 Subject: [PATCH 1489/1949] Use Option instead of checking for null in replaceOneAsync --- .../scala/net/liftweb/mongodb/record/MongoMetaRecord.scala | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/MongoMetaRecord.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/MongoMetaRecord.scala index 333074d0db..9a5fc1b053 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/MongoMetaRecord.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/MongoMetaRecord.scala @@ -305,9 +305,8 @@ trait MongoMetaRecord[BaseRecord <: MongoRecord[BaseRecord]] val filter = new Document("_id", doc.get("_id")) coll.withWriteConcern(concern).replaceOne(filter, doc, options, new SingleResultCallback[UpdateResult] { override def onResult(result: UpdateResult, t: Throwable) = { - if (t == null) { - val upsertedId = result.getUpsertedId - if (upsertedId != null && upsertedId.isObjectId) { + if (Option(t).isEmpty) { + Option(result.getUpsertedId).filter(_.isObjectId).foreach { upsertedId => inst.fieldByName("_id").foreach(fld => fld.setFromAny(upsertedId.asObjectId().getValue)) } foreachCallback(inst, _.afterSave) From 0fa1cbe84fc36b4523f77e066a51927fcc8bc9cf Mon Sep 17 00:00:00 2001 From: Tim Nelson Date: Sun, 5 Mar 2017 13:18:46 -0600 Subject: [PATCH 1490/1949] Added tests for MongoAsync. Re-wrote MongoTestKit to use a single MongoClient. --- .../mongodb/record/MongoMetaRecord.scala | 4 +- .../record/field/JsonObjectField.scala | 1 - .../record/field/MongoFieldFlavor.scala | 9 +- .../mongodb/record/field/MongoListField.scala | 4 +- .../mongodb/record/MongoAsyncTestKit.scala | 137 ++++++++++++++++++ .../mongodb/record/MongoFieldSpec.scala | 6 +- .../mongodb/record/MongoRecordAsyncSpec.scala | 101 +++++++++++++ .../liftweb/mongodb/record/MongoTestKit.scala | 60 ++++---- .../scala/net/liftweb/mongodb/Mongo.scala | 28 +++- .../net/liftweb/mongodb/MongoAsync.scala | 125 +++++++++++----- .../net/liftweb/mongodb/MongoCodecs.scala | 21 ++- .../mongodb/MongoDirectMongoClient.scala | 5 +- .../MongoDocumentMongoClientSpec.scala | 4 +- .../scala/net/liftweb/mongodb/MongoSpec.scala | 39 ++--- .../net/liftweb/mongodb/MongoTestKit.scala | 60 ++++---- 15 files changed, 461 insertions(+), 143 deletions(-) create mode 100644 persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoAsyncTestKit.scala create mode 100644 persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordAsyncSpec.scala diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/MongoMetaRecord.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/MongoMetaRecord.scala index 9a5fc1b053..5290506575 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/MongoMetaRecord.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/MongoMetaRecord.scala @@ -263,7 +263,7 @@ trait MongoMetaRecord[BaseRecord <: MongoRecord[BaseRecord]] } } - def insertAsync(inst:BaseRecord): Future[Boolean] = { + def insertAsync(inst: BaseRecord): Future[Boolean] = { useCollAsync { coll => val cb = new SingleBooleanVoidCallback( () => { foreachCallback(inst, _.afterSave) @@ -304,7 +304,7 @@ trait MongoMetaRecord[BaseRecord <: MongoRecord[BaseRecord]] foreachCallback(inst, _.beforeSave) val filter = new Document("_id", doc.get("_id")) coll.withWriteConcern(concern).replaceOne(filter, doc, options, new SingleResultCallback[UpdateResult] { - override def onResult(result: UpdateResult, t: Throwable) = { + override def onResult(result: UpdateResult, t: Throwable): Unit = { if (Option(t).isEmpty) { Option(result.getUpsertedId).filter(_.isObjectId).foreach { upsertedId => inst.fieldByName("_id").foreach(fld => fld.setFromAny(upsertedId.asObjectId().getValue)) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JsonObjectField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JsonObjectField.scala index 0fa2b7005f..5961849d7d 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JsonObjectField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JsonObjectField.scala @@ -24,7 +24,6 @@ import net.liftweb.json.{JsonParser, Printer} import net.liftweb.record.{Field, FieldHelpers, MandatoryTypedField, Record} import net.liftweb.util.Helpers.tryo import com.mongodb.DBObject -import org.bson.Document abstract class JsonObjectField[OwnerType <: BsonRecord[OwnerType], JObjectType <: JsonObject[JObjectType]] (rec: OwnerType, valueMeta: JsonObjectMeta[JObjectType]) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoFieldFlavor.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoFieldFlavor.scala index 33a6abf4cd..602d249465 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoFieldFlavor.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoFieldFlavor.scala @@ -14,17 +14,16 @@ * limitations under the License. */ -package net.liftweb -package mongodb -package record -package field +package net.liftweb +package mongodb +package record +package field import net.liftweb.common.{Box, Empty, Failure, Full} import net.liftweb.http.js.JE.{JsNull, JsRaw} import net.liftweb.json.Printer import net.liftweb.json.JsonAST._ import com.mongodb.DBObject -import org.bson.Document /** * Describes common aspects related to Mongo fields diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoListField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoListField.scala index 8e4d962b7a..b49086a865 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoListField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoListField.scala @@ -75,8 +75,8 @@ class MongoListField[OwnerType <: BsonRecord[OwnerType], ListType: Manifest](rec case jlist: java.util.List[_] => { if(!jlist.isEmpty) { val elem = jlist.get(0) - if(elem.isInstanceOf[org.bson.Document]) { - setFromDocumentList(jlist.asInstanceOf[java.util.List[org.bson.Document]]) + if(elem.isInstanceOf[Document]) { + setFromDocumentList(jlist.asInstanceOf[java.util.List[Document]]) } else { setBox(Full(jlist.toList.asInstanceOf[MyType])) } diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoAsyncTestKit.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoAsyncTestKit.scala new file mode 100644 index 0000000000..4bab4774e4 --- /dev/null +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoAsyncTestKit.scala @@ -0,0 +1,137 @@ +/* + * Copyright 2010-2017 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 mongodb +package record + +import util.{ConnectionIdentifier, DefaultConnectionIdentifier, Props} + +import scala.collection.JavaConverters._ +import scala.concurrent.{Await, Promise} +import scala.concurrent.duration._ +import java.util.concurrent.TimeoutException + +import org.specs2.mutable.Specification +import org.specs2.specification.BeforeAfterEach + +import org.bson.Document + +import com.mongodb.Block +import com.mongodb.async.SingleResultCallback +import com.mongodb.async.client.{MongoClients, MongoDatabase} + +// The sole mongo object for testing async +object TestMongoAsync { + val mongo = { + val uri = Props.get("mongo.test.uri", "127.0.0.1:27017") + MongoClients.create(s"mongodb://$uri") + } + + class SingleResultCallbackF[A]() extends SingleResultCallback[A] { + private[this] val p = Promise[A]() + + override def onResult(result: A, error: Throwable): Unit = { + Option(error) match { + case None => + p.success(result) + case Some(t) => + p.failure(t) + } + } + + def future = p.future + } + + lazy val isMongoRunning: Boolean = + try { + val res = mongo.listDatabaseNames + val cb = new SingleResultCallbackF[Void]() + + res.forEach( + new Block[String]() { + override def apply(name: String): Unit = { } + }, + cb + ) + + // this will throw an exception if it can't connect to the db + Await.result(cb.future, Duration(2000, MILLISECONDS)) + true + } catch { + case _: TimeoutException => + false + } +} + +trait MongoAsyncTestKit extends Specification with BeforeAfterEach { + sequential + + protected def dbName = "lift_record_"+this.getClass.getName + .replace("$", "") + .replace("net.liftweb.mongodb.record.", "") + .replace(".", "_") + .toLowerCase + + // If you need more than one db, override this + protected def dbs: List[(ConnectionIdentifier, String)] = + (DefaultConnectionIdentifier, dbName) :: Nil + + def debug: Boolean = false + + def before = { + // define the dbs + dbs.foreach { case (id, db) => + MongoAsync.defineDb(id, TestMongoAsync.mongo.getDatabase(db)) + MongoDB.defineDb(id, TestMongo.mongo, db) + } + } + + def checkMongoIsRunning = { + TestMongoAsync.isMongoRunning must beEqualTo(true).orSkip + TestMongo.isMongoRunning must beEqualTo(true).orSkip + } + + def after = { + if (!debug && TestMongoAsync.isMongoRunning) { + val cb = new SingleResultCallback[Void] { + override def onResult(result: Void, t: Throwable) = { } + } + // drop the databases + dbs.foreach { case (id, _) => + MongoAsync.use(id) { _.drop(cb) } + } + } + + // clear the mongo instances + dbs.foreach { case (id, _) => + MongoAsync.remove(id) + } + + if (!debug && TestMongo.isMongoRunning) { + // drop the databases + dbs.foreach { case (id, _) => + MongoDB.use(id) { db => db.dropDatabase } + } + } + + // clear the mongo instances + dbs.foreach { case (id, _) => + MongoDB.remove(id) + } + } +} + diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala index e7439db0a6..cc4faa91b9 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala @@ -554,10 +554,10 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundEach { "MongoMapField" should { "create itself from bson doc" in { - import scala.collection.JavaConversions._ + import scala.collection.JavaConverters._ val rec = MapTestRecord.createRecord - val map = Map("a" -> "4", "b" -> "5", "c" -> "6") - val doc = new Document(map) + val map = Map[String, AnyRef]("a" -> "4", "b" -> "5", "c" -> "6") + val doc = new Document(map.asJava) rec.mandatoryStringMapField.setFromDocument(doc) rec.mandatoryStringMapField.value must_== map rec.mandatoryStringMapField.asDocument must_== doc diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordAsyncSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordAsyncSpec.scala new file mode 100644 index 0000000000..c094f1dded --- /dev/null +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordAsyncSpec.scala @@ -0,0 +1,101 @@ +/* + * Copyright 2017 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 mongodb +package record + +import org.specs2.mutable.Specification + +import org.specs2.concurrent.ExecutionEnv + +class MongoRecordAsyncSpec(implicit ee: ExecutionEnv) extends Specification with MongoAsyncTestKit { + "MongoRecord Async Specification".title + + import fixtures.FieldTypeTestRecord + + "MongoRecord Async" should { + + "insert asynchronously" in { + checkMongoIsRunning + + val obj = FieldTypeTestRecord.createRecord + .mandatoryLongField(42L) + .mandatoryIntField(27) + + FieldTypeTestRecord.insertAsync(obj) must beEqualTo[Boolean](true).await + + val fetched = FieldTypeTestRecord.find(obj.id.get) + + fetched.isDefined must_== true + + fetched.foreach { o => + o.id.get must_== obj.id.get + o.mandatoryLongField.get must_== 42L + o.mandatoryIntField.get must_== 27 + } + + success + } + + "replaceOne asynchronously" in { + checkMongoIsRunning + + val obj = FieldTypeTestRecord.createRecord + .mandatoryLongField(42L) + .mandatoryIntField(27) + + FieldTypeTestRecord.replaceOneAsync(obj) must beEqualTo[FieldTypeTestRecord](obj).await + + val fetched = FieldTypeTestRecord.find(obj.id.get) + + fetched.isDefined must_== true + + fetched.foreach { o => + o.id.get must_== obj.id.get + o.mandatoryLongField.get must_== 42L + o.mandatoryIntField.get must_== 27 + } + + obj + .mandatoryLongField(44L) + .mandatoryIntField(29) + + FieldTypeTestRecord.replaceOneAsync(obj) must beEqualTo[FieldTypeTestRecord](obj).await + + val fetched2 = FieldTypeTestRecord.find(obj.id.get) + + fetched2.isDefined must_== true + + fetched2.foreach { o => + o.id.get must_== obj.id.get + o.mandatoryLongField.get must_== 44L + o.mandatoryIntField.get must_== 29 + } + + success + } + + "replaceOne without upsert" in { + checkMongoIsRunning + + val obj = FieldTypeTestRecord.createRecord + + FieldTypeTestRecord.replaceOneAsync(obj, false) must beEqualTo[FieldTypeTestRecord](obj).await + FieldTypeTestRecord.find(obj.id.get).isDefined must_== false + } + } +} diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoTestKit.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoTestKit.scala index 8778a399d5..3baa862603 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoTestKit.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoTestKit.scala @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 WorldWide Conferencing, LLC + * Copyright 2010-2017 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. @@ -18,13 +18,33 @@ package net.liftweb package mongodb package record -import util.{ConnectionIdentifier, DefaultConnectionIdentifier} +import util.{ConnectionIdentifier, DefaultConnectionIdentifier, Props} + import org.specs2.mutable.Specification import org.specs2.specification.BeforeAfterEach import com.mongodb._ +// The sole mongo object for testing +object TestMongo { + val mongo = { + val uri = Props.get("mongo.test.uri", "127.0.0.1:27017") + val opts = MongoClientOptions.builder.serverSelectionTimeout(2000) + new MongoClient(new MongoClientURI(s"mongodb://$uri", opts)) + } + + lazy val isMongoRunning: Boolean = + try { + // this will throw an exception if it can't connect to the db + mongo.getConnectPoint + true + } catch { + case _: MongoTimeoutException => + false + } +} + trait MongoTestKit extends Specification with BeforeAfterEach { sequential @@ -34,47 +54,35 @@ trait MongoTestKit extends Specification with BeforeAfterEach { .replace(".", "_") .toLowerCase - def mongo = new MongoClient("127.0.0.1", 27017) - // If you need more than one db, override this - def dbs: List[(ConnectionIdentifier, String)] = List((DefaultConnectionIdentifier, dbName)) + def dbs: List[(ConnectionIdentifier, String)] = + (DefaultConnectionIdentifier, dbName) :: Nil def debug = false def before = { // define the dbs - dbs foreach { case (id, db) => - MongoDB.defineDb(id, mongo, db) + dbs.foreach { case (id, db) => + MongoDB.defineDb(id, TestMongo.mongo, db) } } - def isMongoRunning: Boolean = - try { - if (dbs.length < 1) - false - else { - dbs foreach { case (id, _) => - MongoDB.use(id) ( db => { db.getName } ) - } - true - } - } catch { - case _: Exception => false - } - - def checkMongoIsRunning = - isMongoRunning must beEqualTo(true).orSkip + def checkMongoIsRunning = { + TestMongo.isMongoRunning must beEqualTo(true).orSkip + } def after = { - if (!debug && isMongoRunning) { + if (!debug && TestMongo.isMongoRunning) { // drop the databases - dbs foreach { case (id, _) => + dbs.foreach { case (id, _) => MongoDB.use(id) { db => db.dropDatabase } } } // clear the mongo instances - MongoDB.closeAll() + dbs.foreach { case (id, _) => + MongoDB.remove(id) + } } } diff --git a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/Mongo.scala b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/Mongo.scala index a2367a35bf..61877a089d 100644 --- a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/Mongo.scala +++ b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/Mongo.scala @@ -24,6 +24,7 @@ import java.util.concurrent.ConcurrentHashMap import scala.collection.immutable.HashSet import com.mongodb.{DB, DBCollection, Mongo, MongoClient, MongoException, MongoOptions, ServerAddress} +import com.mongodb.client.MongoDatabase /** * Main Mongo object @@ -33,7 +34,7 @@ object MongoDB { /** * HashMap of Mongo instance and db name tuples, keyed by ConnectionIdentifier */ - private val dbs = new ConcurrentHashMap[ConnectionIdentifier, (MongoClient, String)] + private[this] val dbs = new ConcurrentHashMap[ConnectionIdentifier, (MongoClient, String)] /** * Define a MongoClient db using a MongoClient instance. @@ -50,10 +51,17 @@ object MongoDB { case (mngo, db) => Some(mngo.getDB(db)) } + /** + * Get a MongoDatabase reference + */ + private[this] def getDatabase(name: ConnectionIdentifier): Option[MongoDatabase] = { + Option(dbs.get(name)).map { case (mngo, db) => mngo.getDatabase(db) } + } + /** * Get a Mongo collection. Gets a Mongo db first. */ - private def getCollection(name: ConnectionIdentifier, collectionName: String): Option[DBCollection] = getDb(name) match { + private[this] def getCollection(name: ConnectionIdentifier, collectionName: String): Option[DBCollection] = getDb(name) match { case Some(mongo) if mongo != null => Some(mongo.getCollection(collectionName)) case _ => None } @@ -120,4 +128,20 @@ object MongoDB { } dbs.clear() } + + /** + * Clear the HashMap. + */ + def clear(): Unit = { + dbs.clear() + } + + /** + * Remove a specific ConnectionIdentifier from the HashMap. + */ + def remove(id: ConnectionIdentifier): Option[MongoDatabase] = { + val db = getDatabase(id) + dbs.remove(id) + db + } } diff --git a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/MongoAsync.scala b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/MongoAsync.scala index 0c6a92cd9e..fbb8012236 100644 --- a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/MongoAsync.scala +++ b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/MongoAsync.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2017 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.mongodb import java.util.concurrent.ConcurrentHashMap @@ -7,53 +23,80 @@ import com.mongodb.async.client.{MongoClient, MongoCollection, MongoDatabase} import com.mongodb.async.SingleResultCallback import net.liftweb.util.ConnectionIdentifier import org.bson.Document -import org.bson.codecs.{IntegerCodec, LongCodec, ObjectIdCodec, StringCodec} -import org.bson.codecs.configuration.CodecRegistries import scala.concurrent.Promise private[mongodb] class SingleBooleanVoidCallback(f: () => Unit) extends SingleResultCallback[Void] { private[this] val p = Promise[Boolean]() - override def onResult(result: java.lang.Void, t: Throwable): Unit = { - if (t == null) { - f() - p.success(true) + + override def onResult(result: java.lang.Void, error: Throwable): Unit = { + Option(error) match { + case None => + f() + p.success(true) + case Some(t) => + p.failure(t) } - else p.failure(t) } + def future = p.future } -/* - Async version of MongoClient - */ -object MongoAsync { - /* - * HashMap of Mongo instance and db name tuples, keyed by ConnectionIdentifier +/** + * Async version of MongoDB. + * + * You should only have one instance of MongoClient in a JVM. + * + * Example: + * + * {{{ + * import com.mongodb.async.client.MongoClients + * import net.liftweb.util.{ConnectionIdentifier, DefaultConnectionIdentifier} + * import org.bson.codecs.configuration.CodecRegistries + * + * val client = MongoClients.create("mongodb://127.0.0.1:27017") + * + * // main database + * MongoAsync.defineDb(DefaultConnectionIdentifier, client.getDatabase("mydb")) + * + * // admin database + * case object AdminIdentifier extends ConnectionIdentifier { + * val jndiName = "admin" + * } + * + * val codecRegistry = CodecRegistries.fromRegistries( + * com.mongodb.MongoClient.getDefaultCodecRegistry(), + * CodecRegistries.fromCodecs(new LongPrimitiveCodec, new IntegerPrimitiveCodec) + * ) + + * val admin = client.getDatabase("admin").withCodecRegistry(codecRegistry) + * MongoAsync.defineDb(AdminIdentifier, admin) + * + * }}} */ - private[this] val dbs = new ConcurrentHashMap[ConnectionIdentifier, (MongoClient, String)] - val codecRegistry = CodecRegistries.fromRegistries(com.mongodb.MongoClient.getDefaultCodecRegistry(), - CodecRegistries.fromCodecs(new LongPrimitiveCodec, new IntegerPrimitiveCodec) - ) +object MongoAsync { /** - * Define a Mongo db using a MongoClient instance. + * HashMap of MongoDatabase instances keyed by ConnectionIdentifier */ - def defineDb(name: ConnectionIdentifier, mngo: MongoClient, dbName: String) { - dbs.put(name, (mngo, dbName)) - } + private[this] val dbs = new ConcurrentHashMap[ConnectionIdentifier, MongoDatabase] - /* - * Get a DB reference - */ - def getDb(name: ConnectionIdentifier): Option[MongoDatabase] = dbs.get(name) match { - case null => None - case (mngo, db) => Some(mngo.getDatabase(db).withCodecRegistry(codecRegistry)) + /** + * Define a Mongo db using a MongoDatabase instance. + */ + def defineDb(id: ConnectionIdentifier, db: MongoDatabase): Unit = { + dbs.put(id, db) } + /** + * Get a MongoDatabase reference + */ + private[this] def getDatabase(name: ConnectionIdentifier): Option[MongoDatabase] = { + Option(dbs.get(name)) + } def use[T](ci: ConnectionIdentifier)(f: (MongoDatabase) => T): T = { - val db = getDb(ci) match { + val db = getDatabase(ci) match { case Some(mongo) => mongo case _ => throw new MongoException("Mongo not found: "+ci.toString) } @@ -61,7 +104,7 @@ object MongoAsync { } /** - * Executes function {@code f} with the mongo named {@code name} and collection names {@code collectionName}. + * Executes function {@code f} with the mongo named {@code name} and collection named {@code collectionName}. * Gets a collection for you. */ def useCollection[T](name: ConnectionIdentifier, collectionName: String)(f: (MongoCollection[Document]) => T): T = { @@ -73,19 +116,25 @@ object MongoAsync { f(coll) } - /* -* Get a Mongo collection. Gets a Mongo db first. -*/ - private[this] def getCollection(name: ConnectionIdentifier, collectionName: String): Option[MongoCollection[Document]] = getDb(name) match { + /** + * Get a Mongo collection. Gets a Mongo db first. + */ + private[this] def getCollection(name: ConnectionIdentifier, collectionName: String): Option[MongoCollection[Document]] = getDatabase(name) match { case Some(mongo) if mongo != null => Some(mongo.getCollection(collectionName)) case _ => None } - def closeAll(): Unit = { - import scala.collection.JavaConverters._ - dbs.values.asScala.foreach { case (mngo, _) => - mngo.close() - } + /** + * Clear the HashMap. + */ + def clear(): Unit = { dbs.clear() } + + /** + * Remove a specific ConnectionIdentifier from the HashMap. + */ + def remove(id: ConnectionIdentifier): Option[MongoDatabase] = { + Option(dbs.remove(id)) + } } diff --git a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/MongoCodecs.scala b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/MongoCodecs.scala index d87d1b09e5..6a43ed92aa 100644 --- a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/MongoCodecs.scala +++ b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/MongoCodecs.scala @@ -1,14 +1,33 @@ +/* + * Copyright 2017 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.mongodb import org.bson.codecs._ /** - * additional codecs to register for primitive types + * Codec for java.lang.Long */ class LongPrimitiveCodec extends LongCodec { override def getEncoderClass() = java.lang.Long.TYPE } +/** + * Codec for java.lang.Integer + */ class IntegerPrimitiveCodec extends IntegerCodec { override def getEncoderClass() = java.lang.Integer.TYPE } diff --git a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDirectMongoClient.scala b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDirectMongoClient.scala index 483fcbae41..cb3cf8b203 100644 --- a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDirectMongoClient.scala +++ b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDirectMongoClient.scala @@ -1,5 +1,5 @@ /* - * Copyright 2014 WorldWide Conferencing, LLC + * Copyright 2014-2017 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. @@ -23,15 +23,12 @@ import com.mongodb._ import org.specs2.mutable.Specification - /** * System under specification for MongoDirectMonoClient. */ class MongoDirectMongoClientSpec extends Specification with MongoTestKit { "MongoDirectMongoClient Specification".title - override def mongo = new MongoClient("127.0.0.1", 27017) - "MongoClient example" in { checkMongoIsRunning diff --git a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDocumentMongoClientSpec.scala b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDocumentMongoClientSpec.scala index fbc847bcac..d1e0759ebb 100644 --- a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDocumentMongoClientSpec.scala +++ b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoDocumentMongoClientSpec.scala @@ -1,5 +1,5 @@ /* - * Copyright 2010-2014 WorldWide Conferencing, LLC + * Copyright 2010-2017 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. @@ -51,8 +51,6 @@ class MongoDocumentMongoClientSpec extends Specification with MongoTestKit { import mongoclienttestdocs._ - override def mongo = new MongoClient("127.0.0.1", 27017) - "MongoClient example" in { checkMongoIsRunning diff --git a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoSpec.scala b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoSpec.scala index 87ac3a4eb1..bc2ddc10bc 100644 --- a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoSpec.scala +++ b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoSpec.scala @@ -37,20 +37,21 @@ class MongoSpec extends Specification { // make sure mongo is running try { - MongoDB.use(id) { db => - db.getCollectionNames - } - } - catch { - case e: Exception => skipped("MongoDB is not running") + // this will throw an exception if it can't connect to the db + mc.getConnectPoint + } catch { + case _: MongoTimeoutException => + skipped("MongoDB is not running") } // using an undefined identifier throws an exception MongoDB.use(DefaultConnectionIdentifier) { db => db.getCollectionNames } must throwA(new MongoException("Mongo not found: ConnectionIdentifier(lift)")) + // remove defined db - MongoDB.closeAll() + MongoDB.remove(id) + success } @@ -59,31 +60,9 @@ class MongoSpec extends Specification { "Define DB with MongoClient instance" in { val opts = MongoClientOptions.builder .connectionsPerHost(12) + .serverSelectionTimeout(2000) .build passDefinitionTests(TestMongoIdentifier, new MongoClient(new ServerAddress("localhost"), opts), "test_default_b") } - - /* Requires a server other than localhost with auth setup. - "Define and authenticate DB with Mongo instance" in { - MongoDB.close - - // make sure mongo is running - try { - val pwd = "lift_pwd" - val dbUri = new MongoURI("mongodb://") - // define the db - MongoDB.defineDbAuth(TestMongoIdentifier, new Mongo(dbUri), "lift_auth_test", "lift_user", pwd) - // try to use it - MongoDB.use(TestMongoIdentifier) { db => - db.getLastError.ok must beEqualTo(true) - } - } - catch { - case e: Exception => skip("MongoDB is not running") - } - // remove defined db - MongoDB.closeAll() - } - */ } } diff --git a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoTestKit.scala b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoTestKit.scala index 424d4c8956..74f99f0729 100644 --- a/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoTestKit.scala +++ b/persistence/mongodb/src/test/scala/net/liftweb/mongodb/MongoTestKit.scala @@ -1,5 +1,5 @@ /* - * Copyright 2010-2014 WorldWide Conferencing, LLC + * Copyright 2010-2017 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. @@ -17,12 +17,31 @@ package net.liftweb package mongodb -import util.{ConnectionIdentifier, DefaultConnectionIdentifier} +import util.{ConnectionIdentifier, DefaultConnectionIdentifier, Props} import org.specs2.mutable.Specification import org.specs2.specification.BeforeAfterEach -import com.mongodb.MongoClient +import com.mongodb._ + +// The sole mongo object for testing +object TestMongo { + val mongo = { + val uri = Props.get("mongo.test.uri", "127.0.0.1:27017") + val opts = MongoClientOptions.builder.serverSelectionTimeout(2000) + new MongoClient(new MongoClientURI(s"mongodb://$uri", opts)) + } + + lazy val isMongoRunning: Boolean = + try { + // this will throw an exception if it can't connect to the db + mongo.getConnectPoint + true + } catch { + case _: MongoTimeoutException => + false + } +} trait MongoTestKit extends Specification with BeforeAfterEach { sequential @@ -33,46 +52,35 @@ trait MongoTestKit extends Specification with BeforeAfterEach { .replace(".", "_") .toLowerCase - def mongo = new MongoClient("127.0.0.1", 27017) - // If you need more than one db, override this - def dbs: List[(ConnectionIdentifier, String)] = List((DefaultConnectionIdentifier, dbName)) + def dbs: List[(ConnectionIdentifier, String)] = + (DefaultConnectionIdentifier, dbName) :: Nil def debug = false def before = { // define the dbs - dbs foreach { case (id, db) => - MongoDB.defineDb(id, mongo, db) + dbs.foreach { case (id, db) => + MongoDB.defineDb(id, TestMongo.mongo, db) } } - def isMongoRunning: Boolean = - try { - if (dbs.length < 1) - false - else { - dbs foreach { case (id, _) => - MongoDB.use(id) ( db => { db.getCollectionNames} ) - } - true - } - } catch { - case e: Exception => false - } - - def checkMongoIsRunning = isMongoRunning must beEqualTo(true).orSkip + def checkMongoIsRunning = { + TestMongo.isMongoRunning must beEqualTo(true).orSkip + } def after = { - if (!debug && isMongoRunning) { + if (!debug && TestMongo.isMongoRunning) { // drop the databases - dbs foreach { case (id, _) => + dbs.foreach { case (id, _) => MongoDB.use(id) { db => db.dropDatabase } } } // clear the mongo instances - MongoDB.closeAll() + dbs.foreach { case (id, _) => + MongoDB.remove(id) + } } } From 5257d96f533fc9d799311fd39fcf679a77670e94 Mon Sep 17 00:00:00 2001 From: Tim Nelson Date: Tue, 7 Mar 2017 11:17:00 -0600 Subject: [PATCH 1491/1949] Improved MongoAsync.getCollection function --- .../net/liftweb/mongodb/MongoAsync.scala | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/MongoAsync.scala b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/MongoAsync.scala index fbb8012236..0dda3ebd79 100644 --- a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/MongoAsync.scala +++ b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/MongoAsync.scala @@ -95,17 +95,20 @@ object MongoAsync { Option(dbs.get(name)) } - def use[T](ci: ConnectionIdentifier)(f: (MongoDatabase) => T): T = { - val db = getDatabase(ci) match { + /** + * Executes function {@code f} with the mongo database identified by {@code name}. + */ + def use[T](name: ConnectionIdentifier)(f: (MongoDatabase) => T): T = { + val db = getDatabase(name) match { case Some(mongo) => mongo - case _ => throw new MongoException("Mongo not found: "+ci.toString) + case _ => throw new MongoException("Mongo not found: "+name.toString) } f(db) } /** - * Executes function {@code f} with the mongo named {@code name} and collection named {@code collectionName}. - * Gets a collection for you. + * Executes function {@code f} with the collection named {@code collectionName} from + * the mongo database identified by {@code name}. */ def useCollection[T](name: ConnectionIdentifier, collectionName: String)(f: (MongoCollection[Document]) => T): T = { val coll = getCollection(name, collectionName) match { @@ -116,12 +119,8 @@ object MongoAsync { f(coll) } - /** - * Get a Mongo collection. Gets a Mongo db first. - */ - private[this] def getCollection(name: ConnectionIdentifier, collectionName: String): Option[MongoCollection[Document]] = getDatabase(name) match { - case Some(mongo) if mongo != null => Some(mongo.getCollection(collectionName)) - case _ => None + private[this] def getCollection(name: ConnectionIdentifier, collectionName: String): Option[MongoCollection[Document]] = { + getDatabase(name).map(_.getCollection(collectionName)) } /** From a6fb65db0f42de589fa27efef9906c6c2cc2eaed Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Thu, 9 Mar 2017 10:58:18 -0500 Subject: [PATCH 1492/1949] Quick style fix to use private[this]. --- core/json/src/main/scala/net/liftweb/json/Extraction.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/json/src/main/scala/net/liftweb/json/Extraction.scala b/core/json/src/main/scala/net/liftweb/json/Extraction.scala index 5a1d99a4b3..d17da80942 100644 --- a/core/json/src/main/scala/net/liftweb/json/Extraction.scala +++ b/core/json/src/main/scala/net/liftweb/json/Extraction.scala @@ -187,7 +187,7 @@ object Extraction { } } - private def mkMapping(clazz: Class[_], typeArgs: Seq[Class[_]])(implicit formats: Formats): Meta.Mapping = { + private[this] def mkMapping(clazz: Class[_], typeArgs: Seq[Class[_]])(implicit formats: Formats): Meta.Mapping = { if (clazz == classOf[Option[_]] || clazz == classOf[List[_]] || clazz == classOf[Set[_]] || clazz.isArray) { Col(TypeInfo(clazz, None), mkMapping(typeArgs.head, typeArgs.tail)) } else if (clazz == classOf[Map[_, _]]) { From fe10c38c8575e7c8bba85e1a4a4db16e642f33f5 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 18 Mar 2017 10:54:47 -0400 Subject: [PATCH 1493/1949] Address some minor stylistic concerns --- .../scala/net/liftweb/json/Extraction.scala | 17 ++++++++++------- .../src/main/scala/net/liftweb/json/Meta.scala | 6 +++--- .../scala/net/liftweb/json/ExtractionBugs.scala | 2 +- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/core/json/src/main/scala/net/liftweb/json/Extraction.scala b/core/json/src/main/scala/net/liftweb/json/Extraction.scala index d17da80942..6ead4df436 100644 --- a/core/json/src/main/scala/net/liftweb/json/Extraction.scala +++ b/core/json/src/main/scala/net/liftweb/json/Extraction.scala @@ -333,22 +333,25 @@ object Extraction { def newTuple(root: JValue, mappings: List[Mapping]): Any = { root match { - case JArray(items) if items.length >= 1 && items.length <= 22 => + case JArray(items) if items.nonEmpty && items.length <= tuples.length => val builtItems: Seq[Object] = items.zip(mappings).map({ - case (i,m) => - build(i, m).asInstanceOf[Object] + case (item, mapping) => + build(item, mapping).asInstanceOf[Object] }) - val tupleClass = Class.forName("scala.Tuple" + items.length) + val tupleIndex = items.length - 1 + val tupleClass = tuples.lift(tupleIndex).getOrElse { + throw new IllegalArgumentException(s"Cannot instantiate a tuple of length ${items.length} even though that should be a valid tuple length.") + } val typedTupleConstructor = tupleClass.getConstructors()(0) typedTupleConstructor.newInstance(builtItems: _*) - case JObject(items) if items.forall(_.name.startsWith("_")) => - newTuple(JArray(items.map(_.value)), mappings) - case JArray(items) => throw new IllegalArgumentException("Cannot create a tuple of length " + items.length) + case JObject(items) if items.forall(_.name.startsWith("_")) => + newTuple(JArray(items.map(_.value)), mappings) + case x => throw new IllegalArgumentException("Got unexpected while attempting to create tuples: " + x) } diff --git a/core/json/src/main/scala/net/liftweb/json/Meta.scala b/core/json/src/main/scala/net/liftweb/json/Meta.scala index ef1aeb25cc..2d5f5d4427 100644 --- a/core/json/src/main/scala/net/liftweb/json/Meta.scala +++ b/core/json/src/main/scala/net/liftweb/json/Meta.scala @@ -119,7 +119,7 @@ private[json] object Meta { } def mkHeteroContainer(baseType: Type): Mapping = { - val hereroContainerTypes = baseType match { + val heteroContainerTypes = baseType match { case ptype: ParameterizedType => ptype.getActualTypeArguments().map { case c: Class[_] => @@ -131,7 +131,7 @@ private[json] object Meta { } } - val parameters = hereroContainerTypes.map(fieldMapping(_)._1) + val parameters = heteroContainerTypes.map(fieldMapping(_)._1) HCol(TypeInfo(rawClassOf(baseType), parameterizedTypeOpt(baseType)), parameters.toList) } @@ -256,7 +256,7 @@ private[json] object Meta { classOf[java.lang.Short], classOf[Date], classOf[Timestamp], classOf[Symbol], classOf[JValue], classOf[JObject], classOf[JArray]).map((_, ()))) - val tuples = Set( + val tuples = Seq( classOf[Tuple1[_]], classOf[Tuple2[_,_]], classOf[Tuple3[_,_,_]], classOf[Tuple4[_,_,_,_]], classOf[Tuple5[_,_,_,_,_]], classOf[Tuple6[_,_,_,_,_,_]], classOf[Tuple7[_,_,_,_,_,_,_]], classOf[Tuple8[_,_,_,_,_,_,_,_]], diff --git a/core/json/src/test/scala/net/liftweb/json/ExtractionBugs.scala b/core/json/src/test/scala/net/liftweb/json/ExtractionBugs.scala index 0067ca9f1e..7af5efbdd2 100644 --- a/core/json/src/test/scala/net/liftweb/json/ExtractionBugs.scala +++ b/core/json/src/test/scala/net/liftweb/json/ExtractionBugs.scala @@ -109,7 +109,7 @@ object ExtractionBugs extends Specification { deserialized must_== holder } - "deserialize list of heterogenous tuples" in { + "deserialize a list of heterogenous tuples" in { implicit val formats = DefaultFormats // MSF: This currently doesn't work with scala primitives?! The type arguments appear as From 9961491c0bdbf382caae58c77cc51b9830ade0bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Mruk?= Date: Wed, 12 Apr 2017 08:08:19 +0200 Subject: [PATCH 1494/1949] Use proper method to check snippet existence in the snippetClassMap --- web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index 87ce72e020..f474e03a7c 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -180,7 +180,7 @@ object LiftSession { // // We're using ConcurrentHashMap, so no `getOrElseUpdate` here (and // `getOrElseUpdate` isn't atomic anyway). - if (! snippetClassMap.contains(name)) { + if (! snippetClassMap.containsKey(name)) { snippetClassMap.putIfAbsent(name, { // Name might contain some relative packages, so split them out and put them in the proper argument of findClass val (packageSuffix, terminal) = name.lastIndexOf('.') match { From 512f2e6db0848ebea7d6834ddf14a1f964743e3b Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 15 Apr 2017 09:30:30 -0500 Subject: [PATCH 1495/1949] Add issue template and pr template We'll also go ahead and unify the contributing file under the .github folder. --- CONTRIBUTING.md => .github/CONTRIBUTING.md | 0 .github/ISSUE_TEMPLATE.md | 9 +++++++++ .github/PULL_REQUEST_TEMPLATE.md | 5 +++++ README.md | 12 ++++++++++-- 4 files changed, 24 insertions(+), 2 deletions(-) rename CONTRIBUTING.md => .github/CONTRIBUTING.md (100%) create mode 100644 .github/ISSUE_TEMPLATE.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/CONTRIBUTING.md b/.github/CONTRIBUTING.md similarity index 100% rename from CONTRIBUTING.md rename to .github/CONTRIBUTING.md diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000000..e4a68a9e32 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,9 @@ +**[Mailing List](https://groups.google.com/forum/#!forum/liftweb) thread:** REPLACE THIS WITH ML LINK + +**Example project link:** LINK TO EXAMPLE PROJECT IF APPLICABLE + +Provide a description of the issue here including some brief background on the +context in which the issue occurred. There's no need to rehash all of the +background in the Mailing List thread, just summarize the important conclusions +or insights. + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..2575e28298 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,5 @@ +**[Mailing List](https://groups.google.com/forum/#!forum/liftweb) thread**: +REPLACE THIS WITH ML LINK + +Provide a description of the changes this pull request makes including the +background motivation for this change. diff --git a/README.md b/README.md index 56318a2358..b4416b5adf 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,14 @@ Lift applications are: Because Lift applications are written in [Scala](http://www.scala-lang.org), an elegant JVM language, you can still use your favorite Java libraries and deploy to your favorite Servlet Container and app server. Use the code you've already written and deploy to the container you've already configured! +## Issues + +Issues on the Lift GitHub project are intended to describe actionable, +already-discussed items. Committers on the project may open issues for +themselves at any time, but non-committers should visit the [Lift mailing +list](https://groups.google.com/forum/#!forum/liftweb) and start a discussion +for any issue that they wish to open. + ## Pull Requests We will accept pull requests into the [Lift codebase](https://github.com/lift) @@ -20,10 +28,10 @@ if the pull requests meet the following criteria: * The request handles an issue that has been discussed on the [Lift mailing list](http://groups.google.com/forum/#!forum/liftweb) and whose solution has been requested (and in general adheres to the spirit of - the issue guidelines outlined in [CONTRIBUTING.md](https://github.com/lift/framework/blob/master/CONTRIBUTING.md)). + the issue guidelines outlined in [CONTRIBUTING.md](https://github.com/lift/framework/blob/master/.github/CONTRIBUTING.md)). * The request includes a signature at the bottom of the `/contributors.md` file. -For more details, see [CONTRIBUTING.md](https://github.com/lift/framework/blob/master/CONTRIBUTING.md). +For more details, see [CONTRIBUTING.md](https://github.com/lift/framework/blob/master/.github/CONTRIBUTING.md). ## Getting Started From 681616e4a4737e857ed37bb3e6e59dee7770cc7d Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 15 Apr 2017 10:26:49 -0500 Subject: [PATCH 1496/1949] Correctly handle out-of-order old style tuples --- .../main/scala/net/liftweb/json/Extraction.scala | 8 +++++++- .../scala/net/liftweb/json/ExtractionBugs.scala | 13 +++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/core/json/src/main/scala/net/liftweb/json/Extraction.scala b/core/json/src/main/scala/net/liftweb/json/Extraction.scala index 6ead4df436..925ba7979b 100644 --- a/core/json/src/main/scala/net/liftweb/json/Extraction.scala +++ b/core/json/src/main/scala/net/liftweb/json/Extraction.scala @@ -350,7 +350,13 @@ object Extraction { throw new IllegalArgumentException("Cannot create a tuple of length " + items.length) case JObject(items) if items.forall(_.name.startsWith("_")) => - newTuple(JArray(items.map(_.value)), mappings) + val sortedItems = items.sortWith { (i1, i2) => + val numerialName1 = i1.name.drop(1).toInt + val numerialName2 = i2.name.drop(1).toInt + + numerialName1 < numerialName2 + } + newTuple(JArray(sortedItems.map(_.value)), mappings) case x => throw new IllegalArgumentException("Got unexpected while attempting to create tuples: " + x) diff --git a/core/json/src/test/scala/net/liftweb/json/ExtractionBugs.scala b/core/json/src/test/scala/net/liftweb/json/ExtractionBugs.scala index 7af5efbdd2..16185e90cd 100644 --- a/core/json/src/test/scala/net/liftweb/json/ExtractionBugs.scala +++ b/core/json/src/test/scala/net/liftweb/json/ExtractionBugs.scala @@ -122,4 +122,17 @@ object ExtractionBugs extends Specification { val deserialized = parse(serialized).extract[Holder2] deserialized must_== holder } + "deserialize an out of order old-style tuple w/ experimental tuples enabled" in { + implicit val formats = DefaultFormats + + val outOfOrderTuple: JObject = JObject(List( + JField("_1", JString("apple")), + JField("_3", JString("bacon")), + JField("_2", JString("sammich")) + )) + + val extracted = outOfOrderTuple.extract[(String, String, String)] + + extracted must_== ("apple", "sammich", "bacon") + } } From 3b6a4e572b4ec122f35cb436fc26e3fc4f0b0f66 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 15 Apr 2017 10:27:07 -0500 Subject: [PATCH 1497/1949] Add experimentalTupleSupport flag to formats When this is disabled, the new tuple handling code is also disabled and we should generate tuples as serialized objects with keys like _1, _2, _3, etc. When this is enabled, the new tuples handling is active and tuples will be rendered as JSON arrays, providing support for heterogenous collections in JSON, so long as you're willing to avoid using any Scala primitives in your tuples. --- .../scala/net/liftweb/json/Extraction.scala | 6 +-- .../main/scala/net/liftweb/json/Formats.scala | 8 ++++ .../main/scala/net/liftweb/json/Meta.scala | 2 +- .../net/liftweb/json/ExtractionBugs.scala | 39 +++++++++++++++++-- 4 files changed, 48 insertions(+), 7 deletions(-) diff --git a/core/json/src/main/scala/net/liftweb/json/Extraction.scala b/core/json/src/main/scala/net/liftweb/json/Extraction.scala index 925ba7979b..9ee20e5b5c 100644 --- a/core/json/src/main/scala/net/liftweb/json/Extraction.scala +++ b/core/json/src/main/scala/net/liftweb/json/Extraction.scala @@ -83,7 +83,7 @@ object Extraction { case x: Iterable[_] => JArray(x.toList map decompose) case x if (x.getClass.isArray) => JArray(x.asInstanceOf[Array[_]].toList map decompose) case x: Option[_] => x.flatMap[JValue] { y => Some(decompose(y)) }.getOrElse(JNothing) - case x: Product if tuple_?(x.getClass) => + case x: Product if tuple_?(x.getClass) && formats.experimentalTupleSupport => JArray(x.productIterator.toList.map(decompose)) case x => val fields = getDeclaredFields(x.getClass) @@ -192,7 +192,7 @@ object Extraction { Col(TypeInfo(clazz, None), mkMapping(typeArgs.head, typeArgs.tail)) } else if (clazz == classOf[Map[_, _]]) { Dict(mkMapping(typeArgs.tail.head, typeArgs.tail.tail)) - } else if (tuple_?(clazz)) { + } else if (tuple_?(clazz) && formats.experimentalTupleSupport) { val childMappings = typeArgs.map(c => mkMapping(c, Nil)).toList HCol(TypeInfo(clazz, None), childMappings) } else { @@ -376,7 +376,7 @@ object Extraction { case Arg(path, m, optional) => mkValue(root, m, path, optional) - case HCol(targetType, mappings) => + case HCol(targetType, mappings) if formats.experimentalTupleSupport => val c = targetType.clazz if (tuples.find(_.isAssignableFrom(c)).isDefined) { newTuple(root, mappings) diff --git a/core/json/src/main/scala/net/liftweb/json/Formats.scala b/core/json/src/main/scala/net/liftweb/json/Formats.scala index 9951a5e898..b1b862a198 100644 --- a/core/json/src/main/scala/net/liftweb/json/Formats.scala +++ b/core/json/src/main/scala/net/liftweb/json/Formats.scala @@ -31,6 +31,14 @@ trait Formats { self: Formats => val customSerializers: List[Serializer[_]] = Nil val fieldSerializers: List[(Class[_], FieldSerializer[_])] = Nil + /** + * Support for the experimental tuple decomposition/extraction that represents tuples as JSON + * arrays. This provides better support for heterogenous arrays in JSON, but enable it at your + * own risk as it does change the behavior of serialization/deserialization and comes + * with some caveats (such as Scala primitives not being recognized reliably during extraction). + */ + val experimentalTupleSupport = false + /** * The name of the field in JSON where type hints are added (jsonClass by default) */ diff --git a/core/json/src/main/scala/net/liftweb/json/Meta.scala b/core/json/src/main/scala/net/liftweb/json/Meta.scala index 2d5f5d4427..1fa5570d37 100644 --- a/core/json/src/main/scala/net/liftweb/json/Meta.scala +++ b/core/json/src/main/scala/net/liftweb/json/Meta.scala @@ -167,7 +167,7 @@ private[json] object Meta { (mkContainer(t, `(*,*) -> *`, 1, Dict.apply _), false) else if (classOf[Seq[_]].isAssignableFrom(raw)) (mkContainer(t, `* -> *`, 0, Col.apply(info, _)), false) - else if (tuples.find(_.isAssignableFrom(raw)).isDefined) + else if (tuples.find(_.isAssignableFrom(raw)).isDefined && formats.experimentalTupleSupport) (mkHeteroContainer(t), false) else mkConstructor(t) diff --git a/core/json/src/test/scala/net/liftweb/json/ExtractionBugs.scala b/core/json/src/test/scala/net/liftweb/json/ExtractionBugs.scala index 16185e90cd..3bd39a3c1b 100644 --- a/core/json/src/test/scala/net/liftweb/json/ExtractionBugs.scala +++ b/core/json/src/test/scala/net/liftweb/json/ExtractionBugs.scala @@ -97,7 +97,7 @@ object ExtractionBugs extends Specification { } } - "deserialize list of homogonous tuples" in { + "deserialize list of homogonous tuples w/ experimental tuples disabled" in { implicit val formats = DefaultFormats case class Holder(items: List[(String, String)]) @@ -109,7 +109,7 @@ object ExtractionBugs extends Specification { deserialized must_== holder } - "deserialize a list of heterogenous tuples" in { + "deserialize a list of heterogenous tuples w/ experimental tuples disabled" in { implicit val formats = DefaultFormats // MSF: This currently doesn't work with scala primitives?! The type arguments appear as @@ -122,8 +122,41 @@ object ExtractionBugs extends Specification { val deserialized = parse(serialized).extract[Holder2] deserialized must_== holder } + + "deserialize list of homogonous tuples w/ experimental tuples enabled" in { + implicit val formats = new DefaultFormats { + override val experimentalTupleSupport = true + } + + case class Holder(items: List[(String, String)]) + + val holder = Holder(List(("string", "string"))) + val serialized = compactRender(Extraction.decompose(holder)) + + val deserialized = parse(serialized).extract[Holder] + deserialized must_== holder + } + + "deserialize a list of heterogenous tuples w/ experimental tuples enabled" in { + implicit val formats = new DefaultFormats { + override val experimentalTupleSupport = true + } + + // MSF: This currently doesn't work with scala primitives?! The type arguments appear as + // java.lang.Object instead of scala.Int. :/ + case class Holder2(items: List[(String, Integer)]) + + val holder = Holder2(List(("string", 10))) + val serialized = compactRender(Extraction.decompose(holder)) + + val deserialized = parse(serialized).extract[Holder2] + deserialized must_== holder + } + "deserialize an out of order old-style tuple w/ experimental tuples enabled" in { - implicit val formats = DefaultFormats + implicit val formats = new DefaultFormats { + override val experimentalTupleSupport = true + } val outOfOrderTuple: JObject = JObject(List( JField("_1", JString("apple")), From c15c9bec1d7da9147f74973fc1f7e7891ee990b5 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 15 Apr 2017 11:21:18 -0500 Subject: [PATCH 1498/1949] Take the constructor reflection hit for tuples at construction This avoids an extraction time hit on reflection and takes that hit when the extractor is first inited. --- core/json/src/main/scala/net/liftweb/json/Extraction.scala | 5 ++--- core/json/src/main/scala/net/liftweb/json/Meta.scala | 5 +++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/core/json/src/main/scala/net/liftweb/json/Extraction.scala b/core/json/src/main/scala/net/liftweb/json/Extraction.scala index 9ee20e5b5c..a9c7dfb1e0 100644 --- a/core/json/src/main/scala/net/liftweb/json/Extraction.scala +++ b/core/json/src/main/scala/net/liftweb/json/Extraction.scala @@ -339,11 +339,10 @@ object Extraction { build(item, mapping).asInstanceOf[Object] }) val tupleIndex = items.length - 1 - val tupleClass = tuples.lift(tupleIndex).getOrElse { + + val typedTupleConstructor = tupleConstructors.get(tupleIndex).getOrElse { throw new IllegalArgumentException(s"Cannot instantiate a tuple of length ${items.length} even though that should be a valid tuple length.") } - - val typedTupleConstructor = tupleClass.getConstructors()(0) typedTupleConstructor.newInstance(builtItems: _*) case JArray(items) => diff --git a/core/json/src/main/scala/net/liftweb/json/Meta.scala b/core/json/src/main/scala/net/liftweb/json/Meta.scala index 1fa5570d37..5f855775d1 100644 --- a/core/json/src/main/scala/net/liftweb/json/Meta.scala +++ b/core/json/src/main/scala/net/liftweb/json/Meta.scala @@ -272,6 +272,11 @@ private[json] object Meta { classOf[Tuple22[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_]] ) + val tupleConstructors: Map[Int, JConstructor[_]] = tuples.zipWithIndex.map({ + case (tupleClass, index) => + index -> tupleClass.getConstructors()(0) + }).toMap + private val primaryConstructorArgumentsMemo = new Memo[Class[_], List[(String, Type)]] private val declaredFieldsMemo = new Memo[Class[_], Map[String,Field]] From b0fcbd9b726ccf8bd4a494946ccb548361ceb984 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Mruk?= Date: Tue, 18 Apr 2017 07:27:02 +0200 Subject: [PATCH 1499/1949] Updated contributors.md --- contributors.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/contributors.md b/contributors.md index bd3a301604..714048de1d 100644 --- a/contributors.md +++ b/contributors.md @@ -251,3 +251,9 @@ Marek Żebrowski ### Email: ### marek.zebrowski at gmail dot com + +### Name: ### +Paweł Mruk + +### Email: ### +mroocoo at gmail dot com From 8c026bf92ab538cd3779a47a23021e4412697b42 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Wed, 19 Apr 2017 20:45:06 -0400 Subject: [PATCH 1500/1949] Rename experimentalTupleSupport to tuplesAsArrays --- .../main/scala/net/liftweb/json/Extraction.scala | 6 +++--- .../main/scala/net/liftweb/json/Formats.scala | 4 ++-- .../src/main/scala/net/liftweb/json/Meta.scala | 2 +- .../scala/net/liftweb/json/ExtractionBugs.scala | 16 ++++++++-------- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/core/json/src/main/scala/net/liftweb/json/Extraction.scala b/core/json/src/main/scala/net/liftweb/json/Extraction.scala index a9c7dfb1e0..df7a4d2d17 100644 --- a/core/json/src/main/scala/net/liftweb/json/Extraction.scala +++ b/core/json/src/main/scala/net/liftweb/json/Extraction.scala @@ -83,7 +83,7 @@ object Extraction { case x: Iterable[_] => JArray(x.toList map decompose) case x if (x.getClass.isArray) => JArray(x.asInstanceOf[Array[_]].toList map decompose) case x: Option[_] => x.flatMap[JValue] { y => Some(decompose(y)) }.getOrElse(JNothing) - case x: Product if tuple_?(x.getClass) && formats.experimentalTupleSupport => + case x: Product if tuple_?(x.getClass) && formats.tuplesAsArrays => JArray(x.productIterator.toList.map(decompose)) case x => val fields = getDeclaredFields(x.getClass) @@ -192,7 +192,7 @@ object Extraction { Col(TypeInfo(clazz, None), mkMapping(typeArgs.head, typeArgs.tail)) } else if (clazz == classOf[Map[_, _]]) { Dict(mkMapping(typeArgs.tail.head, typeArgs.tail.tail)) - } else if (tuple_?(clazz) && formats.experimentalTupleSupport) { + } else if (tuple_?(clazz) && formats.tuplesAsArrays) { val childMappings = typeArgs.map(c => mkMapping(c, Nil)).toList HCol(TypeInfo(clazz, None), childMappings) } else { @@ -375,7 +375,7 @@ object Extraction { case Arg(path, m, optional) => mkValue(root, m, path, optional) - case HCol(targetType, mappings) if formats.experimentalTupleSupport => + case HCol(targetType, mappings) if formats.tuplesAsArrays => val c = targetType.clazz if (tuples.find(_.isAssignableFrom(c)).isDefined) { newTuple(root, mappings) diff --git a/core/json/src/main/scala/net/liftweb/json/Formats.scala b/core/json/src/main/scala/net/liftweb/json/Formats.scala index b1b862a198..d75b7250cf 100644 --- a/core/json/src/main/scala/net/liftweb/json/Formats.scala +++ b/core/json/src/main/scala/net/liftweb/json/Formats.scala @@ -32,12 +32,12 @@ trait Formats { self: Formats => val fieldSerializers: List[(Class[_], FieldSerializer[_])] = Nil /** - * Support for the experimental tuple decomposition/extraction that represents tuples as JSON + * Support for the tuple decomposition/extraction that represents tuples as JSON * arrays. This provides better support for heterogenous arrays in JSON, but enable it at your * own risk as it does change the behavior of serialization/deserialization and comes * with some caveats (such as Scala primitives not being recognized reliably during extraction). */ - val experimentalTupleSupport = false + val tuplesAsArrays = false /** * The name of the field in JSON where type hints are added (jsonClass by default) diff --git a/core/json/src/main/scala/net/liftweb/json/Meta.scala b/core/json/src/main/scala/net/liftweb/json/Meta.scala index 5f855775d1..7161f14c9f 100644 --- a/core/json/src/main/scala/net/liftweb/json/Meta.scala +++ b/core/json/src/main/scala/net/liftweb/json/Meta.scala @@ -167,7 +167,7 @@ private[json] object Meta { (mkContainer(t, `(*,*) -> *`, 1, Dict.apply _), false) else if (classOf[Seq[_]].isAssignableFrom(raw)) (mkContainer(t, `* -> *`, 0, Col.apply(info, _)), false) - else if (tuples.find(_.isAssignableFrom(raw)).isDefined && formats.experimentalTupleSupport) + else if (tuples.find(_.isAssignableFrom(raw)).isDefined && formats.tuplesAsArrays) (mkHeteroContainer(t), false) else mkConstructor(t) diff --git a/core/json/src/test/scala/net/liftweb/json/ExtractionBugs.scala b/core/json/src/test/scala/net/liftweb/json/ExtractionBugs.scala index 3bd39a3c1b..ea28d18381 100644 --- a/core/json/src/test/scala/net/liftweb/json/ExtractionBugs.scala +++ b/core/json/src/test/scala/net/liftweb/json/ExtractionBugs.scala @@ -97,7 +97,7 @@ object ExtractionBugs extends Specification { } } - "deserialize list of homogonous tuples w/ experimental tuples disabled" in { + "deserialize list of homogonous tuples w/ array tuples disabled" in { implicit val formats = DefaultFormats case class Holder(items: List[(String, String)]) @@ -109,7 +109,7 @@ object ExtractionBugs extends Specification { deserialized must_== holder } - "deserialize a list of heterogenous tuples w/ experimental tuples disabled" in { + "deserialize a list of heterogenous tuples w/ array tuples disabled" in { implicit val formats = DefaultFormats // MSF: This currently doesn't work with scala primitives?! The type arguments appear as @@ -123,9 +123,9 @@ object ExtractionBugs extends Specification { deserialized must_== holder } - "deserialize list of homogonous tuples w/ experimental tuples enabled" in { + "deserialize list of homogonous tuples w/ array tuples enabled" in { implicit val formats = new DefaultFormats { - override val experimentalTupleSupport = true + override val tuplesAsArrays = true } case class Holder(items: List[(String, String)]) @@ -137,9 +137,9 @@ object ExtractionBugs extends Specification { deserialized must_== holder } - "deserialize a list of heterogenous tuples w/ experimental tuples enabled" in { + "deserialize a list of heterogenous tuples w/ array tuples enabled" in { implicit val formats = new DefaultFormats { - override val experimentalTupleSupport = true + override val tuplesAsArrays = true } // MSF: This currently doesn't work with scala primitives?! The type arguments appear as @@ -153,9 +153,9 @@ object ExtractionBugs extends Specification { deserialized must_== holder } - "deserialize an out of order old-style tuple w/ experimental tuples enabled" in { + "deserialize an out of order old-style tuple w/ array tuples enabled" in { implicit val formats = new DefaultFormats { - override val experimentalTupleSupport = true + override val tuplesAsArrays = true } val outOfOrderTuple: JObject = JObject(List( From 9ebf73aa438bb8544c9e542b80a956e3e14571ac Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Wed, 19 Apr 2017 21:05:05 -0400 Subject: [PATCH 1501/1949] Update readme for new tuple implementation --- core/json/README.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/core/json/README.md b/core/json/README.md index 93b13bb86b..4dacd58c5b 100644 --- a/core/json/README.md +++ b/core/json/README.md @@ -530,6 +530,7 @@ Serialization supports: * scala.Option * java.util.Date * Polymorphic Lists (see below) +* Tuples (see below) * Recursive types * Serialization of fields of a class (see below) * Custom serializer functions for types which are not supported (see below) @@ -554,6 +555,37 @@ will get an extra field named 'jsonClass' (the name can be changed by overriding ShortTypeHints outputs short classname for all instances of configured objects. FullTypeHints outputs full classname. Other strategies can be implemented by extending TypeHints trait. +Serializing Tuples +----------------------------- + +If you need to have a heterogeneous collection of completely unrelated types, you might find tuples +useful for representing that data structure. By default, when you serialize a tuple you'll get an +object based on the constructor of the appropreate tuple class. So, for example, if you serialize +a `("bacon", 2)`, you'll get: + + {"_1": "bacon", "_2": 2} + +However, as of Lift 3.1, you can enable `tuplesAsArrays` to represent these tuples as heterogeneous +JSON arrays. To enable this feature you just need to provide a formats object that turns on the +feature. + + scala> import net.liftweb.json._ + scala> import Serialization._ + scala> implicit val formats = new DefaultFormats { override val tuplesAsArrays = true } + scala> write(("bacon", 2)) + res1: String = ["bacon",2] + +When this feature is enabled: + +* lift-json will write tuples as arrays. +* lift-json will correctly extract tuples from arrays +* lift-json will continue to deserialize tuples that were serialied in the old manner, as an object + +The major limitation to this feature is that it doesn't reliably support Scala primitives, so it is +currently disabled by default. If you're using this feature, you should use the Java boxed types +in your class signatures instead of the Scala primitive types. (So, `java.lang.Integer` instead +of `scala.Int`.) + Serializing fields of a class ----------------------------- From 0e1076d03f2bf5e4e9b75082a5676be8abd8e7d3 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Thu, 20 Apr 2017 09:34:49 -0400 Subject: [PATCH 1502/1949] Rename .github/CONTRIBUTING.md to CONTRIBUTING.md --- .github/CONTRIBUTING.md => CONTRIBUTING.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/CONTRIBUTING.md => CONTRIBUTING.md (100%) diff --git a/.github/CONTRIBUTING.md b/CONTRIBUTING.md similarity index 100% rename from .github/CONTRIBUTING.md rename to CONTRIBUTING.md From 6624bf28d597487aa833bd2094528215e04b9f3c Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Thu, 20 Apr 2017 09:35:45 -0400 Subject: [PATCH 1503/1949] Update contributing links for new (old) location --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b4416b5adf..f375e7c48d 100644 --- a/README.md +++ b/README.md @@ -28,10 +28,10 @@ if the pull requests meet the following criteria: * The request handles an issue that has been discussed on the [Lift mailing list](http://groups.google.com/forum/#!forum/liftweb) and whose solution has been requested (and in general adheres to the spirit of - the issue guidelines outlined in [CONTRIBUTING.md](https://github.com/lift/framework/blob/master/.github/CONTRIBUTING.md)). + the issue guidelines outlined in [CONTRIBUTING.md](https://github.com/lift/framework/blob/master/CONTRIBUTING.md)). * The request includes a signature at the bottom of the `/contributors.md` file. -For more details, see [CONTRIBUTING.md](https://github.com/lift/framework/blob/master/.github/CONTRIBUTING.md). +For more details, see [CONTRIBUTING.md](https://github.com/lift/framework/blob/master/CONTRIBUTING.md). ## Getting Started From f03546f5b55a9f47fb81e5a085333bcb51e2ba0e Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Thu, 20 Apr 2017 09:38:16 -0400 Subject: [PATCH 1504/1949] Add link to assembla content about example projects --- .github/ISSUE_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index e4a68a9e32..27c0f6338e 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,6 +1,6 @@ **[Mailing List](https://groups.google.com/forum/#!forum/liftweb) thread:** REPLACE THIS WITH ML LINK -**Example project link:** LINK TO EXAMPLE PROJECT IF APPLICABLE +**[Example project](https://app.assembla.com/wiki/show/liftweb/Posting_example_code) link:** LINK TO EXAMPLE PROJECT IF APPLICABLE Provide a description of the issue here including some brief background on the context in which the issue occurred. There's no need to rehash all of the From d6aecdf53ad37fb3f8fa100d69c96ef736c8f98e Mon Sep 17 00:00:00 2001 From: Tim Nelson Date: Thu, 20 Apr 2017 10:50:33 -0500 Subject: [PATCH 1505/1949] Update usage of FileInput/OutputStream --- .../scala/net/liftweb/markdown/TimeTest.scala | 9 ++++---- .../net/liftweb/util/BasicTypesHelpers.scala | 4 ++-- .../scala/net/liftweb/util/IoHelpers.scala | 6 +++-- .../mocks/MockHttpServletResponse.scala | 1 - .../liftweb/mocks/MockServletContext.scala | 13 +++++------ .../src/main/scala/net/liftweb/http/Req.scala | 22 +++++++++++-------- 6 files changed, 30 insertions(+), 25 deletions(-) diff --git a/core/markdown/src/main/scala/net/liftweb/markdown/TimeTest.scala b/core/markdown/src/main/scala/net/liftweb/markdown/TimeTest.scala index 153dc806ac..9a010d93f7 100644 --- a/core/markdown/src/main/scala/net/liftweb/markdown/TimeTest.scala +++ b/core/markdown/src/main/scala/net/liftweb/markdown/TimeTest.scala @@ -1,7 +1,7 @@ package net.liftweb.markdown /* - * Copyright 2013 WorldWide Conferencing, LLC + * Copyright 2013-2017 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. @@ -19,7 +19,8 @@ package net.liftweb.markdown * Christoph Henkelmann http://henkelmann.eu/ */ -import java.io.{InputStreamReader, FileInputStream, StringWriter} +import java.io.{InputStreamReader, StringWriter} +import java.nio.file.{Files, Paths} /** * Quick and dirty test for measuring the time of this Parser. @@ -62,7 +63,7 @@ object TimeTest { private def readFile(path:String):String = { //read from system input stream - val reader = new InputStreamReader(new FileInputStream(path)) + val reader = new InputStreamReader(Files.newInputStream(Paths.get(path))) val writer = new StringWriter() val buffer = new Array[Char](1024) var read = reader.read(buffer) @@ -134,4 +135,4 @@ object TimeTest { //runWs runActuarius } -} \ No newline at end of file +} diff --git a/core/util/src/main/scala/net/liftweb/util/BasicTypesHelpers.scala b/core/util/src/main/scala/net/liftweb/util/BasicTypesHelpers.scala index b8ec6c00f2..a06bd564b8 100644 --- a/core/util/src/main/scala/net/liftweb/util/BasicTypesHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/BasicTypesHelpers.scala @@ -1,5 +1,5 @@ /* - * Copyright 2006-2011 WorldWide Conferencing, LLC + * Copyright 2006-2017 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. @@ -17,7 +17,7 @@ package net.liftweb package util -import java.io.{InputStream, ByteArrayOutputStream, ByteArrayInputStream, Reader, File, FileInputStream, BufferedReader} +import java.io.{InputStream, ByteArrayOutputStream, ByteArrayInputStream, Reader, BufferedReader} import scala.util.Try import scala.xml._ import common._ diff --git a/core/util/src/main/scala/net/liftweb/util/IoHelpers.scala b/core/util/src/main/scala/net/liftweb/util/IoHelpers.scala index aa104aacaa..3526787b9c 100644 --- a/core/util/src/main/scala/net/liftweb/util/IoHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/IoHelpers.scala @@ -1,5 +1,5 @@ /* - * Copyright 2006-2011 WorldWide Conferencing, LLC + * Copyright 2006-2017 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. @@ -18,6 +18,7 @@ package net.liftweb package util import java.io._ +import java.nio.file.{Files, Path} import scala.collection.mutable.ListBuffer import ControlHelpers._ import common._ @@ -85,7 +86,8 @@ trait IoHelpers { /** * Read an entire file into an Array[Byte] */ - def readWholeFile(file: File): Array[Byte] = readWholeStream(new FileInputStream(file)) + def readWholeFile(file: File): Array[Byte] = readWholeStream(Files.newInputStream(file.toPath)) + def readWholeFile(path: Path): Array[Byte] = readWholeStream(Files.newInputStream(path)) /** * Read all data from a stream into an Array[Byte] diff --git a/web/testkit/src/main/scala/net/liftweb/mocks/MockHttpServletResponse.scala b/web/testkit/src/main/scala/net/liftweb/mocks/MockHttpServletResponse.scala index b2f54fd416..10dd2a7b29 100644 --- a/web/testkit/src/main/scala/net/liftweb/mocks/MockHttpServletResponse.scala +++ b/web/testkit/src/main/scala/net/liftweb/mocks/MockHttpServletResponse.scala @@ -24,7 +24,6 @@ import java.io.StringReader import java.io.BufferedReader import java.io.ByteArrayOutputStream import java.io.ByteArrayInputStream -import java.io.FileInputStream import java.io.InputStream import java.io.StringBufferInputStream import java.io.File diff --git a/web/testkit/src/main/scala/net/liftweb/mocks/MockServletContext.scala b/web/testkit/src/main/scala/net/liftweb/mocks/MockServletContext.scala index 9c1c72f760..a839b02073 100644 --- a/web/testkit/src/main/scala/net/liftweb/mocks/MockServletContext.scala +++ b/web/testkit/src/main/scala/net/liftweb/mocks/MockServletContext.scala @@ -1,5 +1,5 @@ /* - * Copyright 2008-2011 WorldWide Conferencing, LLC + * Copyright 2008-2017 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. @@ -27,10 +27,9 @@ import java.io.StringReader import java.io.BufferedReader import java.io.ByteArrayOutputStream import java.io.ByteArrayInputStream -import java.io.FileInputStream import java.io.InputStream import java.io.StringBufferInputStream -import java.io.File +import java.nio.file.{Files, Paths} import java.util.Arrays import java.util.Date import java.util.Locale @@ -83,9 +82,9 @@ class MockServletContext(var target: String) extends ServletContext { def getRequestDispatcher(path: String): RequestDispatcher = null def getResource(path: String): java.net.URL = null def getResourceAsStream(path: String): java.io.InputStream = { - val file = new File(target + path) - if (file.exists) { - new FileInputStream(file) + val file = Paths.get(target + path) + if (Files.exists(file)) { + Files.newInputStream(file) } else { null } @@ -120,7 +119,7 @@ class MockServletContext(var target: String) extends ServletContext { def addServlet(servletName: String, servlet: javax.servlet.Servlet): ServletRegistration.Dynamic = null def addServlet(servletName: String, servletClass: String): ServletRegistration.Dynamic = null - // This remain unimplemented since we can't provide a Null here due toe type restrictions. + // This remains unimplemented since we can't provide a Null here due to type restrictions. def createFilter[T <: javax.servlet.Filter](filter: Class[T]): T = ??? def createListener[T <: java.util.EventListener](listener: Class[T]): T = ??? def createServlet[T <: javax.servlet.Servlet](servletClass: Class[T]): T = ??? diff --git a/web/webkit/src/main/scala/net/liftweb/http/Req.scala b/web/webkit/src/main/scala/net/liftweb/http/Req.scala index 0dc540cf88..3a818a17a0 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Req.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Req.scala @@ -1,5 +1,5 @@ /* - * Copyright 2007-2011 WorldWide Conferencing, LLC + * Copyright 2007-2017 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. @@ -17,8 +17,8 @@ package net.liftweb package http -import java.io.{InputStream, ByteArrayInputStream, File, FileInputStream, - FileOutputStream} +import java.io.{InputStream, ByteArrayInputStream, File} +import java.nio.file.{Files, Path} import scala.xml._ import common._ @@ -284,14 +284,14 @@ FileParamHolder(name, mimeType, fileName) * @param localFile The local copy of the uploaded file */ class OnDiskFileParamHolder(override val name: String, override val mimeType: String, - override val fileName: String, val localFile: File) extends + override val fileName: String, val localFile: Path) extends FileParamHolder(name, mimeType, fileName) { /** * Returns an input stream that can be used to read the * contents of the uploaded file. */ - def fileStream: InputStream = new FileInputStream(localFile) + def fileStream: InputStream = Files.newInputStream(localFile) /** * Returns the contents of the uploaded file as a Byte array. @@ -301,18 +301,18 @@ FileParamHolder(name, mimeType, fileName) /** * Returns the length of the uploaded file. */ - def length : Long = if (localFile == null) 0 else localFile.length + def length : Long = if (localFile == null) 0 else Files.size(localFile) protected override def finalize { - tryo(localFile.delete) + tryo(Files.delete(localFile)) } } object OnDiskFileParamHolder { def apply(n: String, mt: String, fn: String, inputStream: InputStream): OnDiskFileParamHolder = { - val file: File = File.createTempFile("lift_mime", "upload") - val fos = new FileOutputStream(file) + val file: Path = Files.createTempFile("lift_mime", "upload") + val fos = Files.newOutputStream(file) val ba = new Array[Byte](8192) def doUpload() { inputStream.read(ba) match { @@ -328,6 +328,10 @@ object OnDiskFileParamHolder { fos.close new OnDiskFileParamHolder(n, mt, fn, file) } + + def apply(n: String, mt: String, fn: String, file: File): OnDiskFileParamHolder = + new OnDiskFileParamHolder(n, mt, fn, file.toPath) + } object FileParamHolder { From 393f55763cbdf5da1df688605e18116c2993ddb6 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Thu, 20 Apr 2017 22:08:24 -0400 Subject: [PATCH 1506/1949] Remove deprecated rendering methods --- .../main/scala/net/liftweb/json/JsonAST.scala | 39 ------------------- .../main/scala/net/liftweb/json/package.scala | 11 +----- 2 files changed, 1 insertion(+), 49 deletions(-) diff --git a/core/json/src/main/scala/net/liftweb/json/JsonAST.scala b/core/json/src/main/scala/net/liftweb/json/JsonAST.scala index 33aa861d36..5390225f1f 100644 --- a/core/json/src/main/scala/net/liftweb/json/JsonAST.scala +++ b/core/json/src/main/scala/net/liftweb/json/JsonAST.scala @@ -1111,42 +1111,3 @@ trait JsonDSL extends Implicits { def ~(right: JObject) = JObject(left ::: right.obj) } } - -/** Printer converts JSON to String. - * Before printing a JValue needs to be rendered into scala.text.Document. - *

        - * Example:

        -  * pretty(render(json))
        -  * 
        - */ -@deprecated("Please switch using JsonAST's render methods instead of relying on Printer.", "3.0") -object Printer extends Printer -@deprecated("Please switch using JsonAST's render methods instead of relying on Printer.", "3.0") -trait Printer { - /** Compact printing (no whitespace etc.) - */ - @deprecated("Please switch to using compactRender instead.", "3.0") - def compact(d: JsonAST.RenderIntermediaryDocument): String = JsonAST.compactRender(d.value) - - /** Compact printing (no whitespace etc.) - */ - @deprecated("Please switch to using compactRender instead.", "3.0") - def compact[A <: Writer](d: JsonAST.RenderIntermediaryDocument, out: A): A = { - JsonAST.compactRender(d.value, out) - out - } - - /** Pretty printing. - */ - @deprecated("Please switch to using prettyRender instead.", "3.0") - def pretty(d: JsonAST.RenderIntermediaryDocument): String = - JsonAST.prettyRender(d.value) - - /** Pretty printing. - */ - @deprecated("Please switch to using prettyRender instead.", "3.0") - def pretty[A <: Writer](d: JsonAST.RenderIntermediaryDocument, out: A): A = { - JsonAST.prettyRender(d.value, out) - out - } -} diff --git a/core/json/src/main/scala/net/liftweb/json/package.scala b/core/json/src/main/scala/net/liftweb/json/package.scala index 6ca85a488a..cbb905d072 100644 --- a/core/json/src/main/scala/net/liftweb/json/package.scala +++ b/core/json/src/main/scala/net/liftweb/json/package.scala @@ -14,7 +14,7 @@ * limitations under the License. */ -package net.liftweb +package net.liftweb package object json { import java.io.Reader @@ -40,15 +40,6 @@ package object json { def parse(s: String): JValue = JsonParser.parse(s) def parseOpt(s: String): Option[JValue] = JsonParser.parseOpt(s) - @deprecated("Please switch to using prettyRender or compactRender instead.", "3.0") - def render(value: JValue): JsonAST.RenderIntermediaryDocument = JsonAST.render(value) - - @deprecated("Please switch to using compactRender instead.", "3.0") - def compact(d: JsonAST.RenderIntermediaryDocument): String = Printer.compact(d) - - @deprecated("Please switch to using prettyRender instead.", "3.0") - def pretty(d: JsonAST.RenderIntermediaryDocument): String = Printer.pretty(d) - def prettyRender(value: JValue): String = JsonAST.prettyRender(value) def compactRender(value: JValue): String = JsonAST.compactRender(value) } From 4a2447de52f811703e9e910b57712be1a3a18518 Mon Sep 17 00:00:00 2001 From: Tim Nelson Date: Fri, 21 Apr 2017 07:45:17 -0500 Subject: [PATCH 1507/1949] Copied scaladoc to second readWholeFile function --- core/util/src/main/scala/net/liftweb/util/IoHelpers.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/util/src/main/scala/net/liftweb/util/IoHelpers.scala b/core/util/src/main/scala/net/liftweb/util/IoHelpers.scala index 3526787b9c..406751bb9a 100644 --- a/core/util/src/main/scala/net/liftweb/util/IoHelpers.scala +++ b/core/util/src/main/scala/net/liftweb/util/IoHelpers.scala @@ -87,6 +87,10 @@ trait IoHelpers { * Read an entire file into an Array[Byte] */ def readWholeFile(file: File): Array[Byte] = readWholeStream(Files.newInputStream(file.toPath)) + + /** + * Read an entire file into an Array[Byte] + */ def readWholeFile(path: Path): Array[Byte] = readWholeStream(Files.newInputStream(path)) /** @@ -110,7 +114,7 @@ trait IoHelpers { } /** - * Executes by-name function f and then closes the Cloaseables parameters + * Executes by-name function f and then closes the Closeables parameters */ def doClose[T](is: java.io.Closeable*)(f : => T): T = { try { From ad23be02df619b479d22494d0d431c651ca467ed Mon Sep 17 00:00:00 2001 From: Tim Nelson Date: Fri, 21 Apr 2017 10:34:15 -0500 Subject: [PATCH 1508/1949] Removed requirement of jQuery in liftJQuery definition --- web/webkit/src/main/resources/toserve/lift.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/web/webkit/src/main/resources/toserve/lift.js b/web/webkit/src/main/resources/toserve/lift.js index 1785064a9a..20bf0b1c1a 100644 --- a/web/webkit/src/main/resources/toserve/lift.js +++ b/web/webkit/src/main/resources/toserve/lift.js @@ -507,7 +507,7 @@ return self; }; - + self['catch'] = self.fail; self.done = function(f) { @@ -655,7 +655,9 @@ jQuery(elementOrId).on(eventName, fn); }, - onDocumentReady: jQuery(document).ready, + onDocumentReady: function(fn) { + jQuery(document).ready(fn); + }, ajaxPost: function(url, data, dataType, onSuccess, onFailure) { var processData = true, contentType = 'application/x-www-form-urlencoded; charset=UTF-8'; From 24f494b33db54bc3ac10447482d4f3c273342a7f Mon Sep 17 00:00:00 2001 From: Tim Nelson Date: Sat, 22 Apr 2017 07:40:41 -0500 Subject: [PATCH 1509/1949] Fixed imports in mongo-record --- .../net/liftweb/mongodb/record/field/BsonRecordField.scala | 3 +-- .../net/liftweb/mongodb/record/field/JsonObjectField.scala | 5 ++--- .../liftweb/mongodb/record/field/MongoFieldFlavor.scala | 5 ++--- .../net/liftweb/mongodb/record/field/MongoMapField.scala | 7 +++---- .../net/liftweb/mongodb/record/field/ObjectIdField.scala | 5 ++--- .../net/liftweb/mongodb/record/field/PatternField.scala | 5 ++--- .../scala/net/liftweb/mongodb/record/field/UUIDField.scala | 5 ++--- 7 files changed, 14 insertions(+), 21 deletions(-) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala index 5ec8ec45d9..69d1e0d594 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala @@ -22,8 +22,7 @@ package field import common._ import http.js.JsExp import http.js.JE.JsNull -import json.JsonAST._ -import json.Printer +import json._ import net.liftweb.record._ import com.mongodb._ import org.bson.Document diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JsonObjectField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JsonObjectField.scala index 5961849d7d..edd15000fe 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JsonObjectField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JsonObjectField.scala @@ -1,5 +1,5 @@ /* -* Copyright 2010-2012 WorldWide Conferencing, LLC +* Copyright 2010-2017 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. @@ -19,8 +19,7 @@ package field import scala.xml.{NodeSeq, Text} import net.liftweb.common.{Box, Empty, Failure, Full} import net.liftweb.http.js.JE.{JsNull, Str} -import net.liftweb.json.JsonAST._ -import net.liftweb.json.{JsonParser, Printer} +import net.liftweb.json._ import net.liftweb.record.{Field, FieldHelpers, MandatoryTypedField, Record} import net.liftweb.util.Helpers.tryo import com.mongodb.DBObject diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoFieldFlavor.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoFieldFlavor.scala index 602d249465..8fe0fa6908 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoFieldFlavor.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoFieldFlavor.scala @@ -1,5 +1,5 @@ /* - * Copyright 2010-2011 WorldWide Conferencing, LLC + * Copyright 2010-2017 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. @@ -21,8 +21,7 @@ package field import net.liftweb.common.{Box, Empty, Failure, Full} import net.liftweb.http.js.JE.{JsNull, JsRaw} -import net.liftweb.json.Printer -import net.liftweb.json.JsonAST._ +import net.liftweb.json._ import com.mongodb.DBObject /** diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoMapField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoMapField.scala index ab44be8255..576017c4d1 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoMapField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoMapField.scala @@ -1,5 +1,5 @@ /* - * Copyright 2010-2016 WorldWide Conferencing, LLC + * Copyright 2010-2017 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. @@ -23,8 +23,7 @@ import scala.xml.NodeSeq import net.liftweb.common.{Box, Empty, Failure, Full} import net.liftweb.http.js.JE.{JsNull, JsRaw} -import net.liftweb.json.JsonAST._ -import net.liftweb.json.{JsonParser, Printer} +import net.liftweb.json._ import net.liftweb.record._ import net.liftweb.util.Helpers.tryo @@ -38,7 +37,7 @@ class MongoMapField[OwnerType <: BsonRecord[OwnerType], MapValueType](rec: Owner extends Field[Map[String, MapValueType], OwnerType] with MandatoryTypedField[Map[String, MapValueType]] with MongoFieldFlavor[Map[String, MapValueType]] { - import Meta.Reflection._ + import mongodb.Meta.Reflection._ def owner = rec diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/ObjectIdField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/ObjectIdField.scala index 3570c92030..9966582f21 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/ObjectIdField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/ObjectIdField.scala @@ -1,5 +1,5 @@ /* - * Copyright 2010-2014 WorldWide Conferencing, LLC + * Copyright 2010-2017 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. @@ -26,8 +26,7 @@ import java.util.Date import net.liftweb.common.{Box, Empty, Failure, Full} import net.liftweb.http.js.JE.{JsNull, JsObj, JsRaw, Str} import net.liftweb.http.S -import net.liftweb.json.JsonAST._ -import net.liftweb.json.Printer +import net.liftweb.json._ import net.liftweb.record.{Field, FieldHelpers, MandatoryTypedField, Record} import net.liftweb.util.Helpers._ diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/PatternField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/PatternField.scala index ebe11ebb04..f3a34130da 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/PatternField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/PatternField.scala @@ -1,5 +1,5 @@ /* -* Copyright 2010-2014 WorldWide Conferencing, LLC +* Copyright 2010-2017 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. @@ -21,8 +21,7 @@ import scala.xml.NodeSeq import net.liftweb.common.{Box, Empty, Failure, Full} import net.liftweb.http.js.JE.{JsNull, Str} -import net.liftweb.json.JsonAST._ -import net.liftweb.json.{JsonParser, Printer} +import net.liftweb.json._ import net.liftweb.mongodb.record._ import net.liftweb.record.{Field, FieldHelpers, MandatoryTypedField} import net.liftweb.util.Helpers.tryo diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/UUIDField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/UUIDField.scala index 153b6da464..da646357b0 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/UUIDField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/UUIDField.scala @@ -1,5 +1,5 @@ /* -* Copyright 2010-2014 WorldWide Conferencing, LLC +* Copyright 2010-2017 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. @@ -23,8 +23,7 @@ import scala.xml.NodeSeq import net.liftweb.common.{Box, Empty, Failure, Full} import net.liftweb.http.js.JE.{JsNull, JsRaw} import net.liftweb.http.S -import net.liftweb.json.JsonAST._ -import net.liftweb.json.Printer +import net.liftweb.json._ import net.liftweb.mongodb.record._ import net.liftweb.record.{Field, FieldHelpers, MandatoryTypedField} import net.liftweb.util.Helpers._ From 6a8afad294021ee5ad78fa238035c6422ad352a7 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 23 Apr 2017 20:07:32 -0400 Subject: [PATCH 1510/1949] Add spec demonstrating page script attachment. Also add a failing spec for attaching the page script in cases where we're dealing with a page with no head/body tags. --- .../net/liftweb/http/LiftMergeSpec.scala | 81 ++++++++++++++++--- 1 file changed, 72 insertions(+), 9 deletions(-) diff --git a/web/webkit/src/test/scala/net/liftweb/http/LiftMergeSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/LiftMergeSpec.scala index a73ed6d9df..3f111e092b 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/LiftMergeSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/LiftMergeSpec.scala @@ -14,6 +14,7 @@ import org.mockito.Mockito._ import common._ import js.JE.JsObj +import js.pageScript trait BaseAround extends Around { override def around[T: AsResult](test: =>T): Result = { @@ -62,11 +63,16 @@ class LiftMergeSpec extends Specification with XmlMatchers with Mockito { testRules.autoIncludeAjaxCalc.default.set(() => () => (_: LiftSession) => false) testRules.excludePathFromContextPathRewriting.default .set( - () => { in: String => + () => { in: String => in.startsWith("exclude-me") } ) + val eventExtractingTestRules = new LiftRules() + eventExtractingTestRules.javaScriptSettings.default.set(() => () => Empty) + eventExtractingTestRules.autoIncludeAjaxCalc.default.set(() => () => (_: LiftSession) => false) + eventExtractingTestRules.extractInlineJavaScript = true + "LiftMerge when doing the final page merge" should { "merge head segments in the page body in order into main head" in new WithRules(testRules) { val result = @@ -198,7 +204,7 @@ class LiftMergeSpec extends Specification with XmlMatchers with Mockito { mockReq ) - (result \\ "link").map(_ \@ "href") must_== + (result \\ "link").map(_ \@ "href") must_== "/context-path/testlink" :: "/context-path/testlink2" :: "/context-path/testlink3" :: Nil @@ -232,7 +238,7 @@ class LiftMergeSpec extends Specification with XmlMatchers with Mockito { mockReq ) - (result \\ "script").map(_ \@ "src") must_== + (result \\ "script").map(_ \@ "src") must_== "/context-path/testscript" :: "/context-path/testscript2" :: Nil } @@ -265,7 +271,7 @@ class LiftMergeSpec extends Specification with XmlMatchers with Mockito { mockReq ) - (result \\ "a").map(_ \@ "href") must_== + (result \\ "a").map(_ \@ "href") must_== "/context-path/testa1" :: "testa3" :: "/context-path/testa2" :: @@ -302,7 +308,7 @@ class LiftMergeSpec extends Specification with XmlMatchers with Mockito { mockReq ) - (result \\ "form").map(_ \@ "action") must_== + (result \\ "form").map(_ \@ "action") must_== "/context-path/testform1" :: "testform3" :: "/context-path/testform2" :: @@ -339,7 +345,7 @@ class LiftMergeSpec extends Specification with XmlMatchers with Mockito { ) } - (result \\ "script").map(_ \@ "src") must_== + (result \\ "script").map(_ \@ "src") must_== "testscript" :: "testscript2" :: "testscript3" :: Nil @@ -373,7 +379,7 @@ class LiftMergeSpec extends Specification with XmlMatchers with Mockito { ) } - (result \\ "link").map(_ \@ "href") must_== + (result \\ "link").map(_ \@ "href") must_== "testlink" :: "testlink2" :: "testlink3" :: Nil @@ -407,7 +413,7 @@ class LiftMergeSpec extends Specification with XmlMatchers with Mockito { ) } - (result \\ "a").map(_ \@ "href") must_== + (result \\ "a").map(_ \@ "href") must_== "rewritten" :: "rewritten" :: "rewritten" :: Nil @@ -441,10 +447,67 @@ class LiftMergeSpec extends Specification with XmlMatchers with Mockito { ) } - (result \\ "form").map(_ \@ "action") must_== + (result \\ "form").map(_ \@ "action") must_== "rewritten" :: "rewritten" :: "rewritten" :: Nil } + + "include a page script in the page tail if events are extracted" in new WithLiftContext(eventExtractingTestRules, testSession) { + val result = + testSession.merge( + + + Booyan + + +
        +

        + Test +

        +
        + + , + mockReq + ) + + val scripts = (result \\ "script") + + scripts must have length(1) + scripts.map(_ \@ "src") must beLike { + case scriptSrc :: Nil => + scriptSrc must beMatching("/context-path/lift/page/F[^.]+.js") + } + pageScript.is must beLike { + case Full(response) => + response.js.toJsCmd must contain("tryme()") + response.js.toJsCmd must contain("tryyou()") + } + } + + "include a page script in the page tail even if the page doesn't have a head and body" in new WithLiftContext(eventExtractingTestRules, testSession) { + val result = + testSession.merge( +
        +

        + Test +

        +
        , + mockReq + ) + + val scripts = (result \\ "script") + + scripts must have length(1) + scripts.map(_ \@ "src") must beLike { + case scriptSrc :: Nil => + scriptSrc must beMatching("/context-path/lift/page/F[^.]+.js") + } + pageScript.is must beLike { + case Full(response) => + response.js.toJsCmd must contain("tryme()") + response.js.toJsCmd must contain("tryyou()") + } + } } } From 704ec9420970951c4ac1440939dda85878d48b27 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 23 Apr 2017 20:08:26 -0400 Subject: [PATCH 1511/1949] Correctly append page script even without head and body elements We were just discarding any extracted event JS in this case, whereas we now properly attach it to the page script and then attach a generated script tag to whatever root element we return in that case. --- .../scala/net/liftweb/http/LiftMerge.scala | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala index 7860e4d256..5a595d8a3d 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala @@ -138,7 +138,7 @@ private[http] trait LiftMerge { startingState.copy(headChild = true && mergeHeadAndTail) - case element: Elem if mergeHeadAndTail && + case element: Elem if mergeHeadAndTail && (element.label == "head" || element.label.startsWith("head_")) && htmlDescendant && @@ -217,13 +217,21 @@ private[http] trait LiftMerge { } if (!hasHtmlHeadAndBody) { - val fixedHtml = - normalizeMergeAndExtractEvents(xhtml, HtmlState(mergeHeadAndTail = false)).nodes - - fixedHtml.find { - case e: Elem => true - case _ => false - } getOrElse Text("") + val NodesAndEventJs(fixedHtml, eventJs) = + normalizeMergeAndExtractEvents(xhtml, HtmlState(mergeHeadAndTail = false)) + + fixedHtml.collectFirst { + case e: Elem => + val pageJs = assemblePageSpecificJavaScript(eventJs) + val pageJsElement = + List(pageJs) + .filter(_.toJsCmd.trim.nonEmpty) + .map(pageScopedScriptFileWith) + + e.copy(child = e.child.toSeq ++ pageJsElement) + } getOrElse { + Text("") + } } else { val eventJs = normalizeMergeAndExtractEvents(xhtml, HtmlState(mergeHeadAndTail = true)).js From 002fe6846401e4d0c6fbdaafa24329e1ff5e9909 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 23 Apr 2017 20:09:15 -0400 Subject: [PATCH 1512/1949] Fix a couple of webkit test deprecation warnings. These are related to the renaming of Req.fixHtml. --- .../src/test/scala/net/liftweb/webapptest/ToHeadUsages.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/webkit/src/test/scala/net/liftweb/webapptest/ToHeadUsages.scala b/web/webkit/src/test/scala/net/liftweb/webapptest/ToHeadUsages.scala index ec7a0370ba..7966af7fcd 100644 --- a/web/webkit/src/test/scala/net/liftweb/webapptest/ToHeadUsages.scala +++ b/web/webkit/src/test/scala/net/liftweb/webapptest/ToHeadUsages.scala @@ -167,7 +167,7 @@ object ToHeadUsages extends Specification { } "Exclude from context rewriting" in { - val first = http.Req.fixHtml("/wombat", + val first = http.Req.normalizeHtml("/wombat", foo bar @@ -177,7 +177,7 @@ object ToHeadUsages extends Specification { def excludeBar(in: String): Boolean = in.startsWith("/bar") val second = LiftRules.excludePathFromContextPathRewriting.doWith(excludeBar _) { - Req.fixHtml("/wombat", + Req.normalizeHtml("/wombat", foo bar From b77610e6dd4b064d3f6f356fd761534ff2723052 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 24 Apr 2017 19:15:58 -0400 Subject: [PATCH 1513/1949] Move useful spec helpers into SpecContextHelpers.scala. We added a few spec helpers to `LiftMergeSpec`, but these helpers were more broadly useful, so we now move them to a common file. --- .../net/liftweb/http/LiftMergeSpec.scala | 56 +++-------------- .../net/liftweb/http/SpecContextHelpers.scala | 61 +++++++++++++++++++ 2 files changed, 71 insertions(+), 46 deletions(-) create mode 100644 web/webkit/src/test/scala/net/liftweb/http/SpecContextHelpers.scala diff --git a/web/webkit/src/test/scala/net/liftweb/http/LiftMergeSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/LiftMergeSpec.scala index a73ed6d9df..df929193d0 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/LiftMergeSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/LiftMergeSpec.scala @@ -4,8 +4,7 @@ package http import scala.xml._ import org.specs2._ - import execute.{Result, AsResult} - import mutable.{Around, Specification} + import mutable.Specification import matcher.XmlMatchers import mock.Mockito @@ -15,41 +14,6 @@ import common._ import js.JE.JsObj -trait BaseAround extends Around { - override def around[T: AsResult](test: =>T): Result = { - AsResult(test) - } -} - -trait LiftRulesSetup extends Around { - def rules: LiftRules - - abstract override def around[T: AsResult](test: => T): Result = { - super.around { - LiftRulesMocker.devTestLiftRulesInstance.doWith(rules) { - AsResult(test) - } - } - } -} - -trait SSetup extends Around { - def session: LiftSession - def req: Box[Req] - - abstract override def around[T: AsResult](test: => T): Result = { - super.around { - S.init(req, session) { - AsResult(test) - } - } - } -} - -class WithRules(val rules: LiftRules) extends BaseAround with LiftRulesSetup - -class WithLiftContext(val rules: LiftRules, val session: LiftSession, val req: Box[Req] = Empty) extends BaseAround with LiftRulesSetup with SSetup - class LiftMergeSpec extends Specification with XmlMatchers with Mockito { val mockReq = mock[Req] mockReq.contextPath returns "/context-path" @@ -62,7 +26,7 @@ class LiftMergeSpec extends Specification with XmlMatchers with Mockito { testRules.autoIncludeAjaxCalc.default.set(() => () => (_: LiftSession) => false) testRules.excludePathFromContextPathRewriting.default .set( - () => { in: String => + () => { in: String => in.startsWith("exclude-me") } ) @@ -198,7 +162,7 @@ class LiftMergeSpec extends Specification with XmlMatchers with Mockito { mockReq ) - (result \\ "link").map(_ \@ "href") must_== + (result \\ "link").map(_ \@ "href") must_== "/context-path/testlink" :: "/context-path/testlink2" :: "/context-path/testlink3" :: Nil @@ -232,7 +196,7 @@ class LiftMergeSpec extends Specification with XmlMatchers with Mockito { mockReq ) - (result \\ "script").map(_ \@ "src") must_== + (result \\ "script").map(_ \@ "src") must_== "/context-path/testscript" :: "/context-path/testscript2" :: Nil } @@ -265,7 +229,7 @@ class LiftMergeSpec extends Specification with XmlMatchers with Mockito { mockReq ) - (result \\ "a").map(_ \@ "href") must_== + (result \\ "a").map(_ \@ "href") must_== "/context-path/testa1" :: "testa3" :: "/context-path/testa2" :: @@ -302,7 +266,7 @@ class LiftMergeSpec extends Specification with XmlMatchers with Mockito { mockReq ) - (result \\ "form").map(_ \@ "action") must_== + (result \\ "form").map(_ \@ "action") must_== "/context-path/testform1" :: "testform3" :: "/context-path/testform2" :: @@ -339,7 +303,7 @@ class LiftMergeSpec extends Specification with XmlMatchers with Mockito { ) } - (result \\ "script").map(_ \@ "src") must_== + (result \\ "script").map(_ \@ "src") must_== "testscript" :: "testscript2" :: "testscript3" :: Nil @@ -373,7 +337,7 @@ class LiftMergeSpec extends Specification with XmlMatchers with Mockito { ) } - (result \\ "link").map(_ \@ "href") must_== + (result \\ "link").map(_ \@ "href") must_== "testlink" :: "testlink2" :: "testlink3" :: Nil @@ -407,7 +371,7 @@ class LiftMergeSpec extends Specification with XmlMatchers with Mockito { ) } - (result \\ "a").map(_ \@ "href") must_== + (result \\ "a").map(_ \@ "href") must_== "rewritten" :: "rewritten" :: "rewritten" :: Nil @@ -441,7 +405,7 @@ class LiftMergeSpec extends Specification with XmlMatchers with Mockito { ) } - (result \\ "form").map(_ \@ "action") must_== + (result \\ "form").map(_ \@ "action") must_== "rewritten" :: "rewritten" :: "rewritten" :: Nil diff --git a/web/webkit/src/test/scala/net/liftweb/http/SpecContextHelpers.scala b/web/webkit/src/test/scala/net/liftweb/http/SpecContextHelpers.scala new file mode 100644 index 0000000000..b978da56ac --- /dev/null +++ b/web/webkit/src/test/scala/net/liftweb/http/SpecContextHelpers.scala @@ -0,0 +1,61 @@ +package net.liftweb +package http + +import org.specs2._ + import execute.{Result, AsResult} + import mutable.{Around, Specification} + +import common.{Box, Empty} + +/** + * Used for stacking other `around` providers like `[[LiftRulesSetup]]`. + */ +trait BaseAround extends Around { + override def around[T: AsResult](test: =>T): Result = { + AsResult(test) + } +} + +/** + * Given an instance method or variable `rules`, wraps the spec in a setup of + * that rules instance as the one used by Lift for the duration of the spec. + */ +trait LiftRulesSetup extends Around { + def rules: LiftRules + + abstract override def around[T: AsResult](test: => T): Result = { + super.around { + LiftRulesMocker.devTestLiftRulesInstance.doWith(rules) { + AsResult(test) + } + } + } +} + +/** + * Given an instance method or variable `rules`, wraps the spec in a setup of + * that rules instance as the one used by Lift for the duration of the spec. + */ +trait SSetup extends Around { + def session: LiftSession + def req: Box[Req] + + abstract override def around[T: AsResult](test: => T): Result = { + super.around { + S.init(req, session) { + AsResult(test) + } + } + } +} + +/** + * Wraps a spec in a context where `rules` are the Lift rules in effect. + */ +class WithRules(val rules: LiftRules) extends BaseAround with LiftRulesSetup + +/** + * Wraps a spec in a context where `rules` are the Lift rules in effect, `session` + * is the current Lift session, and `req`, if specified, is the current request. + */ +class WithLiftContext(val rules: LiftRules, val session: LiftSession, val req: Box[Req] = Empty) extends BaseAround with LiftRulesSetup with SSetup From 925b7341eaedeb0f85e487e7ab842248eb815b0f Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 24 Apr 2017 19:19:09 -0400 Subject: [PATCH 1514/1949] `fixHtmlFunc` never extracts inline JS. Before, `fixHtmlFunc` would attempt to extract inline JS if that behavior was enabled in `LiftRules.extractInlineJavaScript`. Unfortunately this didn't work too well in many uses of `JsExp`, due to the fact that the extracted JavaScript would run before the HTML that it was extracted from would be attached to the DOM. We need a longer-term solution, but for now we introduce a pathway for callers of `normalizeHtmlAndEventHandlers` to force `extractInlineJavaScript` functionality on or off, and we use it to turn it off always for `fixHtmlFunc`. We also add a spec for `HtmlFixer` that verifies this behavior. --- .../scala/net/liftweb/http/LiftSession.scala | 13 ++++-- .../net/liftweb/http/js/JsCommands.scala | 21 +++++++--- .../net/liftweb/http/js/HtmlFixerSpec.scala | 40 +++++++++++++++++++ 3 files changed, 66 insertions(+), 8 deletions(-) create mode 100644 web/webkit/src/test/scala/net/liftweb/http/js/HtmlFixerSpec.scala diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index f474e03a7c..82626ad0d4 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -1869,13 +1869,20 @@ class LiftSession(private[http] val _contextPath: String, val underlyingId: Stri * Note that most of the time you can just call * `[[normalizeHtmlAndAppendEventHandlers]]` and not worry about the extra * `JsCmd`, as Lift will automatically append it to the response. - */ - def normalizeHtmlAndEventHandlers(nodes: NodeSeq): NodesAndEventJs = { + * + * @param forceExtractInlineJavaScript If `None`, uses `LiftRules.extractInlineJavaScript` + * to decide whether or not to extract inline JS from the passed nodes. If `Some`, + * extracts (`Some(true)`) or doesn't (`Some(false)`). + */ + def normalizeHtmlAndEventHandlers( + nodes: NodeSeq, + forceExtractInlineJavaScript: Option[Boolean] = None + ): NodesAndEventJs = { HtmlNormalizer.normalizeHtmlAndEventHandlers( nodes, S.contextPath, LiftRules.stripComments.vend, - LiftRules.extractInlineJavaScript + forceExtractInlineJavaScript getOrElse LiftRules.extractInlineJavaScript ) } diff --git a/web/webkit/src/main/scala/net/liftweb/http/js/JsCommands.scala b/web/webkit/src/main/scala/net/liftweb/http/js/JsCommands.scala index 4847dcd77f..5c7e8f4041 100755 --- a/web/webkit/src/main/scala/net/liftweb/http/js/JsCommands.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/js/JsCommands.scala @@ -38,7 +38,7 @@ object JsCommands { * also read and included in the response. Also in this process, all of the * `JsCmd` instances have their `toJsCmd` methods called to convert them to a * string. - * + * * @note The contents of `jsToAppend` are cleared in this process! */ class JsCommands(val reverseList: List[JsCmd]) { @@ -571,9 +571,15 @@ trait HtmlFixer { * construct a function that executes the contents of the scripts * then evaluations to Expression. For use when converting * a JsExp that contains HTML. + * + * + * @note Currently, `fixHtmlFunc` does '''not''' do event extraction, even when + * `LiftRules.extractInlineJavaScript` is `true`, due to poor interactions + * with `JsExp` usage. This will be fixed in a future Lift release; see + * https://github.com/lift/framework/issues/1801 . */ def fixHtmlFunc(uid: String, content: NodeSeq)(f: String => String) = - fixHtmlAndJs(uid, content) match { + fixHtmlAndJs(uid, content, forceExtractInlineJavaScript = Some(false)) match { case (str, Nil) => f(str) case (str, cmds) => "((function() {"+cmds.reduceLeft{_ & _}.toJsCmd+" return "+f(str)+";})())" } @@ -596,7 +602,11 @@ trait HtmlFixer { * This method must be run in the context of the thing creating the XHTML * to capture the bound functions */ - protected def fixHtmlAndJs(uid: String, content: NodeSeq): (String, List[JsCmd]) = { + protected def fixHtmlAndJs( + uid: String, + content: NodeSeq, + forceExtractInlineJavaScript: Option[Boolean] = None + ): (String, List[JsCmd]) = { import Helpers._ val w = new java.io.StringWriter @@ -607,7 +617,8 @@ trait HtmlFixer { session.processSurroundAndInclude( s"JS SetHTML id: $uid", content - ) + ), + forceExtractInlineJavaScript ) } openOr { NodesAndEventJs(content, JsCmds.Noop) @@ -726,7 +737,7 @@ object JsCmds { elem ++ Script(LiftRules.jsArtifacts.onLoad(Run("if (document.getElementById(" + id.encJs + ")) {document.getElementById(" + id.encJs + ").focus();};"))) } } - + /** * Sets the value of an element and sets the focus */ diff --git a/web/webkit/src/test/scala/net/liftweb/http/js/HtmlFixerSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/js/HtmlFixerSpec.scala new file mode 100644 index 0000000000..a7e018fdc1 --- /dev/null +++ b/web/webkit/src/test/scala/net/liftweb/http/js/HtmlFixerSpec.scala @@ -0,0 +1,40 @@ +/* + * Copyright 2010-2017 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 +package js + +import org.specs2.mutable.Specification + +import common._ +import json._ +import JsonDSL._ +import util.Helpers._ + +object HtmlFixerSpec extends Specification { + "HtmlFixer" should { + val testFixer = new HtmlFixer {} + val testSession = new LiftSession("/context-path", "underlying id", Empty) + val testRules = new LiftRules() + testRules.extractInlineJavaScript = true + + "never extract inline JS in fixHtmlFunc" in new WithLiftContext(testRules, testSession) { + testFixer.fixHtmlFunc("test",
        )(identity) must_== + """"
        """" + } + } +} From 348ce3aac0d2ae2c9a3799a049d9fffde0151814 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Tue, 25 Apr 2017 21:52:09 -0400 Subject: [PATCH 1515/1949] Add first drafts of tutorial sections 1-9. --- .../1-view-first-development.adoc | 71 ++++++++ .../2-the-lift-menu-system.adoc | 33 ++++ .../3-adding-snippet-bindings.adoc | 73 ++++++++ .../4-css-selector-transforms.adoc | 171 ++++++++++++++++++ .../5-basic-forms.adoc | 105 +++++++++++ .../6-adding-usernames.adoc | 146 +++++++++++++++ .../7-using-actors-for-chat.adoc | 159 ++++++++++++++++ .../8-customizable-usernames.adoc | 69 +++++++ .../9-comet-actors.adoc | 20 ++ 9 files changed, 847 insertions(+) create mode 100644 docs/getting-started-tutorial/1-view-first-development.adoc create mode 100644 docs/getting-started-tutorial/2-the-lift-menu-system.adoc create mode 100644 docs/getting-started-tutorial/3-adding-snippet-bindings.adoc create mode 100644 docs/getting-started-tutorial/4-css-selector-transforms.adoc create mode 100644 docs/getting-started-tutorial/5-basic-forms.adoc create mode 100644 docs/getting-started-tutorial/6-adding-usernames.adoc create mode 100644 docs/getting-started-tutorial/7-using-actors-for-chat.adoc create mode 100644 docs/getting-started-tutorial/8-customizable-usernames.adoc create mode 100644 docs/getting-started-tutorial/9-comet-actors.adoc diff --git a/docs/getting-started-tutorial/1-view-first-development.adoc b/docs/getting-started-tutorial/1-view-first-development.adoc new file mode 100644 index 0000000000..b52d43b49d --- /dev/null +++ b/docs/getting-started-tutorial/1-view-first-development.adoc @@ -0,0 +1,71 @@ +:idprefix: +:idseparator: - +:toc: right +:toclevels: 2 + +# View-first Development + +If you're developing a user-facing web site or application, one of Lift's +greatest improvements over existing systems is view-first development. +View-first development thoroughly separates the process of creating the user +interface from the process of putting data from the system into it, in a way +that lets you stay focused on users when you're creating the user interface and +worry about the the interface between your backend and the HTML only when +you're working on the backend. + +The flip side of view-first development is that it takes some getting used to +if one is accustomed the typical web MVC framework. The first stop when +figuring out what's going on in a typical web MVC setup is the controller. In +Lift, your first stop is your HTML file. Everything starts in the HTML, and in +what it is that you want to present to the user. You don't just think about +user interactions first, you *build* them first, and let them guide your +development forward and inform it at every step of the way. Turning a usability +tested high fidelity mockup into a live page has never been so straightforward. + +For our chat app, we're going to focus first on two use cases, formulated as +user stories: + + - As a chatter, I want to post a message so that others can see it. + - As a chatter, I want to see messages from me and others so that I can keep + track of the conversation and contribute in context. + +To start with, we'll set up a simple chat.html page in our `src/main/webapp` +directory (where all HTML files go). All we really need in there for now is a +list of chat messages so far, and a box to put our own chat message into. So, +here's some base HTML to get us going: + +```html:src/main/webapp/index.html + + + + Chat! + + + +
        +
          +
        1. Hi!
        2. +
        3. Oh, hey there.
        4. +
        5. How are you?
        6. +
        7. Good, you?
        8. +
        +
        + + + +
        +
        + + +``` + +While we're not using it here, it's probably a good idea to start off with +http://html5boilerplate.com[HTML5 Boilerplate]. Indeed, the default Lift +templates all start with exactly that footnote:[Ok, so not exactly. IE +conditional comments need a little additional work in Lift, because Lift is +smart enough to strip all HTML comments in production mode.]. + +When it comes to user testing, notice that our view is fully-valid HTML, with +placeholder data. It is, in effect, a high-fidelity mockup. And now that we've +got our view sorted out (and, ideally, tested with users), we can start hooking +up the Lift side. diff --git a/docs/getting-started-tutorial/2-the-lift-menu-system.adoc b/docs/getting-started-tutorial/2-the-lift-menu-system.adoc new file mode 100644 index 0000000000..d4ca29a0df --- /dev/null +++ b/docs/getting-started-tutorial/2-the-lift-menu-system.adoc @@ -0,0 +1,33 @@ +:idprefix: +:idseparator: - +:toc: right +:toclevels: 2 + +# The Lift Menu System + +Another distinguishing characteristic of Lift is that it is *secure by +default*. Amongst other things, this means that you can't access a file in your +`src/main/webapp` directory through your application unless you explicitly +define that it's meant to be accessed. You define this using Lift's menu +system, called `SiteMap`. + +Hooking up a simple page like this one is easy, and seems redundant; rest +assured, we'll explore the real power of `SiteMap` as the application becomes +more complicated. All you have to do for the chat page is add a line to your +`SiteMap.scala` that names the page and points to the file in the `webapp` +directory: + +``` +... + Menu.i("Chat") / "chat" +... +``` + +The string passed to `i` is the name of this menu. We can use that to +[automatically render links for our menu](#section-12). It gets processed +through Lift's internationalization system, but since we've got no +internationalization set up for now it'll just go through unchanged. The part +after the `/` specifies where the template will be found—in our case, in the +`chat.html` file directly under `src/main/webapp`. + +With that out of the way, we can move on to bringing our HTML to life. diff --git a/docs/getting-started-tutorial/3-adding-snippet-bindings.adoc b/docs/getting-started-tutorial/3-adding-snippet-bindings.adoc new file mode 100644 index 0000000000..1564b0908c --- /dev/null +++ b/docs/getting-started-tutorial/3-adding-snippet-bindings.adoc @@ -0,0 +1,73 @@ +:idprefix: +:idseparator: - +:toc: right +:toclevels: 2 + +# Adding Snippet Bindings + +In most frameworks, a page's data is looked up by a controller, and backend +code clutters the HTML to produce the correct rendering of the data. This +process is usually done through what amounts to little more than string +munging. Lift throws this paradigm away entirely in favor of a much better +approach based on entities called snippets. + +Snippets let you refer to a block of code that is responsible for rendering a +particular part of your page. You add these references by augmenting your HTML +with a few completely valid `data-` attributes that get stripped before the +HTML is then sent to the browser. These snippets then take your HTML, fully +parsed into a valid DOM tree, and transform it, providing true decoupling +between your business logic and your template, and an extra level of +security footnote:[We already mentioned that Lift is secure by default, and +another way that manifests is that the template HTML is turned into a +first-class XML tree early in the processing cycle, and snippets just transform +that tree. That means script injection and a variety of other attacks are +significantly more difficult against a Lift codebase.]. + + +Let's look at our chat app specifically. We're going to bind two things: the +list of chat messages, and the text input that lets us actually chat. To the +`ol` that contains the chat messages, we add: + +``` +
          +``` + +And to the input form: + +``` +
          +``` + +These two indicate two methods in a class called `Chat`, which Lift searches +for in the `code.snippet` package footnote:[This can be changed using +`Lift.addPackage`. See ...]. We'll write a very basic version that +just passes through the contents of the list and form unchanged, and then in +the next section we'll start adding some behavior. In +`src/main/scala/code/snippet/Chat.scala`, add: + +``` +package code +package snippet + +import scala.xml._ + +object Chat { + def messages(contents: NodeSeq) = contents + def sendMessage(contents: NodeSeq) = contents +} +``` + +Note that the methods referred to from the template can either take a +`NodeSeq` footnote:[What's a `NodeSeq`? Scala uses a `NodeSeq` to represent an +arbitrary block of XML. It is a *seq*uence of >= 1 *node*s, which can in turn +have children.] and return a `NodeSeq`, or they can take no parameters and +return a `(NodeSeq)=>NodeSeq` function. The `NodeSeq` that is passed in is the +element that invoked the snippet in the template, minus the `data-lift` +attribute. The `NodeSeq` that is returned replaces that element completely in +the resulting output. + +Now that we have our snippet methods set up, we can move on to actually showing +some data in them. Right now all they do is pass their contents through +unchanged, so rendering this page in Lift will look just the same as if we just +opened the template directly. To transform them and display our data easily, we +use CSS Selector Transforms. diff --git a/docs/getting-started-tutorial/4-css-selector-transforms.adoc b/docs/getting-started-tutorial/4-css-selector-transforms.adoc new file mode 100644 index 0000000000..867e606006 --- /dev/null +++ b/docs/getting-started-tutorial/4-css-selector-transforms.adoc @@ -0,0 +1,171 @@ +:idprefix: +:idseparator: - +:toc: right +:toclevels: 2 + +# CSS Selector Transforms + +Because Lift operates by transforming HTML trees, it needs an easy way to +specify those transformations. Otherwise we'd be doing a bunch of recursive +tree searches and munges and it would get ugly and probably slow to boot. To +deal with transformations easily, we use a small subset of CSS selectors, with +a few Lift idiosyncrasies to maximize performance and address some use cases +that are particularly useful when transforming trees. + +We'll leave forms for the next section, as forms always come with a catalog of +related functionality, and focus on binding the list of chat messages in this +section. We'll also add a new one before every page load, so that we can see +the list changing. + +First, we'll define a variable to hold the messages: + +``` +... +object Chat { + var messageEntries = List[String]() +... +} +``` + +Then, we can change the definition of the `messages` method to bind the +contents of the message list: + +``` +... + +import net.liftweb.util.Helpers._ + +... + def messages = { + "li *" #> messageEntries + } +... +``` + +In the previous section, we mentioned that Lift snippets can return +`(NodeSeq)=>NodeSeq` functions. That is what's happening here: Lift's CSS +selector transforms are actually functions that take a `NodeSeq` and return a +`NodeSeq`, constructed using an easy-to-read syntax. + +What we do in this particular transformation is select all `li`s. We then +specify that we want to transform them by replacing their contents (`*`) by +whatever is on the right. The right side, however, is a list, in this case of +`String`s. When there's a list on the right side of a transformation, Lift +repeats the matched element or elements once for each entry in the list, and +binds the contents of each element in turn. + +Let's start up Lift and see what's going on. In your terminal, enter the +directory of the chat app and start up the application: + +``` +$ sbt +> container:start +[info] Compiling 4 Scala sources to /Users/Shadowfiend/github/lift-example/target/scala-2.9.2/classes... +[info] jetty-8.1.7.v20120910 +[info] NO JSP Support for /, did not find org.apache.jasper.servlet.JspServlet +[info] started o.e.j.w.WebAppContext{/,[file:/Users/Shadowfiend/github/lift-example/src/main/webapp/]} +[info] started o.e.j.w.WebAppContext{/,[file:/Users/Shadowfiend/github/lift-example/src/main/webapp/]} +[info] Started SelectChannelConnector@0.0.0.0:8080 +[success] Total time: 4 s, completed Oct 6, 2013 2:31:01 PM +> +``` + +Once you see the success message, point your browser to +`http://localhost:8080/`. You should see an empty chat list, since currently +there are no message entries. To fix this, we're going to add a chat message +every time we render the message list: + +``` +... + def messages = { + messageEntries += "It is now " + formattedTimeNow + "li *" #> messageEntries + } +... +``` + +Let's recompile and restart the server: + +``` +> container:stop +[info] stopped o.e.j.w.WebAppContext{/,[file:/Users/Shadowfiend/github/lift-example/src/main/webapp/]} +[success] Total time: 0 s, completed Oct 6, 2013 2:36:48 PM +> container:start +[info] Compiling 1 Scala source to /Users/Shadowfiend/github/lift-example/target/scala-2.9.2/classes... +[info] jetty-8.1.7.v20120910 +[info] NO JSP Support for /, did not find org.apache.jasper.servlet.JspServlet +[info] started o.e.j.w.WebAppContext{/,[file:/Users/Shadowfiend/github/lift-example/src/main/webapp/]} +[info] started o.e.j.w.WebAppContext{/,[file:/Users/Shadowfiend/github/lift-example/src/main/webapp/]} +[info] Started SelectChannelConnector@0.0.0.0:8080 +``` + +Now if you pull up the page you'll see something that doesn't look quite right. +The markup we're producing should look something like: + +``` +
        1. It is now 13:25 UTC
        2. +
        3. It is now 13:25 UTC
        4. +
        5. It is now 13:25 UTC
        6. +
        7. It is now 13:25 UTC
        8. +``` + +If you reload the page, you'll get something like: + +``` +
        9. It is now 13:25 UTC
        10. +
        11. It is now 13:25 UTC
        12. +
        13. It is now 13:25 UTC
        14. +
        15. It is now 13:25 UTC
        16. +
        17. It is now 13:26 UTC
        18. +
        19. It is now 13:26 UTC
        20. +
        21. It is now 13:26 UTC
        22. +
        23. It is now 13:26 UTC
        24. +``` + +What's causing all the repetition? Well, remember when we described what the +CSS Selector Transform was doing, we said we “select all `li`s”. We also said +that the list on the right side means “Lift repeats the matched element **or +elements**”. So we select all the `li`s, but in the template there are 4, so +that the template when rendered alone (say, for a user test, or when a frontend +developer is editing it) has some content in it. How do we bridge the two +without getting nasty in our HTML? + +Lift lets us tag the extra elements with a class `clearable`: + +``` +... +
        25. Hi!
        26. +
        27. Oh, hey there.
        28. +
        29. How are you?
        30. +
        31. Good, you?
        32. +... +``` + +Then, in our snippet, we can use a special transform called `ClearClearable`, +which will remove all of the tagged elements before we start transforming the +template: + +``` +... + def messages = { + messageEntries ::= "It is now " + formattedTimeNow + + ClearClearable & + "li *" #> messageEntries + } +... +``` + +Notice that we combine the two CSS selector transforms here by using the `&`. +You can chain together as many CSS selector transforms as you want this way, as +long as they don't modify the same parts of the same element. We'll deal with +that limitation [a little later](#section13) footnote:[This is because CSS +selector transforms are optimized for speed, and pass through the nodes a +single time to make all of the transformations happen.]. + +Now if we restart the server and look at the results, we'll see the right thing +happening: one entry per message, and every time we reload the page we get a +new entry. + +Now that we've got the list of messages rendering, it's time to get into the +bread and butter of web development: forms. diff --git a/docs/getting-started-tutorial/5-basic-forms.adoc b/docs/getting-started-tutorial/5-basic-forms.adoc new file mode 100644 index 0000000000..0945efe1b3 --- /dev/null +++ b/docs/getting-started-tutorial/5-basic-forms.adoc @@ -0,0 +1,105 @@ +:idprefix: +:idseparator: - +:toc: right +:toclevels: 2 + +# Basic Forms + +It's a recurring and important theme that Lift is secure by default. This +manifests in the way that forms are constructed as well. Form fields in Lift +are associated with a callback function that runs when the field is submitted +with the form. On the client, the field name is always unique to this page +load, and this unique field name is a cryptographically secure random value +that is associated to the callback function you specify on the server +footnote:[What about using your own field names, you may ask? You can always do +that. To access a submitted field with a given name, you can use +`S.param("field name")`. You'll get back a `Box` that will be `Full` and +contain the value you submitted if it was submitted. The `Box` will be `Empty` +if the field wasn't submitted in this request. However, be very careful about +using this method, since it exposes you to CSRF attacks.]. This makes Lift +forms resistant to many cross-site request forgery (CSRF) attacks, and +resistant to the BREACH attack that typical CSRF tokens are vulnerable to when +served with gzip compression over a secure connection. + +Let's look at a simple example with our chat application. Currently our form +looks like this: + +``` + + + + +
          +``` + +Our `sendMessage` snippet looks like this: + +``` +... + def sendMessage(contents: NodeSeq) = contents +... +``` + +We want to bind two things above. The first is the text field, which we want to +bind so that we can get a message from the user, and the second is the submit +button, so that we can process the new message. Here's how we can do that: + +``` +... +import net.liftweb.http.SHtml + +... + def sendMessage = { + var message: String = "" + + "#new-message" #> SHtml.text(message, message = _) & + "type=submit" #> SHtml.submitButton(() => { + messageEntries ::= message + }) + } +... +``` + +First things first, we're using the `SHtml` singleton. This singleton contains +Lift's form handling helpers. We're using two of them here. The first is +`SHtml.text`. This returns an `input type="text"` whose initial value is the +first parameter you pass to it. The second parameter is a function that runs +when the field is submitted. It takes in a single `String`, which is the value +the user submitted, and does something with it. In our case, we use Scala +shorthand to indicate the field's handler will just assign the value submitted +by the user to the `message` variable. + +The second form helper we're using is `SHtml.submitButton`. This returns an +`input type="submit"` that runs the function you pass to it when the form is +submitted. In this case, when the form submits, we're going to prepend the +value of `message` to the existing message entries list. + +Before continuing, let's change the `messages` snippet so it doesn't keep +adding a new message on each page load: + +``` +... + def messages = { + ClearClearable & + "li *" #> messageEntries + } +... +``` + +Now we can restart the server, and when we reload the page we'll be able to +post messages and see them appear on the list of entries. + +So now we have a basic chat page. We've fulfilled our two initial use cases: + + - As a chatter, I want to post a message so that others can see it. + - As a chatter, I want to see messages from me and others so that I can keep + track of the conversation and contribute in context. + +But, this clearly isn't a particularly usable chat. For one, we don't actually +know who's posting what message. For another, the current implementation of the +messages relies on a single variable that updates when a user posts to it. This +works fine when there's just one user posting a time, but once multiple users +start submitting the post form simultaneously, we start getting into serious +threading and data consistency issues. + +Let's deal with usernames first. diff --git a/docs/getting-started-tutorial/6-adding-usernames.adoc b/docs/getting-started-tutorial/6-adding-usernames.adoc new file mode 100644 index 0000000000..53124e8561 --- /dev/null +++ b/docs/getting-started-tutorial/6-adding-usernames.adoc @@ -0,0 +1,146 @@ +:idprefix: +:idseparator: - +:toc: right +:toclevels: 2 + +# Adding Usernames + +We're about to add another use case to our chat system: + + - As a chatter, I want to see who posted a message so that I have better + context for the conversation. + +The first thing we'll do is change the HTML to look like we want it to. Let's +add the username: + +``` +... +
        33. + Antonio Hi! +
        34. +
        35. + David Oh, hey there. +
        36. +
        37. + Antonio How are you? +
        38. +
        39. + Antonio Good, you? +
        40. +... +``` + +Initially, we'll generate a username for the current user. We can store it in a +`SessionVar`. `SessionVar`s in Lift are used to store things that should exist +for the duration of a user's session. A user's session exists as long as Lift +is aware of the user viewing a page related to that session. If Lift sees no +activity from a given session after 20 minutes foonote:[This is configurable, +of course. See `LiftRules.sessionInactivityTimeout`.], the session will be +thrown away, as will the associated `SessionVar` values and related data. + +For now, let's look at adding the `SessionVar` to the `Chat` snippet: + +``` +... +object username extends SessionVar[String]("username") + +object Chat { +... +``` + +Here, we create a new `SessionVar`, whose default value will be “username” if it +is accessed without having been set. We can change that to be random: + +``` +object username extends SessionVar[String]("User " + randomString(5)) +``` + +We're using a Lift helper called `randomString`. We just pass it a length and +it gives us back a random string of that length. This'll make sure that each +user session has a (reasonably) unique username. + +Now, we need to store usernames alongside messages. Let's do that by making the +messageEntries list contain a case class instance instead of a simple `String`: + +``` +... +case class ChatMessage(poster: String, body: String) +class Chat { + var messageEntries = List[ChatMessage]() + + def messages = { + ClearClearable & + "li" #> messageEntries.map { entry => + ".poster *" #> entry.poster & + ".body *" #> entry.body + } + } + + def sendMessage = { + var message = ChatMessage("", "") + + "#new-message" #> SHtml.text(message, { body: String => message = ChatMessage(username.is, body) }) & + "type=submit" #> SHtml.submitButton(() => { + messageEntries ::= message + }) + } +} +``` + +We introduce a new case class, `ChatMessage`, that carries a poster and a +message body. We also update `messageEntries` to be a list of those. + +One of the big changes here is how we update the `messages` snippet method. +Before, we just mapped the content of `li` to the list of `String`s. However, +`ChatMessage` objects can't be dealt with so simply. Instead, the left side +becomes a simple selection of `li`. The right side is now a list of CSS +selector transforms—one for each `ChatMessage`. As before, Lift copies the +contents of the `li` once for each entry in the list, and then transforms it +according to that particular entry. In this case, rather than just putting a +string into the `li`, we set the contents of the `.poster` and `.body` elements +inside it. + +Now, the trained eye might notice that `sendMessage` never checks whether the +client submitted the form without including the message in the submission. This +is a relatively obscure/weird corner case, but one that's worth dealing with +because it's so easy. To deal with it, we can change `message` from being a +`ChatMessage` to being a `Box[ChatMessage]` that starts off `Empty`. We can +then only add the message to the list if the box has been set to `Full`. This +ensures that we never add a weird blank message to the list, and lets us do it +without having to deal with an initial value of `null` for the `message` +variable footnote:[Why is not dealing with `null` desirable? Using a `Box` lets +you deal with "this value isn't there" as an inherent type. `null`, on the +other hand, is something that can masquerade as any value (for example, you can +put `null` into either a `ChatMessage` or a `String`), and the compiler can't +check for you that you made sure this optional value was set before using it. +With a `Box`, the compiler will enforce the checks so that you'll know if +there's a possibility of a value not being set.]: + +``` +... + def sendMessage = { + var message: Box[ChatMessage] = Empty + + "#new-message" #> SHtml.text(message, { body: String => message = Full(ChatMessage(username.is, body)) }) & + "type=submit" #> SHtml.submitButton(() => { + for (body <- message) { + messageEntries ::= message + } + }) + } +... +``` + +We use a `for` comprehension to unpack the value of `message`. The body of that +comprehension won't run unless `message` is a `Full` box containing a +`ChatMessage` sent by the client. + +Now that we have a reasonably nice chat system with actual usernames, it's time +to look at the underlying issue of *consistency*. If two users posted a chat +message at the same time right now, who knows what would happen to the +`messageEntries` list? We could end up with only one of their messages, or with +both, or with an undefined state of nastiness. + +Before letting a user set their own username, let's deal with this issue by +serializing the posting of and access to messages using a simple mechanism: an +actor. diff --git a/docs/getting-started-tutorial/7-using-actors-for-chat.adoc b/docs/getting-started-tutorial/7-using-actors-for-chat.adoc new file mode 100644 index 0000000000..73773e1229 --- /dev/null +++ b/docs/getting-started-tutorial/7-using-actors-for-chat.adoc @@ -0,0 +1,159 @@ +:idprefix: +:idseparator: - +:toc: right +:toclevels: 2 + +# Using Actors for Chats + +Actors are fairly simple: they receive messages, do something with them, and +potentially respond to them. This isn't unlike the process that you go through +when you call a method on a regular class; however, when you send a message to +an actor, it is processed after any earlier messages you sent, never at the +same time. This makes it a lot easier to reason about what's going on at any +given point, even if multiple threads are sending messages to the actor. + +## Storing the Message List + +We're going to use a fairly basic actor for now. As before, we're going to have +a single chat room for the entire site we're building, so we'll use a singleton +object. Lift provides a very simple actor implementation, which we'll be using +here. There are more complicated actor systems, like the one provided by +[Akka](http://akka.io), but they're only necessary in cases where you need more +flexibility or fault tolerance. We'll stick to the easy stuff, starting with a +new file at `src/main/scala/code/actor/ChatActor.scala`: + +``` +package code +package actor + +import net.liftweb.actor._ + +case class ChatMessage(poster: String, body: String) + +case class MessagePosted(message: ChatMessage) + +object ChatActor extends LiftActor { + private var messageEntries = List[ChatMessage]() + + def messageHandler = { + case MessagePosted(newMessage) => + messageEntries ::= newMessage + } +} +``` + +This provides a very basic actor that can receive a new message and add it to +its internal list. We've moved the `ChatMessage` class from the `Chat` snippet +to this file. Typically, messages to actors are case classes. This is because +they're easy to pattern match (as you can see, message handling is done via +pattern matching footnote:[Strictly speaking, `messageHandler` is a +`PartialFunction`. This means that it can match any subset of objects that it +wants to.]) and because they're generally immutable, so there's no chance of +someone else trying to modify the message as we're processing it. + +To ask the actor to add a message, we'll send it the `MessagePosted` message +using the `!` operator. Here's how we can update our code in the `Chat` +snippet: + +``` +... +import actor._ +... + def sendMessage = { + ... + "type=submit" #> SHtml.submitButton(() => { + for (body <- message) { + ChatActor ! MessagePosted(message) + } + }) + } +... +``` + +Now, whenever a message is posted, it will be sent to the `ChatActor`, which +will update its internal list. + +This is, however, only half of the equation. Putting messages into the actor +isn't useful if we can't get them back out! + +## Retrieving Messages + +To retrieve messages, we can add a new message for the `ChatActor`: + +``` +... +case class MessagePosted(message: ChatMessage) +case object GetMessages +... +``` + +And a handler for it: + +``` +... + def messageHandler = { + ... + case GetMessages => + reply(messageEntries) + } +... +``` + +When handling `GetMessages`, we use the `reply` method. This method lets us +send an answer back to the person who sent us this message. By default, +messages don't send answers, and the `!` operator is non-blocking, meaning it +adds the message to the end of the actor's list of messages to process and then +lets the original code continue running without waiting for the actor to deal +with it. + +To wait for a reply, we have to use the `!?` operator instead. We do this when +listing messages by updating the `Chat` snippet: + +``` +... + def messages = { + val messageEntries = Box.asA[List[ChatMessage]](ChatActor !? GetMessages).flatten + + ClearClearable & + "li" #> messageEntries.map { entry => + ".poster *" #> entry.poster & + ".body *" #> entry.body + } + } +... +``` + +Two things to notice here. First off, we use `ChatActor !? GetMessages` to +retrieve the messages. This will block until the `ChatActor` can process our +message and send the reply back to us. Unfortunately, because we're not +invoking a method, there is no type safety in the `!?` operator, so the +compiler doesn't know what the type that `GetMessages` will return to us is. +Because of that, we have to do some casting. To deal with this, Lift provides a +very handy utility function, `Box.asA[T]`; it attempts to convert its parameter +to the type `T`, and, if it succeeds, provides a `Full` `Box` with the +converted value of the appropriate type. If it fails, it provides an `Empty` +`Box` instead. + +To deal with the fact that the `Box` may be `Full` or `Empty`, we use `flatten` +on the `Box`. We do this because the type of `messageEntries` is now a +`Box[List[ChatMessage]]`, meaning a box that *contains* a list of chat +messages. `flatten` will give us the plain list of messages if the `Box` is +`Full`, and an empty list if it's `Empty`, which is perfect. + +It's worth mentioning that it seems like we *know* we'll be getting a +`List[ChatMessage]` from the actor. However, the compiler *doesn't*, and that +means it can't guarantee to us that future changes won't render that assumption +false. Using `Box.asA` ensures that, if someone changes the `ChatActor` later +to reply with something else, our snippet won't blow up in the user's face—it +will just not display the existing messages. The intrepid reader can then go +and fix the issue. + +Another annoyance in the code as it stands now is that if 8000 people are +posting messages and I log into the site, my page won't load until those 8000 +messages are processed by the actor. That's because of how `reply` works: we +wait until the actor gets to our message and then replies to it. There are far +better ways of dealing with both of these issues, which we'll talk about when +we talk about using `CometActor`s [later](link-to-comet-actors). + +First, though, let's go back and look at how we can let the user change their +username so they don't have to use our nasty automatically-generated name. diff --git a/docs/getting-started-tutorial/8-customizable-usernames.adoc b/docs/getting-started-tutorial/8-customizable-usernames.adoc new file mode 100644 index 0000000000..9f1df267d3 --- /dev/null +++ b/docs/getting-started-tutorial/8-customizable-usernames.adoc @@ -0,0 +1,69 @@ +:idprefix: +:idseparator: - +:toc: right +:toclevels: 2 + +# Customizable Usernames + +Let's deal with the next use case: + + - As a chatter, I want to change what name other users see for me when I post + a message. + +What we really want is a text box on the client that will let us edit the name. +We'll add it to the top of our chat area in `chat.html`: + +```html +... +
          +
          + Posting as: + +
          +... +``` + +The ideal way for this to work would be for you to be able to change the value +of the field, and have it save. We can do exactly that using Lift's `ajaxText` +helper in `Chat.scala`: + +```scala +... + def nameField = { + "input" #> SHtml.ajaxText(username.is, username.set _) + } +... +``` + +How's that for ludicrously easy? We create an `ajaxText` whose initial value +will be the value of the `username` `SessionVar` that we created initially to +track the user's name. The second parameter to `ajaxText` is what gets run when +a change occurs on the client, and we hook it up directly to the `SessionVar`'s +`set` method, so that changing the text field on the client changes the +`SessionVar`. + +However, maybe we want to provide some feedback to the user to let them know +the name has been updated. We can get a little more detailed: + +```scala +... + def nameField = { + "input" #> SHtml.ajaxText(username.is, { updatedUsername: String => + username.set(updatedUsername) + + Alert("Updated your username!") + } + } +... +``` + +Now, when the change gets saved, the user will get a popup that will say +“Updated your username!”. Note that `ajaxText` fields are set up to submit +their changes on blur *or* when the user hits `enter` in the field. + +Now that the user can update their name, it's time to make things truly real +time. Until now, to see the messages someone else has posted, we'd have to +reload the page. Only our messages were posted to the page in real time. Not +much of a chat at all, is it! + +It's time to break out the `CometActor`. diff --git a/docs/getting-started-tutorial/9-comet-actors.adoc b/docs/getting-started-tutorial/9-comet-actors.adoc new file mode 100644 index 0000000000..ae4a4bbd7c --- /dev/null +++ b/docs/getting-started-tutorial/9-comet-actors.adoc @@ -0,0 +1,20 @@ +:idprefix: +:idseparator: - +:toc: right +:toclevels: 2 + +# Comet Actors + +Lift has very robust support for pushing information to the client without the +user having to take explicit action, and it's all mediated through a +`CometActor`. `CometActor` works in many ways exactly like the regular actor +we're already using to track chat messages, only `CometActor`s have a couple +more tricks up their sleeve. Most notably for our purposes: they can re-render +themselves in response to stuff going on inside the server, and send the +updated version to the client. This finally gives us a way to update Jane's +chat message list when Jill posts a message, without Jane having to do +anything. + +Our first move will be to change how exactly we handle binding chat messages. +First, we'll do a quick conversion that puts everything in a `CometActor`, but +doesn't add any additional functionality. Instead of calling From 4eb79939d74f83976fdb8b34e46f1f7ab2f8440a Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Tue, 25 Apr 2017 22:10:16 -0400 Subject: [PATCH 1516/1949] Fix typos and such in sections 1-4. --- .../1-view-first-development.adoc | 4 ++-- .../2-the-lift-menu-system.adoc | 2 +- .../3-adding-snippet-bindings.adoc | 14 ++++++------ .../4-css-selector-transforms.adoc | 22 +++++++++---------- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/docs/getting-started-tutorial/1-view-first-development.adoc b/docs/getting-started-tutorial/1-view-first-development.adoc index b52d43b49d..6b844d0001 100644 --- a/docs/getting-started-tutorial/1-view-first-development.adoc +++ b/docs/getting-started-tutorial/1-view-first-development.adoc @@ -14,7 +14,7 @@ worry about the the interface between your backend and the HTML only when you're working on the backend. The flip side of view-first development is that it takes some getting used to -if one is accustomed the typical web MVC framework. The first stop when +if one is accustomed to the typical web MVC framework. The first stop when figuring out what's going on in a typical web MVC setup is the controller. In Lift, your first stop is your HTML file. Everything starts in the HTML, and in what it is that you want to present to the user. You don't just think about @@ -29,7 +29,7 @@ user stories: - As a chatter, I want to see messages from me and others so that I can keep track of the conversation and contribute in context. -To start with, we'll set up a simple chat.html page in our `src/main/webapp` +To start with, we'll set up a simple `chat.html` page in our `src/main/webapp` directory (where all HTML files go). All we really need in there for now is a list of chat messages so far, and a box to put our own chat message into. So, here's some base HTML to get us going: diff --git a/docs/getting-started-tutorial/2-the-lift-menu-system.adoc b/docs/getting-started-tutorial/2-the-lift-menu-system.adoc index d4ca29a0df..5e533e4673 100644 --- a/docs/getting-started-tutorial/2-the-lift-menu-system.adoc +++ b/docs/getting-started-tutorial/2-the-lift-menu-system.adoc @@ -24,7 +24,7 @@ directory: ``` The string passed to `i` is the name of this menu. We can use that to -[automatically render links for our menu](#section-12). It gets processed +link:menu-links[automatically render links for our menu]. It gets processed through Lift's internationalization system, but since we've got no internationalization set up for now it'll just go through unchanged. The part after the `/` specifies where the template will be found—in our case, in the diff --git a/docs/getting-started-tutorial/3-adding-snippet-bindings.adoc b/docs/getting-started-tutorial/3-adding-snippet-bindings.adoc index 1564b0908c..25e30e638b 100644 --- a/docs/getting-started-tutorial/3-adding-snippet-bindings.adoc +++ b/docs/getting-started-tutorial/3-adding-snippet-bindings.adoc @@ -29,7 +29,7 @@ list of chat messages, and the text input that lets us actually chat. To the `ol` that contains the chat messages, we add: ``` -
            +
              ``` And to the input form: @@ -40,10 +40,10 @@ And to the input form: These two indicate two methods in a class called `Chat`, which Lift searches for in the `code.snippet` package footnote:[This can be changed using -`Lift.addPackage`. See ...]. We'll write a very basic version that -just passes through the contents of the list and form unchanged, and then in -the next section we'll start adding some behavior. In -`src/main/scala/code/snippet/Chat.scala`, add: +link:++https://liftweb.net/api/30/api/index.html#net.liftweb.http.LiftRules@addToPackages(what:String):Unit++[`LiftRules.addPackage`.]. +We'll write a very basic version that just passes through the contents of the +list and form unchanged, and then in the next section we'll start adding some +behavior. In `src/main/scala/code/snippet/Chat.scala`, add: ``` package code @@ -59,8 +59,8 @@ object Chat { Note that the methods referred to from the template can either take a `NodeSeq` footnote:[What's a `NodeSeq`? Scala uses a `NodeSeq` to represent an -arbitrary block of XML. It is a *seq*uence of >= 1 *node*s, which can in turn -have children.] and return a `NodeSeq`, or they can take no parameters and +arbitrary block of XML. It is a __seq___uence of >= 1 __node___s, which can in +turn have children.] and return a `NodeSeq`, or they can take no parameters and return a `(NodeSeq)=>NodeSeq` function. The `NodeSeq` that is passed in is the element that invoked the snippet in the template, minus the `data-lift` attribute. The `NodeSeq` that is returned replaces that element completely in diff --git a/docs/getting-started-tutorial/4-css-selector-transforms.adoc b/docs/getting-started-tutorial/4-css-selector-transforms.adoc index 867e606006..a4286667b1 100644 --- a/docs/getting-started-tutorial/4-css-selector-transforms.adoc +++ b/docs/getting-started-tutorial/4-css-selector-transforms.adoc @@ -47,10 +47,10 @@ In the previous section, we mentioned that Lift snippets can return selector transforms are actually functions that take a `NodeSeq` and return a `NodeSeq`, constructed using an easy-to-read syntax. -What we do in this particular transformation is select all `li`s. We then +What we do in this particular transformation is select all ``li``s. We then specify that we want to transform them by replacing their contents (`*`) by whatever is on the right. The right side, however, is a list, in this case of -`String`s. When there's a list on the right side of a transformation, Lift +``String``s. When there's a list on the right side of a transformation, Lift repeats the matched element or elements once for each entry in the list, and binds the contents of each element in turn. @@ -78,7 +78,7 @@ every time we render the message list: ``` ... def messages = { - messageEntries += "It is now " + formattedTimeNow + messageEntries :+= "It is now " + formattedTimeNow "li *" #> messageEntries } ... @@ -123,10 +123,10 @@ If you reload the page, you'll get something like: ``` What's causing all the repetition? Well, remember when we described what the -CSS Selector Transform was doing, we said we “select all `li`s”. We also said +CSS Selector Transform was doing, we said we “select all ``li``s”. We also said that the list on the right side means “Lift repeats the matched element **or -elements**”. So we select all the `li`s, but in the template there are 4, so -that the template when rendered alone (say, for a user test, or when a frontend +elements**”. So we select all the ``li``s, but in the template there are 4, so +that the template when viewed alone (say, for a user test, or when a frontend developer is editing it) has some content in it. How do we bridge the two without getting nasty in our HTML? @@ -148,7 +148,7 @@ template: ``` ... def messages = { - messageEntries ::= "It is now " + formattedTimeNow + messageEntries :+= "It is now " + formattedTimeNow ClearClearable & "li *" #> messageEntries @@ -156,10 +156,10 @@ template: ... ``` -Notice that we combine the two CSS selector transforms here by using the `&`. -You can chain together as many CSS selector transforms as you want this way, as -long as they don't modify the same parts of the same element. We'll deal with -that limitation [a little later](#section13) footnote:[This is because CSS +Notice that we combine the two CSS selector transforms here by using `&`. You +can chain together as many CSS selector transforms as you want this way, as long +as they don't modify the same parts of the same element. We'll deal with that +limitation link:13-who-knows[a little later] footnote:[This is because CSS selector transforms are optimized for speed, and pass through the nodes a single time to make all of the transformations happen.]. From 088b38689ebee4919927a35838e4520d82ab007a Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 26 Apr 2017 22:45:35 -0400 Subject: [PATCH 1517/1949] Fix typos and such in sections 5-8. --- docs/getting-started-tutorial/5-basic-forms.adoc | 5 +++-- docs/getting-started-tutorial/7-using-actors-for-chat.adoc | 4 ++-- docs/getting-started-tutorial/8-customizable-usernames.adoc | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/getting-started-tutorial/5-basic-forms.adoc b/docs/getting-started-tutorial/5-basic-forms.adoc index 0945efe1b3..9159dad38e 100644 --- a/docs/getting-started-tutorial/5-basic-forms.adoc +++ b/docs/getting-started-tutorial/5-basic-forms.adoc @@ -18,8 +18,9 @@ contain the value you submitted if it was submitted. The `Box` will be `Empty` if the field wasn't submitted in this request. However, be very careful about using this method, since it exposes you to CSRF attacks.]. This makes Lift forms resistant to many cross-site request forgery (CSRF) attacks, and -resistant to the BREACH attack that typical CSRF tokens are vulnerable to when -served with gzip compression over a secure connection. +resistant to https://liftweb.net/lift_and_breach[the BREACH attack that typical +CSRF tokens are vulnerable to when served with gzip compression over a secure +connection. Let's look at a simple example with our chat application. Currently our form looks like this: diff --git a/docs/getting-started-tutorial/7-using-actors-for-chat.adoc b/docs/getting-started-tutorial/7-using-actors-for-chat.adoc index 73773e1229..5a56d6066a 100644 --- a/docs/getting-started-tutorial/7-using-actors-for-chat.adoc +++ b/docs/getting-started-tutorial/7-using-actors-for-chat.adoc @@ -18,7 +18,7 @@ We're going to use a fairly basic actor for now. As before, we're going to have a single chat room for the entire site we're building, so we'll use a singleton object. Lift provides a very simple actor implementation, which we'll be using here. There are more complicated actor systems, like the one provided by -[Akka](http://akka.io), but they're only necessary in cases where you need more +http://aka.io[Akka], but they're only necessary in cases where you need more flexibility or fault tolerance. We'll stick to the easy stuff, starting with a new file at `src/main/scala/code/actor/ChatActor.scala`: @@ -153,7 +153,7 @@ posting messages and I log into the site, my page won't load until those 8000 messages are processed by the actor. That's because of how `reply` works: we wait until the actor gets to our message and then replies to it. There are far better ways of dealing with both of these issues, which we'll talk about when -we talk about using `CometActor`s [later](link-to-comet-actors). +we talk about using `CometActor`s link:9-comet-actors[later]. First, though, let's go back and look at how we can let the user change their username so they don't have to use our nasty automatically-generated name. diff --git a/docs/getting-started-tutorial/8-customizable-usernames.adoc b/docs/getting-started-tutorial/8-customizable-usernames.adoc index 9f1df267d3..3c6219b189 100644 --- a/docs/getting-started-tutorial/8-customizable-usernames.adoc +++ b/docs/getting-started-tutorial/8-customizable-usernames.adoc @@ -38,7 +38,7 @@ helper in `Chat.scala`: How's that for ludicrously easy? We create an `ajaxText` whose initial value will be the value of the `username` `SessionVar` that we created initially to track the user's name. The second parameter to `ajaxText` is what gets run when -a change occurs on the client, and we hook it up directly to the `SessionVar`'s +a change occurs on the client, and we hook it up directly to the ``SessionVar``'s `set` method, so that changing the text field on the client changes the `SessionVar`. From e18e904718dab4e3cd70d6893720d7a8343a8ab9 Mon Sep 17 00:00:00 2001 From: Tim Nelson Date: Fri, 28 Apr 2017 06:13:22 -0500 Subject: [PATCH 1518/1949] Added IoHelpersSpec --- .../net/liftweb/util/IoHelpersSpec.txt | 1 + .../net/liftweb/util/IoHelpersSpec.scala | 58 +++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 core/util/src/test/resources/net/liftweb/util/IoHelpersSpec.txt create mode 100644 core/util/src/test/scala/net/liftweb/util/IoHelpersSpec.scala diff --git a/core/util/src/test/resources/net/liftweb/util/IoHelpersSpec.txt b/core/util/src/test/resources/net/liftweb/util/IoHelpersSpec.txt new file mode 100644 index 0000000000..5f6e865cb6 --- /dev/null +++ b/core/util/src/test/resources/net/liftweb/util/IoHelpersSpec.txt @@ -0,0 +1 @@ +IoHelpersSpec diff --git a/core/util/src/test/scala/net/liftweb/util/IoHelpersSpec.scala b/core/util/src/test/scala/net/liftweb/util/IoHelpersSpec.scala new file mode 100644 index 0000000000..aae4c6e192 --- /dev/null +++ b/core/util/src/test/scala/net/liftweb/util/IoHelpersSpec.scala @@ -0,0 +1,58 @@ +/* + * Copyright 2017 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 util + +import org.specs2.mutable.Specification + +import common._ +import Helpers._ + +import java.nio.charset.StandardCharsets +import java.nio.file.{Files, Path} + +object IoHelpersSpec extends Specification with IoHelpers { + "IoHelpers Specification".title + + "Io helpers" should { + + "readWholeFile properly" in { + // Copy a resource file to the tmp directory so we can refer to it as a Path + val resourceAsPath: Box[Path] = { + for { + bytes <- tryo(readWholeStream(getClass.getResourceAsStream("IoHelpersSpec.txt"))).filter(_ ne null) + text <- tryo(new String(bytes)) + path = { + val tempFile = Files.createTempFile(s"IoHelpersSpec_${nextFuncName}", ".tmp") + Files.write(tempFile, text.getBytes(StandardCharsets.UTF_8)) + tempFile + } + } yield path + } + + resourceAsPath.isDefined must_== true + + resourceAsPath.foreach { path => + val pathContents = new String(readWholeFile(path)).trim + Files.delete(path) + pathContents must_== "IoHelpersSpec" + } + + success + } + } +} From 38cecae5c458dd30397d2dc119daa246d6a719b0 Mon Sep 17 00:00:00 2001 From: Ari Gold Date: Fri, 28 Apr 2017 14:14:24 -0700 Subject: [PATCH 1519/1949] handle missing HTTPRequests in S.request Sometimes S.req returns a Req without a HTTPRequest inside. Ergo, when we call HttpRequest.snapshot in Req.snapshot, we get a NPE. To fix this, we modify currentReq in both definitions of buildDeferredFunction so that they filter out null requests. --- web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index 82626ad0d4..88d87c350b 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -1547,7 +1547,7 @@ class LiftSession(private[http] val _contextPath: String, val underlyingId: Stri * had been executed on the thread that created the function. */ def buildDeferredFunction[T](deferredFunction: () => T): () => T = { - val currentReq = S.request.map(_.snapshot) + val currentReq = S.request.filter(_.request != null).map(_.snapshot) val renderVersion = RenderVersion.get val requestVarFunc = RequestVarHandler.generateSnapshotRestorer[T]() @@ -1565,7 +1565,7 @@ class LiftSession(private[http] val _contextPath: String, val underlyingId: Stri * request and session context. */ def buildDeferredFunction[A,T](deferredFunction: (A)=>T): (A)=>T = { - val currentReq = S.request.map(_.snapshot) + val currentReq = S.request.filter(_.request != null).map(_.snapshot) val renderVersion = RenderVersion.get val requestVarFunc = RequestVarHandler.generateSnapshotRestorer[T]() From 1f8a747236a4097971efee5e2932e45c1165819e Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 1 May 2017 22:27:18 -0400 Subject: [PATCH 1520/1949] JSON-serialize +/- infinity and NaN as null. Before now, we were serializing these as their Scala string equivalents. For the infinities, this meant no one could deserialize them. For NaN, this meant only JavaScript when parsing JSON as actual JavaScript could deserialize it. lift-json itself couldn't deserialize the serialized special values, in fact. --- .../src/main/scala/net/liftweb/json/JsonAST.scala | 3 +++ .../scala/net/liftweb/json/JsonPrintingSpec.scala | 14 ++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/core/json/src/main/scala/net/liftweb/json/JsonAST.scala b/core/json/src/main/scala/net/liftweb/json/JsonAST.scala index 5390225f1f..d4a9c25a98 100644 --- a/core/json/src/main/scala/net/liftweb/json/JsonAST.scala +++ b/core/json/src/main/scala/net/liftweb/json/JsonAST.scala @@ -946,6 +946,9 @@ object JsonAST { case null => buf.append("null") case JBool(true) => buf.append("true") case JBool(false) => buf.append("false") + // These special double values serialize to null; otherwise, we would + // generate invalid JSON. + case JDouble(nully) if nully.isNaN || nully.isInfinity => buf.append("null") case JDouble(n) => buf.append(n.toString) case JInt(n) => buf.append(n.toString) case JNull => buf.append("null") diff --git a/core/json/src/test/scala/net/liftweb/json/JsonPrintingSpec.scala b/core/json/src/test/scala/net/liftweb/json/JsonPrintingSpec.scala index 915c6c112e..84dcc6591b 100644 --- a/core/json/src/test/scala/net/liftweb/json/JsonPrintingSpec.scala +++ b/core/json/src/test/scala/net/liftweb/json/JsonPrintingSpec.scala @@ -34,6 +34,20 @@ object JsonPrintingSpec extends Specification with JValueGen with ScalaCheck { forAll(rendering) } + "rendering special double values" should { + "render positive infinity as null" in { + JsonAST.compactRender(JDouble(Double.PositiveInfinity)) must_== "null" + } + + "render negative infinity as null" in { + JsonAST.compactRender(JDouble(Double.NegativeInfinity)) must_== "null" + } + + "render NaN as null" in { + JsonAST.compactRender(JDouble(Double.NaN)) must_== "null" + } + } + private def parse(json: String) = scala.util.parsing.json.JSON.parseRaw(json) implicit def arbDoc: Arbitrary[JValue] = Arbitrary(genJValue) From d77f4006d5860cda59a6bc309ed8c013139dcb1b Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Tue, 2 May 2017 22:04:23 -0400 Subject: [PATCH 1521/1949] Add interlinking between tutorial parts. --- docs/getting-started-tutorial/1-view-first-development.adoc | 2 +- docs/getting-started-tutorial/2-the-lift-menu-system.adoc | 2 +- docs/getting-started-tutorial/3-adding-snippet-bindings.adoc | 2 +- docs/getting-started-tutorial/4-css-selector-transforms.adoc | 2 +- docs/getting-started-tutorial/5-basic-forms.adoc | 2 +- docs/getting-started-tutorial/6-adding-usernames.adoc | 4 ++-- docs/getting-started-tutorial/7-using-actors-for-chat.adoc | 4 ++-- docs/getting-started-tutorial/8-customizable-usernames.adoc | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/getting-started-tutorial/1-view-first-development.adoc b/docs/getting-started-tutorial/1-view-first-development.adoc index 6b844d0001..e60bdf9887 100644 --- a/docs/getting-started-tutorial/1-view-first-development.adoc +++ b/docs/getting-started-tutorial/1-view-first-development.adoc @@ -68,4 +68,4 @@ smart enough to strip all HTML comments in production mode.]. When it comes to user testing, notice that our view is fully-valid HTML, with placeholder data. It is, in effect, a high-fidelity mockup. And now that we've got our view sorted out (and, ideally, tested with users), we can start hooking -up the Lift side. +up link:2-the-lift-menu-system.adoc[the Lift side]. diff --git a/docs/getting-started-tutorial/2-the-lift-menu-system.adoc b/docs/getting-started-tutorial/2-the-lift-menu-system.adoc index 5e533e4673..cddf382db2 100644 --- a/docs/getting-started-tutorial/2-the-lift-menu-system.adoc +++ b/docs/getting-started-tutorial/2-the-lift-menu-system.adoc @@ -30,4 +30,4 @@ internationalization set up for now it'll just go through unchanged. The part after the `/` specifies where the template will be found—in our case, in the `chat.html` file directly under `src/main/webapp`. -With that out of the way, we can move on to bringing our HTML to life. +With that out of the way, we can move on to link:3-adding-snippet-bindings.adoc[bringing our HTML to life]. diff --git a/docs/getting-started-tutorial/3-adding-snippet-bindings.adoc b/docs/getting-started-tutorial/3-adding-snippet-bindings.adoc index 25e30e638b..96e004dbb1 100644 --- a/docs/getting-started-tutorial/3-adding-snippet-bindings.adoc +++ b/docs/getting-started-tutorial/3-adding-snippet-bindings.adoc @@ -70,4 +70,4 @@ Now that we have our snippet methods set up, we can move on to actually showing some data in them. Right now all they do is pass their contents through unchanged, so rendering this page in Lift will look just the same as if we just opened the template directly. To transform them and display our data easily, we -use CSS Selector Transforms. +use link:4-css-selector-transforms.adoc[CSS Selector Transforms]. diff --git a/docs/getting-started-tutorial/4-css-selector-transforms.adoc b/docs/getting-started-tutorial/4-css-selector-transforms.adoc index a4286667b1..01f8e8cbee 100644 --- a/docs/getting-started-tutorial/4-css-selector-transforms.adoc +++ b/docs/getting-started-tutorial/4-css-selector-transforms.adoc @@ -168,4 +168,4 @@ happening: one entry per message, and every time we reload the page we get a new entry. Now that we've got the list of messages rendering, it's time to get into the -bread and butter of web development: forms. +bread and butter of web development: link:5-basic-forms.adoc[forms]. diff --git a/docs/getting-started-tutorial/5-basic-forms.adoc b/docs/getting-started-tutorial/5-basic-forms.adoc index 9159dad38e..a2b10c8304 100644 --- a/docs/getting-started-tutorial/5-basic-forms.adoc +++ b/docs/getting-started-tutorial/5-basic-forms.adoc @@ -103,4 +103,4 @@ works fine when there's just one user posting a time, but once multiple users start submitting the post form simultaneously, we start getting into serious threading and data consistency issues. -Let's deal with usernames first. +Let's link:6-adding-usernames.adoc[deal with usernames first]. diff --git a/docs/getting-started-tutorial/6-adding-usernames.adoc b/docs/getting-started-tutorial/6-adding-usernames.adoc index 53124e8561..7fdbe07f4e 100644 --- a/docs/getting-started-tutorial/6-adding-usernames.adoc +++ b/docs/getting-started-tutorial/6-adding-usernames.adoc @@ -142,5 +142,5 @@ message at the same time right now, who knows what would happen to the both, or with an undefined state of nastiness. Before letting a user set their own username, let's deal with this issue by -serializing the posting of and access to messages using a simple mechanism: an -actor. +serializing the posting of and access to messages using a simple mechanism: +link:7-using-actors-for-chat.adoc[an actor]. diff --git a/docs/getting-started-tutorial/7-using-actors-for-chat.adoc b/docs/getting-started-tutorial/7-using-actors-for-chat.adoc index 5a56d6066a..5ca26ff7e5 100644 --- a/docs/getting-started-tutorial/7-using-actors-for-chat.adoc +++ b/docs/getting-started-tutorial/7-using-actors-for-chat.adoc @@ -155,5 +155,5 @@ wait until the actor gets to our message and then replies to it. There are far better ways of dealing with both of these issues, which we'll talk about when we talk about using `CometActor`s link:9-comet-actors[later]. -First, though, let's go back and look at how we can let the user change their -username so they don't have to use our nasty automatically-generated name. +First, though, let's go back and look at how we can let the user link:8-customizable-usernames.adoc[change their +username so they don't have to use our nasty automatically-generated name]. diff --git a/docs/getting-started-tutorial/8-customizable-usernames.adoc b/docs/getting-started-tutorial/8-customizable-usernames.adoc index 3c6219b189..c426f1294e 100644 --- a/docs/getting-started-tutorial/8-customizable-usernames.adoc +++ b/docs/getting-started-tutorial/8-customizable-usernames.adoc @@ -66,4 +66,4 @@ time. Until now, to see the messages someone else has posted, we'd have to reload the page. Only our messages were posted to the page in real time. Not much of a chat at all, is it! -It's time to break out the `CometActor`. +It's time to link:9-comet-actors.adoc[break out the `CometActor`]. From b6668a9728da9bd1ad58a14fd1b9277f6980d822 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 3 May 2017 22:23:45 -0400 Subject: [PATCH 1522/1949] Significantly rework the introduction + explanation of `ChatMessage`. We now use asciidoc callouts to more clearly link back to particular parts of the code that was updated, and break it down into two more digestible chunks. --- .../6-adding-usernames.adoc | 115 ++++++++++-------- 1 file changed, 61 insertions(+), 54 deletions(-) diff --git a/docs/getting-started-tutorial/6-adding-usernames.adoc b/docs/getting-started-tutorial/6-adding-usernames.adoc index 7fdbe07f4e..9935bd523b 100644 --- a/docs/getting-started-tutorial/6-adding-usernames.adoc +++ b/docs/getting-started-tutorial/6-adding-usernames.adoc @@ -62,78 +62,85 @@ user session has a (reasonably) unique username. Now, we need to store usernames alongside messages. Let's do that by making the messageEntries list contain a case class instance instead of a simple `String`: -``` +```scala ... -case class ChatMessage(poster: String, body: String) +case class ChatMessage(poster: String, body: String) // <1> class Chat { - var messageEntries = List[ChatMessage]() + var messageEntries = List[ChatMessage]() // <2> def messages = { ClearClearable & - "li" #> messageEntries.map { entry => + "li" #> messageEntries.map { entry => // <3> ".poster *" #> entry.poster & ".body *" #> entry.body } } - - def sendMessage = { - var message = ChatMessage("", "") - - "#new-message" #> SHtml.text(message, { body: String => message = ChatMessage(username.is, body) }) & - "type=submit" #> SHtml.submitButton(() => { - messageEntries ::= message - }) - } -} -``` - -We introduce a new case class, `ChatMessage`, that carries a poster and a -message body. We also update `messageEntries` to be a list of those. - -One of the big changes here is how we update the `messages` snippet method. -Before, we just mapped the content of `li` to the list of `String`s. However, -`ChatMessage` objects can't be dealt with so simply. Instead, the left side -becomes a simple selection of `li`. The right side is now a list of CSS -selector transforms—one for each `ChatMessage`. As before, Lift copies the -contents of the `li` once for each entry in the list, and then transforms it -according to that particular entry. In this case, rather than just putting a -string into the `li`, we set the contents of the `.poster` and `.body` elements -inside it. - -Now, the trained eye might notice that `sendMessage` never checks whether the -client submitted the form without including the message in the submission. This -is a relatively obscure/weird corner case, but one that's worth dealing with -because it's so easy. To deal with it, we can change `message` from being a -`ChatMessage` to being a `Box[ChatMessage]` that starts off `Empty`. We can -then only add the message to the list if the box has been set to `Full`. This -ensures that we never add a weird blank message to the list, and lets us do it -without having to deal with an initial value of `null` for the `message` -variable footnote:[Why is not dealing with `null` desirable? Using a `Box` lets -you deal with "this value isn't there" as an inherent type. `null`, on the -other hand, is something that can masquerade as any value (for example, you can -put `null` into either a `ChatMessage` or a `String`), and the compiler can't -check for you that you made sure this optional value was set before using it. -With a `Box`, the compiler will enforce the checks so that you'll know if -there's a possibility of a value not being set.]: - -``` ... +``` +<1> First, we introduce a new case class, `ChatMessage`, that carries a poster + and a message body. +<2> We also update `messageEntries` to be a list of ``ChatMessage``s instead of + plain ``String``s. +<3> One of the big changes here is how we update the `messages` snippet method. + Before, we just mapped the content of `li` to the list of ``String``s. + However, `ChatMessage` objects can't be dealt with so simply. Instead, the + left side becomes a simple selection of `li`. The right side is now a list + of CSS selector transforms -- one for each `ChatMessage`. As before, Lift + copies the contents of the `li` once for each entry in the list, and then + transforms it according to that particular entry. In this case, rather than + just putting a string into the `li`, we set the contents of the `.poster` + and `.body` elements inside it. + +Now let's update the binding of the `sendMessage` form to deal with the new +`ChatMessage` class: + +```scala def sendMessage = { - var message: Box[ChatMessage] = Empty + var message = ChatMessage("", "") // <1> - "#new-message" #> SHtml.text(message, { body: String => message = Full(ChatMessage(username.is, body)) }) & - "type=submit" #> SHtml.submitButton(() => { + "#new-message" #> SHtml.text( // <2> + message, + { messageBody: String => message = Full(ChatMessage(username.get, messageBody)) } // <3> + ) & + "type=submit" #> SHtml.submitButton(() => { // <4> for (body <- message) { messageEntries ::= message } }) } -... +} ``` - -We use a `for` comprehension to unpack the value of `message`. The body of that -comprehension won't run unless `message` is a `Full` box containing a -`ChatMessage` sent by the client. +<1> Before, we used an empty `String` as our starting value for the message. + However, we can't do that anymore here. Our only option would be to use + `null`, but `null` is very dangerous footnote:[Why is not dealing with + `null` desirable? Using a `Box` lets you deal with "this value isn't there" + as an inherent type. `null`, on the other hand, is something that can + masquerade as any value (for example, you can put `null` into either a + `ChatMessage` or a `String`), and the compiler can't check for you that you + made sure this optional value was set before using it. With a `Box`, the + compiler will enforce the checks so that you'll know if there's a + possibility of a value not being set.] and, as a rule, we avoid using it in + Scala. Instead, we use an `Empty` `Box`, and, when we receive a message + body, we create a `Full` `Box` with the newly posted `ChatMessage`. +<2> Here, we update the handler for the `#new-message` text field. Before, the + handler function was `message = _`; when `message` was a `String`, we could + simply assign the message the user sent directly to it, and we were good to + go. However, `message` is now a `ChatMessage` -- it has to carry not only + the message body that the user typed, but also their username. To do that, + we write a complete handler function that takes in the body that the user + submitted with the form and, combined with the current user's username, + creates a `ChatMessage`. This `ChatMessage` is what we now put into the + `message` variable. +<3> Notably, `username.get` is how you fetch the current value of the `username` + `SessionVar`. Don't confuse it with the `.get` on `Option`, which is very + dangerous! If you prefer to use a method that is less easily confused with + ``Option``'s `.get` (as many Lift developers and committers do), you can use + `.is` instead, which does the same thing. +<4> As a result of the `Box` wrapping the submitted `ChatMessage`, we have to + update the submission handler. We use a `for` comprehension to unpack the + value of `message`. The body of that comprehension won't run unless + `message` is `Full`, so we can't try to insert an empty message into the + message list. Now that we have a reasonably nice chat system with actual usernames, it's time to look at the underlying issue of *consistency*. If two users posted a chat From f492f67ceb22be687fba375e613114678bf0bfe2 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 13 May 2017 11:33:02 -0400 Subject: [PATCH 1523/1949] Add workaround for travis issue. As of the 2017-05-04 update to travis Precise images, /usr/local/bin/sbt is not actually executable out of the box. We add a before_script hook per the recommendation issued by the travis folks at travis-ci/travis-ci#7703 in order to make sbt executable again, allowing the rest of the build to continue normally. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index dcdeb8e6e7..f5cf0afccf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,6 +28,7 @@ before_script: - "cd web/webkit" - "npm install" - "cd -" + - sudo chmod +x /usr/local/bin/sbt notifications: webhooks: From a29054c6890c0870f24a6fb615d811d11f643e11 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 13 May 2017 11:43:51 -0400 Subject: [PATCH 1524/1949] Always suspend comet requests before resuming them. When a container supports request suspension, we were first scheduling a function to resume the request on a different thread and then suspending the request. While the function was scheduled in the future, the fact that it was scheduled before the suspend had definitively occurred meant there were thread schedulings where the resume could occur before the suspend had executed, leading to exceptions. We now suspend the request *before* scheduling the resume. We schedule the resume in a finally block to most closely track current behaviors when the thread scheduling was correct, in which a resume would occur whether or not the suspend threw an exception. I'm not certain about the full shape of that edge case, so for now we take the least-changed path to dealing with it while still addressing the core reported problem. --- .../scala/net/liftweb/http/LiftServlet.scala | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala index 9f65ed609a..9c00e4dfbe 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala @@ -114,14 +114,17 @@ class LiftServlet extends Loggable { } if (reqOrg.request.suspendResumeSupport_?) { - runFunction(liftResponse => { - // do the actual write on a separate thread - Schedule.schedule(() => { - reqOrg.request.resume(reqOrg, liftResponse) - }, 0.seconds) - }) + try { + reqOrg.request.suspend(cometTimeout) + } finally { + runFunction(liftResponse => { + // do the actual write on a separate thread + Schedule.schedule(() => { + reqOrg.request.resume(reqOrg, liftResponse) + }, 0.seconds) + }) + } - reqOrg.request.suspend(cometTimeout) false } else { val future = new LAFuture[LiftResponse] @@ -880,7 +883,7 @@ class LiftServlet extends Loggable { val jsCommands = new JsCommands(JsCmds.Run(jsUpdateTime) :: jsUpdateStuff) - + // If we need to, ensure we capture JS from this request's render version. // The comet actor will already have handled the comet's version. S.request.flatMap(req => extractRenderVersion(req.path.partPath)) match { From 57d8d57b003c69e0a2734d6201298d7a15cac98a Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 13 May 2017 12:48:29 -0400 Subject: [PATCH 1525/1949] Add RenderSettings double rendering customization. Rather than forcibly rendering NaN and +/-Infinity as null, we now provide a doubleRenderer in RenderSettings. By default, we shift to the null rendering strategy for special values. However, we also provide the old-style as-is rendering, and a new option to throw an exception when rendering special-value doubles for those who would prefer to not spit nulls into JSON where they expect strictly doubles. --- .../main/scala/net/liftweb/json/JsonAST.scala | 64 +++++++++++++++++-- .../net/liftweb/json/JsonPrintingSpec.scala | 61 +++++++++++++++++- 2 files changed, 119 insertions(+), 6 deletions(-) diff --git a/core/json/src/main/scala/net/liftweb/json/JsonAST.scala b/core/json/src/main/scala/net/liftweb/json/JsonAST.scala index d4a9c25a98..ceace6fcae 100644 --- a/core/json/src/main/scala/net/liftweb/json/JsonAST.scala +++ b/core/json/src/main/scala/net/liftweb/json/JsonAST.scala @@ -881,17 +881,74 @@ object JsonAST { */ val compactJs = RenderSettings(0, jsEscapeChars) } + + /** + * Parent trait for double renderers, which decide how doubles contained in + * a JDouble are rendered to JSON string. + */ + sealed trait DoubleRenderer { + def renderDouble(double: Double): String + } + /** + * A `DoubleRenderer` that renders special values `NaN`, `-Infinity`, and + * `Infinity` as-is using `toString`. This is not strictly valid JSON, but + * JavaScript can eval it. Other double values are also rendered the same + * way. + * + * Usage is not recommended. + */ + case object RenderSpecialValuesAsIs extends DoubleRenderer { + def renderDouble(double: Double): String = { + double.toString + } + } + /** + * A `DoubleRenderer` that renders special values `NaN`, `-Infinity`, and + * `Infinity` as `null`. Other doubles are rendered normally using + * `toString`. + */ + case object RenderSpecialValuesAsNull extends DoubleRenderer { + def renderDouble(double: Double): String = { + if (double.isNaN || double.isInfinity) { + "null" + } else { + double.toString + } + } + } + /** + * A `DoubleRenderer` that throws an `IllegalArgumentException` when the + * special values `NaN`, `-Infinity`, and `Infinity` are encountered. Other + * doubles are rendered normally using `toString`. + */ + case object FailToRenderSpecialValues extends DoubleRenderer { + def renderDouble(double: Double): String = { + if (double.isNaN || double.isInfinity) { + throw new IllegalArgumentException(s"Double value $double is not renderable to JSON.") + } else { + double.toString + } + } + } /** * RenderSettings allows for customizing how JSON is rendered to a String. * At the moment, you can customize the indentation (if 0, all the JSON is * printed on one line), the characters that should be escaped (in addition * to a base set that will always be escaped for valid JSON), and whether or * not a space should be included after a field name. + * + * @param doubleRendering Before Lift 3.1.0, the three special double values + * NaN, Infinity, and -Infinity were serialized as-is. This is invalid + * JSON, but valid JavaScript. We now default special double values to + * serialize as null, but provide both the old behavior and a new behavior + * that throws an exception upon finding these values. See + * `[[DoubleRenderer]]` and its subclasses for more. */ case class RenderSettings( indent: Int, escapeChars: Set[Char] = Set(), - spaceAfterFieldName: Boolean = false + spaceAfterFieldName: Boolean = false, + doubleRenderer: DoubleRenderer = RenderSpecialValuesAsNull ) { val lineBreaks_? = indent > 0 } @@ -946,10 +1003,7 @@ object JsonAST { case null => buf.append("null") case JBool(true) => buf.append("true") case JBool(false) => buf.append("false") - // These special double values serialize to null; otherwise, we would - // generate invalid JSON. - case JDouble(nully) if nully.isNaN || nully.isInfinity => buf.append("null") - case JDouble(n) => buf.append(n.toString) + case JDouble(n) => buf.append(settings.doubleRenderer.renderDouble(n)) case JInt(n) => buf.append(n.toString) case JNull => buf.append("null") case JString(null) => buf.append("null") diff --git a/core/json/src/test/scala/net/liftweb/json/JsonPrintingSpec.scala b/core/json/src/test/scala/net/liftweb/json/JsonPrintingSpec.scala index 84dcc6591b..f04fd1afab 100644 --- a/core/json/src/test/scala/net/liftweb/json/JsonPrintingSpec.scala +++ b/core/json/src/test/scala/net/liftweb/json/JsonPrintingSpec.scala @@ -17,6 +17,8 @@ package net.liftweb package json +import scala.util.Random + import org.specs2.mutable.Specification import org.specs2.ScalaCheck import org.scalacheck.Arbitrary @@ -34,7 +36,12 @@ object JsonPrintingSpec extends Specification with JValueGen with ScalaCheck { forAll(rendering) } - "rendering special double values" should { + "rendering special double values by default" should { + "render a standard double as is" in { + val double = Random.nextDouble + JsonAST.compactRender(JDouble(double)) must_== double.toString + } + "render positive infinity as null" in { JsonAST.compactRender(JDouble(Double.PositiveInfinity)) must_== "null" } @@ -48,6 +55,58 @@ object JsonPrintingSpec extends Specification with JValueGen with ScalaCheck { } } + "rendering special double values with as-is handling" should { + def render(json: JValue) = { + JsonAST.render( + json, + JsonAST.RenderSettings(0, doubleRenderer = JsonAST.RenderSpecialValuesAsIs) + ) + } + + "render a standard double as is" in { + val double = Random.nextDouble + render(JDouble(double)) must_== double.toString + } + + "render positive infinity as null" in { + render(JDouble(Double.PositiveInfinity)) must_== "Infinity" + } + + "render negative infinity as null" in { + render(JDouble(Double.NegativeInfinity)) must_== "-Infinity" + } + + "render NaN as null" in { + render(JDouble(Double.NaN)) must_== "NaN" + } + } + + "rendering special double values with special value exceptions enabled" should { + def render(json: JValue) = { + JsonAST.render( + json, + JsonAST.RenderSettings(0, doubleRenderer = JsonAST.FailToRenderSpecialValues) + ) + } + + "render a standard double as is" in { + val double = Random.nextDouble + render(JDouble(double)) must_== double.toString + } + + "render positive infinity as null" in { + render(JDouble(Double.PositiveInfinity)) must throwAn[IllegalArgumentException] + } + + "render negative infinity as null" in { + render(JDouble(Double.NegativeInfinity)) must throwAn[IllegalArgumentException] + } + + "render NaN as null" in { + render(JDouble(Double.NaN)) must throwAn[IllegalArgumentException] + } + } + private def parse(json: String) = scala.util.parsing.json.JSON.parseRaw(json) implicit def arbDoc: Arbitrary[JValue] = Arbitrary(genJValue) From 19ae0bd2c44dc934472f9392aafcc5813451700e Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 13 May 2017 12:54:18 -0400 Subject: [PATCH 1526/1949] Adjust DoubleRenderer to be a (String)=>Double. --- .../src/main/scala/net/liftweb/json/JsonAST.scala | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/json/src/main/scala/net/liftweb/json/JsonAST.scala b/core/json/src/main/scala/net/liftweb/json/JsonAST.scala index ceace6fcae..8459b218e1 100644 --- a/core/json/src/main/scala/net/liftweb/json/JsonAST.scala +++ b/core/json/src/main/scala/net/liftweb/json/JsonAST.scala @@ -886,8 +886,8 @@ object JsonAST { * Parent trait for double renderers, which decide how doubles contained in * a JDouble are rendered to JSON string. */ - sealed trait DoubleRenderer { - def renderDouble(double: Double): String + sealed trait DoubleRenderer extends Function1[Double,String] { + def apply(double: Double): String } /** * A `DoubleRenderer` that renders special values `NaN`, `-Infinity`, and @@ -898,7 +898,7 @@ object JsonAST { * Usage is not recommended. */ case object RenderSpecialValuesAsIs extends DoubleRenderer { - def renderDouble(double: Double): String = { + def apply(double: Double): String = { double.toString } } @@ -908,7 +908,7 @@ object JsonAST { * `toString`. */ case object RenderSpecialValuesAsNull extends DoubleRenderer { - def renderDouble(double: Double): String = { + def apply(double: Double): String = { if (double.isNaN || double.isInfinity) { "null" } else { @@ -922,7 +922,7 @@ object JsonAST { * doubles are rendered normally using `toString`. */ case object FailToRenderSpecialValues extends DoubleRenderer { - def renderDouble(double: Double): String = { + def apply(double: Double): String = { if (double.isNaN || double.isInfinity) { throw new IllegalArgumentException(s"Double value $double is not renderable to JSON.") } else { @@ -1003,7 +1003,7 @@ object JsonAST { case null => buf.append("null") case JBool(true) => buf.append("true") case JBool(false) => buf.append("false") - case JDouble(n) => buf.append(settings.doubleRenderer.renderDouble(n)) + case JDouble(n) => buf.append(settings.doubleRenderer(n)) case JInt(n) => buf.append(n.toString) case JNull => buf.append("null") case JString(null) => buf.append("null") From a5e95cf98b5ccd1b067a7f8564b506e88b3b3862 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 13 May 2017 11:56:21 -0400 Subject: [PATCH 1527/1949] Add class name->Class caching to FullTypeHints. FullTypeHints in lift-json does a classloader lookup whenever it tries to instantiate a type, to look the class up based on its fully-qualified name. Unfortunately, classloader loadClass is a blocking, synchronized action, which can cause serious thread contention in high load scenarios. --- .../src/main/scala/net/liftweb/json/Formats.scala | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/core/json/src/main/scala/net/liftweb/json/Formats.scala b/core/json/src/main/scala/net/liftweb/json/Formats.scala index d75b7250cf..61be1871a5 100644 --- a/core/json/src/main/scala/net/liftweb/json/Formats.scala +++ b/core/json/src/main/scala/net/liftweb/json/Formats.scala @@ -18,6 +18,10 @@ package net.liftweb package json import java.util.{Date, TimeZone} +import java.util.concurrent.ConcurrentHashMap + +import scala.collection.concurrent.{Map=>ConcurrentScalaMap} +import scala.collection.JavaConverters._ /** Formats to use when converting JSON. * Formats are usually configured by using an implicit parameter: @@ -230,9 +234,16 @@ case class ShortTypeHints(hints: List[Class[_]]) extends TypeHints { /** Use full class name as a type hint. */ case class FullTypeHints(hints: List[Class[_]]) extends TypeHints { + private val hintsToClass: ConcurrentScalaMap[String, Class[_]] = + new ConcurrentHashMap[String, Class[_]]().asScala ++= hints.map(clazz => hintFor(clazz) -> clazz) + def hintFor(clazz: Class[_]) = clazz.getName + def classFor(hint: String): Option[Class[_]] = { - Some(Thread.currentThread.getContextClassLoader.loadClass(hint)) + hintsToClass.get(hint).orElse { + val clazz = Thread.currentThread.getContextClassLoader.loadClass(hint) + hintsToClass.putIfAbsent(hint, clazz).orElse(Some(clazz)) + } } } From edccda4ee1b58fb734a1795992299088bb80b07f Mon Sep 17 00:00:00 2001 From: Allan Renucci Date: Sat, 13 May 2017 18:38:48 +0200 Subject: [PATCH 1528/1949] Bump scala versions --- .travis.yml | 4 ++-- build.sbt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index f5cf0afccf..524d6c16bd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,8 @@ language: scala scala: - - 2.11.8 - - 2.12.1 + - 2.11.11 + - 2.12.2 cache: directories: diff --git a/build.sbt b/build.sbt index c2b30084f5..797b308195 100644 --- a/build.sbt +++ b/build.sbt @@ -12,9 +12,9 @@ startYear in ThisBuild := Some(2006) organizationName in ThisBuild := "WorldWide Conferencing, LLC" -scalaVersion in ThisBuild := "2.12.1" +scalaVersion in ThisBuild := "2.12.2" -crossScalaVersions in ThisBuild := Seq("2.12.1", "2.11.7") +crossScalaVersions in ThisBuild := Seq("2.12.2", "2.11.11") libraryDependencies in ThisBuild <++= scalaVersion {sv => Seq(specs2, specs2Matchers, specs2Mock, scalacheck, scalatest) } From e4123bd5e7b980e3a61a75039eb8084f8d90b168 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Tue, 16 May 2017 22:01:12 -0400 Subject: [PATCH 1529/1949] Add Double in the name of the DoubleRenderer singletons. --- core/json/src/main/scala/net/liftweb/json/JsonAST.scala | 8 ++++---- .../test/scala/net/liftweb/json/JsonPrintingSpec.scala | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/core/json/src/main/scala/net/liftweb/json/JsonAST.scala b/core/json/src/main/scala/net/liftweb/json/JsonAST.scala index 8459b218e1..f0ded8c31a 100644 --- a/core/json/src/main/scala/net/liftweb/json/JsonAST.scala +++ b/core/json/src/main/scala/net/liftweb/json/JsonAST.scala @@ -897,7 +897,7 @@ object JsonAST { * * Usage is not recommended. */ - case object RenderSpecialValuesAsIs extends DoubleRenderer { + case object RenderSpecialDoubleValuesAsIs extends DoubleRenderer { def apply(double: Double): String = { double.toString } @@ -907,7 +907,7 @@ object JsonAST { * `Infinity` as `null`. Other doubles are rendered normally using * `toString`. */ - case object RenderSpecialValuesAsNull extends DoubleRenderer { + case object RenderSpecialDoubleValuesAsNull extends DoubleRenderer { def apply(double: Double): String = { if (double.isNaN || double.isInfinity) { "null" @@ -921,7 +921,7 @@ object JsonAST { * special values `NaN`, `-Infinity`, and `Infinity` are encountered. Other * doubles are rendered normally using `toString`. */ - case object FailToRenderSpecialValues extends DoubleRenderer { + case object FailToRenderSpecialDoubleValues extends DoubleRenderer { def apply(double: Double): String = { if (double.isNaN || double.isInfinity) { throw new IllegalArgumentException(s"Double value $double is not renderable to JSON.") @@ -948,7 +948,7 @@ object JsonAST { indent: Int, escapeChars: Set[Char] = Set(), spaceAfterFieldName: Boolean = false, - doubleRenderer: DoubleRenderer = RenderSpecialValuesAsNull + doubleRenderer: DoubleRenderer = RenderSpecialDoubleValuesAsNull ) { val lineBreaks_? = indent > 0 } diff --git a/core/json/src/test/scala/net/liftweb/json/JsonPrintingSpec.scala b/core/json/src/test/scala/net/liftweb/json/JsonPrintingSpec.scala index f04fd1afab..c864da01dc 100644 --- a/core/json/src/test/scala/net/liftweb/json/JsonPrintingSpec.scala +++ b/core/json/src/test/scala/net/liftweb/json/JsonPrintingSpec.scala @@ -59,7 +59,7 @@ object JsonPrintingSpec extends Specification with JValueGen with ScalaCheck { def render(json: JValue) = { JsonAST.render( json, - JsonAST.RenderSettings(0, doubleRenderer = JsonAST.RenderSpecialValuesAsIs) + JsonAST.RenderSettings(0, doubleRenderer = JsonAST.RenderSpecialDoubleValuesAsIs) ) } @@ -85,7 +85,7 @@ object JsonPrintingSpec extends Specification with JValueGen with ScalaCheck { def render(json: JValue) = { JsonAST.render( json, - JsonAST.RenderSettings(0, doubleRenderer = JsonAST.FailToRenderSpecialValues) + JsonAST.RenderSettings(0, doubleRenderer = JsonAST.FailToRenderSpecialDoubleValues) ) } From 158a19f9d8e2b909ab65b0208f4816527c90577c Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Tue, 16 May 2017 22:01:28 -0400 Subject: [PATCH 1530/1949] Clarify why we can't render special double values when they're off. Specifically, mention that it's the current double renderer that can't render them. --- core/json/src/main/scala/net/liftweb/json/JsonAST.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/json/src/main/scala/net/liftweb/json/JsonAST.scala b/core/json/src/main/scala/net/liftweb/json/JsonAST.scala index f0ded8c31a..ea5e54125e 100644 --- a/core/json/src/main/scala/net/liftweb/json/JsonAST.scala +++ b/core/json/src/main/scala/net/liftweb/json/JsonAST.scala @@ -924,7 +924,7 @@ object JsonAST { case object FailToRenderSpecialDoubleValues extends DoubleRenderer { def apply(double: Double): String = { if (double.isNaN || double.isInfinity) { - throw new IllegalArgumentException(s"Double value $double is not renderable to JSON.") + throw new IllegalArgumentException(s"Double value $double cannot be rendered to JSON with the current DoubleRenderer.") } else { double.toString } From 03287c7ca6adf4cbfd33100fdae817c87de243bc Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Tue, 16 May 2017 22:01:54 -0400 Subject: [PATCH 1531/1949] Fix wording for FailToRenderSpecialDoubleValues specs. They were... Straight up wrong <_< --- .../src/test/scala/net/liftweb/json/JsonPrintingSpec.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/json/src/test/scala/net/liftweb/json/JsonPrintingSpec.scala b/core/json/src/test/scala/net/liftweb/json/JsonPrintingSpec.scala index c864da01dc..29ac470c1b 100644 --- a/core/json/src/test/scala/net/liftweb/json/JsonPrintingSpec.scala +++ b/core/json/src/test/scala/net/liftweb/json/JsonPrintingSpec.scala @@ -94,15 +94,15 @@ object JsonPrintingSpec extends Specification with JValueGen with ScalaCheck { render(JDouble(double)) must_== double.toString } - "render positive infinity as null" in { + "throw an exception when attempting to render positive infinity" in { render(JDouble(Double.PositiveInfinity)) must throwAn[IllegalArgumentException] } - "render negative infinity as null" in { + "throw an exception when attempting to render negative infinity" in { render(JDouble(Double.NegativeInfinity)) must throwAn[IllegalArgumentException] } - "render NaN as null" in { + "throw an exception when attempting to render NaN" in { render(JDouble(Double.NaN)) must throwAn[IllegalArgumentException] } } From 05c8cd1ee0ffa363a4d5a218577a1171916db75d Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Tue, 16 May 2017 22:04:17 -0400 Subject: [PATCH 1532/1949] Add FIXME about removing finally block in next major version. We've put the request resume scheduling in a finally block so it will run even if the request suspend fails. This is strictly speaking incorrect---we shouldn't attempt to process the request if we fail to suspend it altogether, since this also means it will fail to resume. However, there could be consumer code that relies on requests to trigger their processing even if request suspension fails, since that is how it worked before. As such, we mark this with a FIXME to adjust the behavior to the "correct" behavior in the next major version, but keep the behaviorally-similar version for the time being. --- web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala index 9c00e4dfbe..8c03480825 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala @@ -117,6 +117,10 @@ class LiftServlet extends Loggable { try { reqOrg.request.suspend(cometTimeout) } finally { + // FIXME This should be removed from the finally block in the next major + // FIXME Lift version; it is currently here to ensure any code that + // FIXME depends on a response handler running even if requests suspension + // FIXME fails continues to work as-is during the 3.x series. runFunction(liftResponse => { // do the actual write on a separate thread Schedule.schedule(() => { From 58411fed55358b70380f97d1e203fa8c37093124 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 17 May 2017 10:10:34 -0400 Subject: [PATCH 1533/1949] Improve API compatibility of OnDiskFileParamHolder. When we shifted to using java.nio.file.* classes, we made localFile on OnDiskFileParamHolder a java.nio.file.Path instead of a java.io.File. Additionally, though we provided an apply overload that took a File and converted to a Path, we didn't provide a *constructor* overload that did the same. We unify the constructor and apply overload, and we change localFile to localPath. We also add a localFile accessor that converts the localPath to a java.io.File for compatibility. --- .../src/main/scala/net/liftweb/http/Req.scala | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/Req.scala b/web/webkit/src/main/scala/net/liftweb/http/Req.scala index 3a818a17a0..75df13d62d 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Req.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Req.scala @@ -284,14 +284,20 @@ FileParamHolder(name, mimeType, fileName) * @param localFile The local copy of the uploaded file */ class OnDiskFileParamHolder(override val name: String, override val mimeType: String, - override val fileName: String, val localFile: Path) extends -FileParamHolder(name, mimeType, fileName) + override val fileName: String, val localPath: Path) extends + FileParamHolder(name, mimeType, fileName) { + def this(name: String, mimeType: String, fileName: String, localFile: File) = { + this(name, mimeType, fileName, localFile.toPath) + } + + def localFile: File = localPath.toFile + /** * Returns an input stream that can be used to read the * contents of the uploaded file. */ - def fileStream: InputStream = Files.newInputStream(localFile) + def fileStream: InputStream = Files.newInputStream(localPath) /** * Returns the contents of the uploaded file as a Byte array. @@ -301,10 +307,10 @@ FileParamHolder(name, mimeType, fileName) /** * Returns the length of the uploaded file. */ - def length : Long = if (localFile == null) 0 else Files.size(localFile) + def length : Long = if (localPath == null) 0 else Files.size(localPath) protected override def finalize { - tryo(Files.delete(localFile)) + tryo(Files.delete(localPath)) } } From 39aecb19a1f6f1bf752726d325141feb4e0455f0 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Thu, 18 May 2017 09:18:33 -0400 Subject: [PATCH 1534/1949] Clarify lift-json can't parse special doubles. --- core/json/src/main/scala/net/liftweb/json/JsonAST.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/json/src/main/scala/net/liftweb/json/JsonAST.scala b/core/json/src/main/scala/net/liftweb/json/JsonAST.scala index ea5e54125e..6edc5da6c6 100644 --- a/core/json/src/main/scala/net/liftweb/json/JsonAST.scala +++ b/core/json/src/main/scala/net/liftweb/json/JsonAST.scala @@ -891,7 +891,8 @@ object JsonAST { } /** * A `DoubleRenderer` that renders special values `NaN`, `-Infinity`, and - * `Infinity` as-is using `toString`. This is not strictly valid JSON, but + * `Infinity` as-is using `toString`. This is not valid JSON, meaning JSON + * libraries generally won't be able to parse it (including lift-json!), but * JavaScript can eval it. Other double values are also rendered the same * way. * From 5d633b9280f5d3c1609b8409d078ada96114c6cc Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Thu, 25 May 2017 12:51:37 -0400 Subject: [PATCH 1535/1949] Reword the request resumption finally FIXME. Its wording was a bit hard to follow. --- .../src/main/scala/net/liftweb/http/LiftServlet.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala index 8c03480825..cd90dd1f2c 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala @@ -118,9 +118,9 @@ class LiftServlet extends Loggable { reqOrg.request.suspend(cometTimeout) } finally { // FIXME This should be removed from the finally block in the next major - // FIXME Lift version; it is currently here to ensure any code that - // FIXME depends on a response handler running even if requests suspension - // FIXME fails continues to work as-is during the 3.x series. + // FIXME Lift version; it is currently here to ensure code continues to + // FIXME work as-is during the 3.x series even if it depends on response + // FIXME handlers running when request suspension fails. runFunction(liftResponse => { // do the actual write on a separate thread Schedule.schedule(() => { From e358a70f6f62ab1f2d621cbe5aa971e3a6fff8cf Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Thu, 25 May 2017 16:01:05 -0400 Subject: [PATCH 1536/1949] Spec cleanup in lift-common. Nuke some warnings and whitespace. --- .../scala/net/liftweb/common/HListSpec.scala | 4 ++- .../net/liftweb/common/LoggingSpec.scala | 26 +++++++++---------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/core/common/src/test/scala/net/liftweb/common/HListSpec.scala b/core/common/src/test/scala/net/liftweb/common/HListSpec.scala index dae1638cb7..8438aad19b 100644 --- a/core/common/src/test/scala/net/liftweb/common/HListSpec.scala +++ b/core/common/src/test/scala/net/liftweb/common/HListSpec.scala @@ -81,6 +81,8 @@ object HListSpec extends Specification { success } + + case Right(_) => failure case Left(_) => failure } } @@ -90,7 +92,7 @@ object HListSpec extends Specification { import HLists._ val res = for { - a :+: one :+: lst :+: _ <- + a :+: one :+: lst :+: _ <- (Full("a") ?~ "Yak" :&: Full(1) :&: Full(List(1,2,3))) ?~! "Dude" } yield a.length * one * lst.foldLeft(1)(_ * _) diff --git a/core/common/src/test/scala/net/liftweb/common/LoggingSpec.scala b/core/common/src/test/scala/net/liftweb/common/LoggingSpec.scala index 8f1d9fe972..2df3966e9e 100644 --- a/core/common/src/test/scala/net/liftweb/common/LoggingSpec.scala +++ b/core/common/src/test/scala/net/liftweb/common/LoggingSpec.scala @@ -37,27 +37,27 @@ object LoggingSpec extends Specification { (new MyTopClass).x must_== 1 MyTopObj.x must_==1 } - + "be nested in object" in { object MyObj extends Loggable { logger.info("nested Hello") val x = 2 } - + MyObj.x must_== 2 - + } - + "create named loggers" in { val logger = Logger("MyLogger") - + logger.info("Logged with my named logger") success } - + "log static MDC values" in { val logger = Logger("StaticMDC") - + logger.info("Logged with no MDC") MDC.put("mdc1" -> (1,2)) logger.info("Logged with mdc1=(1,2)") @@ -71,17 +71,17 @@ object LoggingSpec extends Specification { logger.info("Logged with no MDC") success } - + "save MDC context with logWith" in { val logger = Logger("logWith") - + logger.info("Logged with no MDC") MDC.put("mdc1" -> (1,2), "mdc2" -> "yy") logger.info("Logged with mdc1=(1,2), mdc2=yy") Logger.logWith("mdc2" -> "xx") { logger.info("Logged with mdc1=(1,2), mdc2=xx") Logger.logWith("mdc1" -> 99) { - logger.info("Logged with mdc1=99, mdc2=xx") + logger.info("Logged with mdc1=99, mdc2=xx") } logger.info("Logged with mdc1=(1,2), mdc2=xx") } @@ -97,7 +97,7 @@ object LoggingSpec extends Specification { trace("result",l.foldLeft(0)(trace("lhs",_) + trace("rhs",_))) must_== l.foldLeft(0)(_+_) val x = 1 } - MyObj.x + MyObj success } @@ -106,12 +106,12 @@ object LoggingSpec extends Specification { First.info("In first") } object First extends Logger - + trait Second { private val logger = Logger(classOf[Second]) logger.info("In second") } - + class C extends First with Second with Logger { info("In C") val x = 2 From c75ab55835a0b562ce000d4cbe4be5b079652e0f Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Thu, 25 May 2017 16:01:25 -0400 Subject: [PATCH 1537/1949] Add BoxLogging and spec. BoxLogging provides helpers for logging boxes and then continuing to operate on the box, alloing logging to become a fluent part of the box interaction. It provides methods that can allow for logging empties and failures, or just failures. It also allows logging these to different log levels, and allows for different adapters to be applied. For example, one could write a snippet: class MySnippet(id: String) extends LoggableBoxLogging { def render = { "#content" #> { Content.getById(id).flatMap { content => content.text.asHtml // assume this returns Box[NodeSeq] }.infoLogEmptyBox(s"Failed to retrieve content HTML for $id") } } When this snippet gets rendered, if the provided content lookup fails, or if the conversion to `NodeSeq` fails, there will be an INFO level log that will capture this failure with the provided message prefix. BoxLogging provides handling for arbitrary-depth failure chains, as well as providing logging for failure params, thus ensuring all of the information in a failure will be logged. It also correctly passes any captured Throwable to the logger methods, provided that the adapter handles them correctly. Two adapters are provided out of the box: `LoggableBoxLogging`, which can be mixed in as above and is based on Lift's `Loggable`, and `SLF4JBoxLogging`, which is based on the raw SLF4J interface, and requires a protected `logger` `var` with an SLF4J logger to be available. Lastly, for simplicity, you can import net.liftweb.common.BoxLogging._ to have the logging helpers available without mixing anything else into your classes. --- .../scala/net/liftweb/common/BoxLogging.scala | 304 ++++++++++++ .../net/liftweb/common/BoxLoggingSpec.scala | 454 ++++++++++++++++++ 2 files changed, 758 insertions(+) create mode 100644 core/common/src/main/scala/net/liftweb/common/BoxLogging.scala create mode 100644 core/common/src/test/scala/net/liftweb/common/BoxLoggingSpec.scala diff --git a/core/common/src/main/scala/net/liftweb/common/BoxLogging.scala b/core/common/src/main/scala/net/liftweb/common/BoxLogging.scala new file mode 100644 index 0000000000..6aae0a13c7 --- /dev/null +++ b/core/common/src/main/scala/net/liftweb/common/BoxLogging.scala @@ -0,0 +1,304 @@ +/* + * Copyright 2007-2017 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 common + +/** + * Mix this trait in to get some low-cost implicits for logging boxes + * easily. The consumer will need to implement a `[[logBoxError]]` + * method to log messages with an optional `Throwable`, as well as its related + * friends for trace, debug, info, and warn levels. This allows abstracting out + * where and what the actual logger is. + * + * With this mixed in, boxes will have `[[logFailure]]` and + * `[[logFailure]]` methods. The first logs all `[[Failure]]`s as well + * as `[[Empty]]`. The second logs only `Failure`s and + * `[[ParamFailure]]`s, treating `Empty` as a valid value. These both log their + * respective items at ERROR level. You can also use `traceLog*`, `debugLog*`, + * `infoLog*`, and `warnLog*` if you want to log at other levels (e.g., you can + * use `infoLogFailure` to log an `Empty` or `Failure` at `INFO` level). + * + * All of these return the box unchanged, so you can continue to use it + * in `for` comprehensions, call `openOr` on it, etc. + * + * There is an implementation for anyone who wants to use Lift's + * `[[Loggable]]` trait called `[[LoggableBoxLogging]]`. Another implementaiton + * is available for use with a plain SLF4J logger, `SLF4JBoxLogging`. You can + * also implement a version for any other logging adapter. Lastly, you can + * simply import `BoxLogging._` to get the methods available at a top level; + * however, note that using them this way will lose information about where the + * log message came from. + * + * Here is an example of how you might use this in system that executes a third- + * party service and notifies another system of failures. + * + * {{{ + * val systemResult: Box[ServiceReturn] = + * system + * .executeService(true, requester) + * .map(...) + * .logFailure("Failed to execute service") match { + * case Full(content) => + * content + * case failure: Failure => + * otherSystem.notifyFailure(failure) + * case Empty => + * otherSystem.notifyFailure(Failure("No idea what happened.")) + * } + * }}} + */ +trait BoxLogging { + private[this] def logBoxError(message: String): Unit = { + logBoxError(message, None) + } + private[this] def logBoxWarn(message: String): Unit = { + logBoxWarn(message, None) + } + private[this] def logBoxInfo(message: String): Unit = { + logBoxInfo(message, None) + } + private[this] def logBoxDebug(message: String): Unit = { + logBoxDebug(message, None) + } + private[this] def logBoxTrace(message: String): Unit = { + logBoxTrace(message, None) + } + + /** + * Called with an error message and possibly a throwable that caused + * the error in question. Should ERROR log the message and the throwable. + * + * Exists in order to abstract away logger abstractions. Abstractception, + * as it were. + */ + protected def logBoxError(message: String, throwable: Option[Throwable]): Unit + /** + * Called with a warn message and possibly a throwable that caused the issue + * in question. Should WARN log the message and the throwable. + * + * Exists in order to abstract away logger abstractions. Abstractception, + * as it were. + */ + protected def logBoxWarn(message: String, throwable: Option[Throwable]): Unit + /** + * Called with an info message and possibly a throwable that caused the issue + * in question. Should INFO log the message and the throwable. + * + * Exists in order to abstract away logger abstractions. Abstractception, + * as it were. + */ + protected def logBoxInfo(message: String, throwable: Option[Throwable]): Unit + /** + * Called with a debug message and possibly a throwable that caused the issue + * in question. Should DEBUG log the message and the throwable. + * + * Exists in order to abstract away logger abstractions. Abstractception, + * as it were. + */ + protected def logBoxDebug(message: String, throwable: Option[Throwable]): Unit + /** + * Called with a trace message and possibly a throwable that caused the issue + * in question. Should TRACE log the message and the throwable. + * + * Exists in order to abstract away logger abstractions. Abstractception, + * as it were. + */ + protected def logBoxTrace(message: String, throwable: Option[Throwable]): Unit + + implicit class LogEmptyOrFailure[T](val box: Box[T]) extends AnyRef { + private def doLog(message: String, logFn: (String, Option[Throwable])=>Unit, onEmpty: ()=>Unit) = { + box match { + case ParamFailure(failureMessage, exception, Full(chain), param) => + logFn(s"$message: $failureMessage with param $param", exception) + chain.logFailure(s"$failureMessage with param $param caused by", logFn) + + case ParamFailure(failureMessage, exception, _, param) => + logFn(s"$message: $failureMessage with param $param", exception) + + case Failure(failureMessage, exception, Full(chain)) => + logFn(s"$message: $failureMessage", exception) + chain.logFailure(s"$failureMessage caused by", logFn) + + case Failure(failureMessage, exception, _) => + logFn(s"$message: $failureMessage", exception) + + case Empty => + onEmpty() + + case _: Full[_] => // nothing to log + } + } + + private def logFailure(message: String, logFn: (String, Option[Throwable])=>Unit): Unit = { + doLog(message, logFn, ()=>logFn(s"$message: Box was Empty.", None)) + } + + def logEmptyBox(message: String): Box[T] = { + doLog(message, logBoxError, ()=>logBoxError(s"$message: Box was Empty.")) + box + } + + def warnLogEmptyBox(message: String): Box[T] = { + doLog(message, logBoxWarn, ()=>logBoxWarn(s"$message: Box was Empty.")) + box + } + + def infoLogEmptyBox(message: String): Box[T] = { + doLog(message, logBoxInfo, ()=>logBoxInfo(s"$message: Box was Empty.")) + box + } + + def debugLogEmptyBox(message: String): Box[T] = { + doLog(message, logBoxDebug, ()=>logBoxDebug(s"$message: Box was Empty.")) + box + } + + def traceLogEmptyBox(message: String): Box[T] = { + doLog(message, logBoxTrace, ()=>logBoxTrace(s"$message: Box was Empty.")) + box + } + + def logFailure(message: String): Box[T] = { + doLog(message, logBoxError, ()=>()) + box + } + + def warnLogFailure(message: String): Box[T] = { + doLog(message, logBoxWarn, ()=>()) + box + } + + def infoLogFailure(message: String): Box[T] = { + doLog(message, logBoxInfo, ()=>()) + box + } + + def debugLogFailure(message: String): Box[T] = { + doLog(message, logBoxDebug, ()=>()) + box + } + + def traceLogFailure(message: String): Box[T] = { + doLog(message, logBoxTrace, ()=>()) + box + } + } +} + +/** + * A version of `[[BoxLogging]]` with a default implementation of + * `logBoxError` that logs to the logger provided by Lift's + * `[[Loggable]]`. + */ +trait LoggableBoxLogging extends BoxLogging with Loggable { + protected def logBoxError(message: String, throwable: Option[Throwable]) = { + throwable match { + case Some(throwable) => + logger.error(message, throwable) + case _ => + logger.error(message) + } + } + protected def logBoxWarn(message: String, throwable: Option[Throwable]) = { + throwable match { + case Some(throwable) => + logger.warn(message, throwable) + case _ => + logger.warn(message) + } + } + protected def logBoxInfo(message: String, throwable: Option[Throwable]) = { + throwable match { + case Some(throwable) => + logger.info(message, throwable) + case _ => + logger.info(message) + } + } + protected def logBoxDebug(message: String, throwable: Option[Throwable]) = { + throwable match { + case Some(throwable) => + logger.debug(message, throwable) + case _ => + logger.debug(message) + } + } + protected def logBoxTrace(message: String, throwable: Option[Throwable]) = { + throwable match { + case Some(throwable) => + // Below, disambiguate between the trace helper for tracing a value and + // the one that takes a throwable. + logger.trace(message: AnyRef, throwable) + case _ => + logger.trace(message) + } + } +} + +trait SLF4JBoxLogging extends BoxLogging { + protected val logger: org.slf4j.Logger + + protected def logBoxError(message: String, throwable: Option[Throwable]) = { + throwable match { + case Some(throwable) => + logger.error(message, throwable) + case _ => + logger.error(message) + } + } + protected def logBoxWarn(message: String, throwable: Option[Throwable]) = { + throwable match { + case Some(throwable) => + logger.warn(message, throwable) + case _ => + logger.warn(message) + } + } + protected def logBoxInfo(message: String, throwable: Option[Throwable]) = { + throwable match { + case Some(throwable) => + logger.info(message, throwable) + case _ => + logger.info(message) + } + } + protected def logBoxDebug(message: String, throwable: Option[Throwable]) = { + throwable match { + case Some(throwable) => + logger.debug(message, throwable) + case _ => + logger.debug(message) + } + } + protected def logBoxTrace(message: String, throwable: Option[Throwable]) = { + throwable match { + case Some(throwable) => + logger.trace(message, throwable) + case _ => + logger.trace(message) + } + } +} + +/** + * A convenience singleton for `[[BoxLogging]]` that makes `logFailure` + * and `logFailure` available for all code after it's been imported + * as `import com.elemica.common.BoxLogging._`. Logging done this way + * will come from `BoxLogging`, not from the class where the box was + * logged. + */ +object BoxLogging extends LoggableBoxLogging diff --git a/core/common/src/test/scala/net/liftweb/common/BoxLoggingSpec.scala b/core/common/src/test/scala/net/liftweb/common/BoxLoggingSpec.scala new file mode 100644 index 0000000000..f463dfb1e8 --- /dev/null +++ b/core/common/src/test/scala/net/liftweb/common/BoxLoggingSpec.scala @@ -0,0 +1,454 @@ +package net.liftweb +package common + +import org.slf4j.{Logger=>SLF4JLogger} + +import org.specs2.mock.Mockito +import org.specs2.mutable.Specification + +object BoxLoggingSpec extends Specification with Mockito { + class MockBoxLoggingClass extends BoxLogging { + var loggedErrors = List[(String, Option[Throwable])]() + var loggedWarns = List[(String, Option[Throwable])]() + var loggedInfos = List[(String, Option[Throwable])]() + var loggedDebugs = List[(String, Option[Throwable])]() + var loggedTraces = List[(String, Option[Throwable])]() + + protected def logBoxError(message: String, throwable: Option[Throwable]): Unit = { + loggedErrors ::= (message, throwable) + } + protected def logBoxWarn(message: String, throwable: Option[Throwable]): Unit = { + loggedWarns ::= (message, throwable) + } + protected def logBoxInfo(message: String, throwable: Option[Throwable]): Unit = { + loggedInfos ::= (message, throwable) + } + protected def logBoxDebug(message: String, throwable: Option[Throwable]): Unit = { + loggedDebugs ::= (message, throwable) + } + protected def logBoxTrace(message: String, throwable: Option[Throwable]): Unit = { + loggedTraces ::= (message, throwable) + } + } + + "BoxLogging" should { + "when logging empty boxes" in { + def verifyContentList(list: List[(String, Option[Throwable])]) = { + list must beLike { + case (paramFailure4, None) :: + (paramFailure3, None) :: + (paramFailure2, None) :: + (paramFailure1, Some(paramExp)) :: + (chained1, None) :: + (chained2, None) :: + (level1, None) :: + (level2, Some(exp2)) :: + (level3, None) :: + (level4, Some(exp4)) :: + (emptyMessage, None) :: + (fullParamMessage, Some(paramException)) :: + (paramMessage, None) :: + (exceptedMessage, Some(failureException)) :: + (failureMessage, None) :: + Nil => + (failureMessage must startWith("Second")) and + (failureMessage must contain("Failed")) and + (exceptedMessage must startWith("Third")) and + (exceptedMessage must contain("Excepted")) and + (failureException.getMessage must_== "uh-oh") and + (paramMessage must startWith("Fourth")) and + (paramMessage must contain("ParamFailed")) and + (paramMessage must contain("BoxLoggingSpec")) and + (fullParamMessage must startWith("Fifth")) and + (fullParamMessage must contain("ParamExcepted")) and + (fullParamMessage must contain("BoxLoggingSpec")) and + (paramException.getMessage must_== "param uh-oh") and + (emptyMessage must startWith("Sixth")) and + (emptyMessage must contain("Empty")) and + (level1 must contain("Failure level 3 caused by: Failure level 4")) and + (level2 must contain("Failure level 2 caused by: Failure level 3")) and + (exp2 must beAnInstanceOf[IllegalArgumentException]) and + (level3 must contain("Failure level 1 caused by: Failure level 2")) and + (level4 must contain("Multilevel failure: Failure level 1")) and + (exp4 must beAnInstanceOf[NullPointerException]) and + (chained1 must contain("Chained failure caused by: Boom")) and + (chained2 must contain("Chain all failures: Chained failure")) and + (paramFailure4 must contain("Param Failure lvl 3 with param Param 3 caused by: Param Failure lvl 4 with param Param 4")) and + (paramFailure3 must contain("Failure lvl 2 caused by: Param Failure lvl 3 with param Param 3")) and + (paramFailure2 must contain("Param Failure lvl 1 with param Param 1 caused by: Failure lvl 2")) and + (paramFailure1 must contain("Param failure: Param Failure lvl 1 with param Param 1")) and + (paramExp must beAnInstanceOf[IllegalArgumentException]) + } + } + + "log correctly on ERROR level" in { + val results = + new MockBoxLoggingClass { + Full("Not empty").logEmptyBox("First") + Failure("Failed").logEmptyBox("Second") + Failure("Excepted", Full(new Exception("uh-oh")), Empty).logEmptyBox("Third") + ParamFailure("ParamFailed", this).logEmptyBox("Fourth") + ParamFailure( + "ParamExcepted", + Full(new Exception("param uh-oh")), + Empty, + this + ).logEmptyBox("Fifth") + (Empty).logEmptyBox("Sixth") + Failure( + "Failure level 1", Full(new NullPointerException), Full(Failure( + "Failure level 2", Empty, Full(Failure( + "Failure level 3", Full(new IllegalArgumentException), Full(Failure( + "Failure level 4" + ))) + )) + ) + ).logEmptyBox("Multilevel failure") + (Failure("Boom") ?~! "Chained failure").logEmptyBox("Chain all failures") + ParamFailure( + "Param Failure lvl 1", Full(new IllegalArgumentException), Full(Failure( + "Failure lvl 2", Empty, Full(ParamFailure( + "Param Failure lvl 3", Empty, Full(ParamFailure( + "Param Failure lvl 4", + "Param 4" + )), + "Param 3" + )) + )), + "Param 1" + ).logEmptyBox("Param failure") + } + + verifyContentList(results.loggedErrors) + } + + "log correctly on WARN level" in { + val results = + new MockBoxLoggingClass { + Full("Not empty").warnLogEmptyBox("First") + Failure("Failed").warnLogEmptyBox("Second") + Failure("Excepted", Full(new Exception("uh-oh")), Empty).warnLogEmptyBox("Third") + ParamFailure("ParamFailed", this).warnLogEmptyBox("Fourth") + ParamFailure( + "ParamExcepted", + Full(new Exception("param uh-oh")), + Empty, + this + ).warnLogEmptyBox("Fifth") + (Empty).warnLogEmptyBox("Sixth") + Failure( + "Failure level 1", Full(new NullPointerException), Full(Failure( + "Failure level 2", Empty, Full(Failure( + "Failure level 3", Full(new IllegalArgumentException), Full(Failure( + "Failure level 4" + ))) + )) + ) + ).warnLogEmptyBox("Multilevel failure") + (Failure("Boom") ?~! "Chained failure").warnLogEmptyBox("Chain all failures") + ParamFailure( + "Param Failure lvl 1", Full(new IllegalArgumentException), Full(Failure( + "Failure lvl 2", Empty, Full(ParamFailure( + "Param Failure lvl 3", Empty, Full(ParamFailure( + "Param Failure lvl 4", + "Param 4" + )), + "Param 3" + )) + )), + "Param 1" + ).warnLogEmptyBox("Param failure") + } + + verifyContentList(results.loggedWarns) + } + + "log correctly on INFO level" in { + val results = + new MockBoxLoggingClass { + Full("Not empty").infoLogEmptyBox("First") + Failure("Failed").infoLogEmptyBox("Second") + Failure("Excepted", Full(new Exception("uh-oh")), Empty).infoLogEmptyBox("Third") + ParamFailure("ParamFailed", this).infoLogEmptyBox("Fourth") + ParamFailure( + "ParamExcepted", + Full(new Exception("param uh-oh")), + Empty, + this + ).infoLogEmptyBox("Fifth") + (Empty).infoLogEmptyBox("Sixth") + Failure( + "Failure level 1", Full(new NullPointerException), Full(Failure( + "Failure level 2", Empty, Full(Failure( + "Failure level 3", Full(new IllegalArgumentException), Full(Failure( + "Failure level 4" + ))) + )) + ) + ).infoLogEmptyBox("Multilevel failure") + (Failure("Boom") ?~! "Chained failure").infoLogEmptyBox("Chain all failures") + ParamFailure( + "Param Failure lvl 1", Full(new IllegalArgumentException), Full(Failure( + "Failure lvl 2", Empty, Full(ParamFailure( + "Param Failure lvl 3", Empty, Full(ParamFailure( + "Param Failure lvl 4", + "Param 4" + )), + "Param 3" + )) + )), + "Param 1" + ).infoLogEmptyBox("Param failure") + } + + verifyContentList(results.loggedInfos) + } + + "log correctly on DEBUG level" in { + val results = + new MockBoxLoggingClass { + Full("Not empty").debugLogEmptyBox("First") + Failure("Failed").debugLogEmptyBox("Second") + Failure("Excepted", Full(new Exception("uh-oh")), Empty).debugLogEmptyBox("Third") + ParamFailure("ParamFailed", this).debugLogEmptyBox("Fourth") + ParamFailure( + "ParamExcepted", + Full(new Exception("param uh-oh")), + Empty, + this + ).debugLogEmptyBox("Fifth") + (Empty).debugLogEmptyBox("Sixth") + Failure( + "Failure level 1", Full(new NullPointerException), Full(Failure( + "Failure level 2", Empty, Full(Failure( + "Failure level 3", Full(new IllegalArgumentException), Full(Failure( + "Failure level 4" + ))) + )) + ) + ).debugLogFailure("Multilevel failure") + (Failure("Boom") ?~! "Chained failure").debugLogFailure("Chain all failures") + ParamFailure( + "Param Failure lvl 1", Full(new IllegalArgumentException), Full(Failure( + "Failure lvl 2", Empty, Full(ParamFailure( + "Param Failure lvl 3", Empty, Full(ParamFailure( + "Param Failure lvl 4", + "Param 4" + )), + "Param 3" + )) + )), + "Param 1" + ).debugLogFailure("Param failure") + } + + verifyContentList(results.loggedDebugs) + } + + "log correctly on TRACE level" in { + val results = + new MockBoxLoggingClass { + Full("Not empty").traceLogEmptyBox("First") + Failure("Failed").traceLogEmptyBox("Second") + Failure("Excepted", Full(new Exception("uh-oh")), Empty).traceLogEmptyBox("Third") + ParamFailure("ParamFailed", this).traceLogEmptyBox("Fourth") + ParamFailure( + "ParamExcepted", + Full(new Exception("param uh-oh")), + Empty, + this + ).traceLogEmptyBox("Fifth") + (Empty).traceLogEmptyBox("Sixth") + Failure( + "Failure level 1", Full(new NullPointerException), Full(Failure( + "Failure level 2", Empty, Full(Failure( + "Failure level 3", Full(new IllegalArgumentException), Full(Failure( + "Failure level 4" + ))) + )) + ) + ).traceLogEmptyBox("Multilevel failure") + (Failure("Boom") ?~! "Chained failure").traceLogEmptyBox("Chain all failures") + ParamFailure( + "Param Failure lvl 1", Full(new IllegalArgumentException), Full(Failure( + "Failure lvl 2", Empty, Full(ParamFailure( + "Param Failure lvl 3", Empty, Full(ParamFailure( + "Param Failure lvl 4", + "Param 4" + )), + "Param 3" + )) + )), + "Param 1" + ).traceLogEmptyBox("Param failure") + } + + verifyContentList(results.loggedTraces) + } + } + + "when logging only failures" in { + def verifyContentList(list: List[(String, Option[Throwable])]) = { + list must beLike { + case (fullParamMessage, Some(paramException)) :: + (paramMessage, None) :: + (exceptedMessage, Some(failureException)) :: + (failureMessage, None) :: + Nil => + (failureMessage must startWith("Second")) and + (failureMessage must contain("Failed")) and + (exceptedMessage must startWith("Third")) and + (exceptedMessage must contain("Excepted")) and + (failureException.getMessage must_== "uh-oh") and + (paramMessage must startWith("Fourth")) and + (paramMessage must contain("ParamFailed")) and + (paramMessage must contain("BoxLoggingSpec")) and + (fullParamMessage must startWith("Fifth")) and + (fullParamMessage must contain("ParamExcepted")) and + (fullParamMessage must contain("BoxLoggingSpec")) and + (paramException.getMessage must_== "param uh-oh") + } + } + + "log correctly on ERROR level" in { + val results = + new MockBoxLoggingClass { + Full("Not empty").logFailure("First") + Failure("Failed").logFailure("Second") + Failure("Excepted", Full(new Exception("uh-oh")), Empty).logFailure("Third") + ParamFailure("ParamFailed", this).logFailure("Fourth") + ParamFailure( + "ParamExcepted", + Full(new Exception("param uh-oh")), + Empty, + this + ).logFailure("Fifth") + (Empty).logFailure("Sixth") + } + + verifyContentList(results.loggedErrors) + } + + "log correctly on WARN level" in { + val results = + new MockBoxLoggingClass { + Full("Not empty").warnLogFailure("First") + Failure("Failed").warnLogFailure("Second") + Failure("Excepted", Full(new Exception("uh-oh")), Empty).warnLogFailure("Third") + ParamFailure("ParamFailed", this).warnLogFailure("Fourth") + ParamFailure( + "ParamExcepted", + Full(new Exception("param uh-oh")), + Empty, + this + ).warnLogFailure("Fifth") + (Empty).warnLogFailure("Sixth") + } + + verifyContentList(results.loggedWarns) + } + + "log correctly on INFO level" in { + val results = + new MockBoxLoggingClass { + Full("Not empty").infoLogFailure("First") + Failure("Failed").infoLogFailure("Second") + Failure("Excepted", Full(new Exception("uh-oh")), Empty).infoLogFailure("Third") + ParamFailure("ParamFailed", this).infoLogFailure("Fourth") + ParamFailure( + "ParamExcepted", + Full(new Exception("param uh-oh")), + Empty, + this + ).infoLogFailure("Fifth") + (Empty).infoLogFailure("Sixth") + } + + verifyContentList(results.loggedInfos) + } + + "log correctly on DEBUG level" in { + val results = + new MockBoxLoggingClass { + Full("Not empty").debugLogFailure("First") + Failure("Failed").debugLogFailure("Second") + Failure("Excepted", Full(new Exception("uh-oh")), Empty).debugLogFailure("Third") + ParamFailure("ParamFailed", this).debugLogFailure("Fourth") + ParamFailure( + "ParamExcepted", + Full(new Exception("param uh-oh")), + Empty, + this + ).debugLogFailure("Fifth") + (Empty).debugLogFailure("Sixth") + } + + verifyContentList(results.loggedDebugs) + } + + "log correctly on TRACE level" in { + val results = + new MockBoxLoggingClass { + Full("Not empty").traceLogFailure("First") + Failure("Failed").traceLogFailure("Second") + Failure("Excepted", Full(new Exception("uh-oh")), Empty).traceLogFailure("Third") + ParamFailure("ParamFailed", this).traceLogFailure("Fourth") + ParamFailure( + "ParamExcepted", + Full(new Exception("param uh-oh")), + Empty, + this + ).traceLogFailure("Fifth") + (Empty).traceLogFailure("Sixth") + } + + verifyContentList(results.loggedTraces) + } + } + + "when logging in a Loggable" in { + import net.liftweb.common.Logger + import org.slf4j.{Logger => SLF4JLogger} + + val mockLogger = mock[SLF4JLogger] + mockLogger.isErrorEnabled() returns true + + class MyLoggable extends LoggableBoxLogging { + override val logger = new Logger { + override protected def _logger = mockLogger + } + } + + "log to the Lift logger" in { + val result = + new MyLoggable { + Failure("Failed").logFailure("Second") + Failure("Excepted", Full(new Exception("uh-oh")), Empty).logFailure("Third") + } + + (there was one(mockLogger).error(any[String])) and + (there was one(mockLogger).error(any[String], any[Exception])) + } + } + + "when logging with in SLF4J context" in { + import org.slf4j.{Logger => SLF4JLogger} + + val mockLogger = mock[SLF4JLogger] + + class TestClass extends SLF4JBoxLogging { + val logger = mockLogger + } + + "log to the SLF4J logger" in { + new TestClass { + Failure("Failed").logFailure("Second") + Failure("Excepted", Full(new Exception("uh-oh")), Empty).logFailure("Third") + } + + there was one(mockLogger).error(any[String]) + there was one(mockLogger).error(any[String], any[Exception]) + } + } + } +} From 83f211665f1bfdd814ab8b5a0f491ceeac1e35a5 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Thu, 25 May 2017 17:15:03 -0400 Subject: [PATCH 1538/1949] Fix LAFuture.collect/collectAll when sub-futures fail. Before, this would cause the overall future to hang forever. We now fail the overall future if any contained future fails. We also refactor the way that collect and collectAll are written to go through a common path, which is also available for third-party use, and we add some extra specs around collect and collectAll behavior. --- .../scala/net/liftweb/actor/LAFuture.scala | 139 +++++++++++------- .../net/liftweb/actor/LAFutureSpec.scala | 93 +++++++++--- 2 files changed, 159 insertions(+), 73 deletions(-) diff --git a/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala b/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala index a176345406..3727f45606 100644 --- a/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala +++ b/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala @@ -17,6 +17,8 @@ package net.liftweb package actor +import scala.collection.mutable.ArrayBuffer + import common._ @@ -162,6 +164,11 @@ class LAFuture[T](val scheduler: LAScheduler = LAScheduler, context: Box[LAFutur */ def isAborted: Boolean = synchronized {aborted} + /** + * Has the future been satisfied or + */ + def isCompleted: Boolean = synchronized { ! (isSatisfied || isAborted) } + /** * Abort the future. It can never be satified */ @@ -333,32 +340,64 @@ object LAFuture { } } - /** - * Collect all the future values into the aggregate future - * The returned future will be satisfied when all the - * collected futures are satisfied + * Given handlers for a value's success and failure and a set of futures, runs + * the futures simultaneously and invokes either success or failure callbacks + * as each future completes. When all futures are complete, if the handlers + * have not either satisfied or failed the overall result, `allValuesCompleted` + * is called to complete it. If it *still* isn't complete, the overall result + * is failed with an error. + * + * Note that the success and failure functions are guaranteed to be run in a + * thread-safe manner. Each is passed the value, the result future, the + * accumulating `ArrayBuffer`, and the index of the future that has been + * completed. For the failure handler, the value is the `Box` of the failure. */ - def collect[T](future: LAFuture[T]*): LAFuture[List[T]] = { - val result = new LAFuture[List[T]] - if (future.isEmpty) { - result.satisfy(Nil) + def collect[T, A]( + valueSucceeded: (T, LAFuture[A], ArrayBuffer[Box[T]], Int)=>Unit, + valueFailed: (Box[Nothing], LAFuture[A], ArrayBuffer[Box[T]], Int)=>Unit, + allValuesCompleted: (LAFuture[A], ArrayBuffer[Box[T]])=>Unit, + futures: LAFuture[T]* + ): LAFuture[A] = { + val result = new LAFuture[A] + + if (futures.isEmpty) { + allValuesCompleted(result, new ArrayBuffer[Box[T]](0)) } else { val sync = new Object - val len = future.length - val vals = new collection.mutable.ArrayBuffer[Box[T]](len) + val len = futures.length + val accumulator = new ArrayBuffer[Box[T]](len) // pad array so inserts at random places are possible - for (i <- 0 to len) { vals.insert(i, Empty) } + for (i <- 0 to len) { accumulator.insert(i, Empty) } var gotCnt = 0 - future.toList.zipWithIndex.foreach { - case (f, idx) => - f.foreach { - v => sync.synchronized { - vals.insert(idx, Full(v)) + futures.toList.zipWithIndex.foreach { + case (future, index) => + future.onSuccess { + value => sync.synchronized { + gotCnt += 1 + valueSucceeded(value, result, accumulator, index) + + if (gotCnt >= len && ! result.complete_?) { + allValuesCompleted(result, accumulator) + + if (! result.complete_?) { + result.fail(Failure("collect invoker did not complete result")) + } + } + } + } + future.onFail { + failureBox => sync.synchronized { gotCnt += 1 - if (gotCnt >= len) { - result.satisfy(vals.toList.flatten) + valueFailed(failureBox, result, accumulator, index) + + if (gotCnt >= len && ! result.complete_?) { + allValuesCompleted(result, accumulator) + + if (! result.complete_?) { + result.fail(Failure("collect invoker did not complete result")) + } } } } @@ -368,6 +407,21 @@ object LAFuture { result } + + /** + * Collect all the future values into the aggregate future + * The returned future will be satisfied when all the + * collected futures are satisfied + */ + def collect[T](future: LAFuture[T]*): LAFuture[List[T]] = { + collect[T, List[T]]( + { (value, result, values, index) => values.insert(index, Full(value)) }, + { (valueBox, result, values, index) => result.fail(valueBox) }, + { (result, values) => result.satisfy(values.toList.flatten) }, + future: _* + ) + } + /** * Collect all the future values into the aggregate future * The returned future will be satisfied when all the @@ -376,40 +430,21 @@ object LAFuture { * returned future with an Empty */ def collectAll[T](future: LAFuture[Box[T]]*): LAFuture[Box[List[T]]] = { - val result = new LAFuture[Box[List[T]]] - if (future.isEmpty) { - result.satisfy(Full(Nil)) - } else { - val sync = new Object - val len = future.length - val vals = new collection.mutable.ArrayBuffer[Box[T]](len) - // pad array so inserts at random places are possible - for (i <- 0 to len) { vals.insert(i, Empty) } - var gotCnt = 0 - - future.toList.zipWithIndex.foreach { - case (f, idx) => - f.foreach { - vb => sync.synchronized { - vb match { - case Full(v) => { - vals.insert(idx, Full(v)) - gotCnt += 1 - if (gotCnt >= len) { - result.satisfy(Full(vals.toList.flatten)) - } - } - - case eb: EmptyBox => { - result.satisfy(eb) - } - } - } - } - } - } - - result + collect[Box[T], Box[List[T]]]( + { (value, result, values, index) => + value match { + case Full(realValue) => + values.insert(index, Full(Full(realValue))) + case other: EmptyBox => + result.satisfy(other) + } + }, + { (valueBox, result, values, index) => result.fail(valueBox) }, + { (result: LAFuture[Box[List[T]]], values: ArrayBuffer[Box[Box[T]]]) => + result.satisfy(Full(values.toList.flatten.flatten)) + }, + future: _* + ) } private def inContext[T](f: () => T, context: Box[LAFuture.Context]): () => T = { diff --git a/core/actor/src/test/scala/net/liftweb/actor/LAFutureSpec.scala b/core/actor/src/test/scala/net/liftweb/actor/LAFutureSpec.scala index 2a6178c8e0..f2afc7b927 100644 --- a/core/actor/src/test/scala/net/liftweb/actor/LAFutureSpec.scala +++ b/core/actor/src/test/scala/net/liftweb/actor/LAFutureSpec.scala @@ -1,6 +1,6 @@ package net.liftweb.actor -import net.liftweb.common.{Failure, Box} +import net.liftweb.common.{Box, Failure, Full} import org.specs2.mutable.Specification import java.util.concurrent.atomic.AtomicBoolean @@ -61,30 +61,81 @@ class LAFutureSpec extends Specification { result shouldEqual givenFailure } - "collect one future result" in { - val givenOneResult = 123 - val one = LAFuture(() => givenOneResult) - LAFuture.collect(one).get(timeout) shouldEqual List(givenOneResult) - } + "when collecting results with LAFuture.collect" in { + "collect one future result" in { + val givenOneResult = 123 + val one = LAFuture(() => givenOneResult) + LAFuture.collect(one).get(timeout) shouldEqual List(givenOneResult) + } - "collect more future results in correct order" in { - val givenOneResult = 123 - val givenTwoResult = 234 - val one = LAFuture(() => givenOneResult) - val two = LAFuture(() => givenTwoResult) - LAFuture.collect(one, two).get(timeout) shouldEqual List(givenOneResult, givenTwoResult) - } + "collect more future results in correct order" in { + val givenOneResult = 123 + val givenTwoResult = 234 + val one = LAFuture(() => givenOneResult) + val two = LAFuture(() => givenTwoResult) + LAFuture.collect(one, two).get(timeout) shouldEqual List(givenOneResult, givenTwoResult) + } + + "collect empty list immediately" in { + val collectResult = LAFuture.collect(Nil: _*) + collectResult.isSatisfied shouldEqual true + collectResult.get(timeout) shouldEqual Nil + } - "collect empty list immediately" in { - val collectResult = LAFuture.collect(Nil: _*) - collectResult.isSatisfied shouldEqual true - collectResult.get(timeout) shouldEqual Nil + "report a failed LAFuture as a failure for the overall future" in { + val one: LAFuture[Int] = new LAFuture + val two: LAFuture[Int] = LAFuture(() => 5) + + one.fail(Failure("boom boom boom!")) + + val collectResult = LAFuture.collect(one, two) + collectResult.get(timeout) shouldEqual Failure("boom boom boom!") + } } - "collectAll empty list immediately" in { - val collectResult = LAFuture.collectAll(Nil : _*) - collectResult.isSatisfied shouldEqual true - collectResult.get(timeout) shouldEqual Nil + "when collecting Boxed results with collectAll" in { + "collectAll collects an EmptyBox immediately" in { + val one: LAFuture[Box[Int]] = LAFuture(() => { Failure("whoops"): Box[Int] }) + val two: LAFuture[Box[Int]] = LAFuture(() => { Thread.sleep(10000); Full(1) }) + + val collectResult = LAFuture.collectAll(one, two) + collectResult.get(5000) shouldEqual Failure("whoops") + } + + "collectAll collects a set of Fulls" in { + val one: LAFuture[Box[Int]] = LAFuture(() => Full(1): Box[Int]) + val two: LAFuture[Box[Int]] = LAFuture(() => Full(2): Box[Int]) + val three: LAFuture[Box[Int]] = LAFuture(() => Full(3): Box[Int]) + + val collectResult = LAFuture.collectAll(one, two, three) + collectResult.get(timeout) shouldEqual Full(List(1, 2, 3)) + } + + "collectAll reports a failed LAFuture as a failure for the overall future" in { + val one: LAFuture[Box[Int]] = new LAFuture + val two: LAFuture[Box[Int]] = LAFuture(() => Full(5): Box[Int]) + + one.fail(Failure("boom boom boom!")) + + val collectResult = LAFuture.collectAll(one, two) + collectResult.get(timeout) shouldEqual Failure("boom boom boom!") + } + + "collectAll empty list immediately" in { + val collectResult = LAFuture.collectAll(Nil : _*) + collectResult.isSatisfied shouldEqual true + collectResult.get(timeout) shouldEqual Nil + } + + "report a failed LAFuture as a failure for the overall future" in { + val one: LAFuture[Box[Int]] = new LAFuture + val two: LAFuture[Box[Int]] = LAFuture(() => Full(5): Box[Int]) + + one.fail(Failure("boom boom boom!")) + + val collectResult = LAFuture.collectAll(one, two) + collectResult.get(timeout) shouldEqual Failure("boom boom boom!") + } } } From 3bfd982f7ca99b87cca76b69e0e1d977600f5276 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Thu, 25 May 2017 17:22:01 -0400 Subject: [PATCH 1539/1949] Harmonize status functions for LAFuture. complete_?, isAborted, and isSatisfied weren't particular symmetric. We now have completed_?, aborted_?, and satisfied_?, and for those who prefer them we also bring isCompleted, isAborted, and isSatisfied to the party. --- .../scala/net/liftweb/actor/LAFuture.scala | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala b/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala index 3727f45606..0c6e1171bc 100644 --- a/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala +++ b/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala @@ -154,20 +154,36 @@ class LAFuture[T](val scheduler: LAScheduler = LAScheduler, context: Box[LAFutur } } + /** + * Java-friendly alias for satisfied_?. + */ + def isSatisfied: Boolean = satisfied_? + /** * Has the future been satisfied */ - def isSatisfied: Boolean = synchronized {satisfied} + def satisfied_? = synchronized {satisfied} + /** + * Java-friendly alias for aborted_?. + */ + def isAborted: Boolean = aborted_? /** * Has the future been aborted */ - def isAborted: Boolean = synchronized {aborted} + def aborted_? = synchronized {satisfied} /** - * Has the future been satisfied or + * Java-friendly alias for completed_?. */ - def isCompleted: Boolean = synchronized { ! (isSatisfied || isAborted) } + def isCompleted: Boolean = complete_? + /** + * Has the future completed? + */ + def completed_? : Boolean = synchronized(satisfied || aborted) + + @deprecated("Please use completed_? instead.", "3.1.0") + def complete_? : Boolean = completed_? /** * Abort the future. It can never be satified @@ -247,11 +263,6 @@ class LAFuture[T](val scheduler: LAScheduler = LAScheduler, context: Box[LAFutur } } } - - /** - * Has the future completed? - */ - def complete_? : Boolean = synchronized(satisfied || aborted) } /** From b498ed698831b2c63ebc5e3f30851c6786966a24 Mon Sep 17 00:00:00 2001 From: Ari Gold Date: Fri, 9 Jun 2017 20:39:50 -0700 Subject: [PATCH 1540/1949] add test to buildDeferredFunction for missing request in session --- .../net/liftweb/http/LiftSessionSpec.scala | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/web/webkit/src/test/scala/net/liftweb/http/LiftSessionSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/LiftSessionSpec.scala index 755ca6fbb8..50673d5c08 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/LiftSessionSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/LiftSessionSpec.scala @@ -17,8 +17,10 @@ package net.liftweb package http +import scala.concurrent.ExecutionContext.Implicits.global import scala.xml.NodeSeq -import net.liftweb.common.{Full, Empty} +import net.liftweb.common.{Full, Empty, Failure} +import net.liftweb.util.Helpers.tryo import org.specs2.specification.BeforeEach import org.specs2.mutable.Specification @@ -90,4 +92,20 @@ object LiftSessionSpec extends Specification with BeforeEach { } } } + + "LiftSession when building deferred functions" should { + + "not fail when the underlying container request is null" in { + val session = new LiftSession("Test Session", "", Empty) + + def stubFunction: () => Int = () => 3 + + S.init(Full(Req.nil), session) { + + val attempt = tryo(session.buildDeferredFunction(stubFunction)) + + attempt.toOption must beSome + } + } + } } From 84b385385975fe2c532ee39fdd97a53c594c3cc2 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sun, 11 Jun 2017 17:35:32 -0400 Subject: [PATCH 1541/1949] Add special handling for UnavailableException This code adds special handling for javax.servelet.UnavailableException. This exception is an idiomatic way to signal a full abort to the Java Application server running the application. Please see #1843 for more information. --- .../liftweb/http/provider/HTTPProvider.scala | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/provider/HTTPProvider.scala b/web/webkit/src/main/scala/net/liftweb/http/provider/HTTPProvider.scala index abf75f7289..3def6eaa72 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/provider/HTTPProvider.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/provider/HTTPProvider.scala @@ -14,9 +14,9 @@ * limitations under the License. */ -package net.liftweb -package http -package provider +package net.liftweb +package http +package provider import net.liftweb.common._ import net.liftweb.util._ @@ -87,12 +87,29 @@ trait HTTPProvider { preBoot b.boot } catch { + // The UnavailableException is the idiomatic way to tell a Java application container that + // the boot process has gone horribly, horribly wrong. That _must_ bubble to the application + // container that is invoking the app. See https://github.com/lift/framework/issues/1843 + case unavailableException: javax.servlet.UnavailableException => + logger.error("------------------------------------------------------------------") + logger.error("------------------------------------------------------------------") + logger.error("------------------------------------------------------------------") + logger.error("------------------------------------------------------------------") + logger.error("********** Failed to Boot! An unavailable exception was thrown an all futher boot activities are aborted", unavailableException); + logger.error("------------------------------------------------------------------") + logger.error("------------------------------------------------------------------") + logger.error("------------------------------------------------------------------") + logger.error("------------------------------------------------------------------") + logger.error("------------------------------------------------------------------") + + throw unavailableException + case e: Exception => logger.error("------------------------------------------------------------------") logger.error("------------------------------------------------------------------") logger.error("------------------------------------------------------------------") logger.error("------------------------------------------------------------------") - logger.error("********** Failed to Boot! Your application may not run properly", e); + logger.error("********** Failed to Boot! Your application may not run properly", e); logger.error("------------------------------------------------------------------") logger.error("------------------------------------------------------------------") logger.error("------------------------------------------------------------------") From 1ba4cf5dea44317756b2530d61f60d8bc5c36cfa Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sun, 11 Jun 2017 17:55:37 -0400 Subject: [PATCH 1542/1949] Use a custom implicitNotFound error for CSS transforms --- core/util/src/main/scala/net/liftweb/util/CssSel.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/util/src/main/scala/net/liftweb/util/CssSel.scala b/core/util/src/main/scala/net/liftweb/util/CssSel.scala index 0585f7cb2c..6c7126865a 100644 --- a/core/util/src/main/scala/net/liftweb/util/CssSel.scala +++ b/core/util/src/main/scala/net/liftweb/util/CssSel.scala @@ -4,6 +4,7 @@ package util import common._ import xml._ import collection.mutable.ListBuffer +import scala.annotation.implicitNotFound /** * Created with IntelliJ IDEA. @@ -753,6 +754,7 @@ final class CssJBridge extends CssBindImplicits { } +@implicitNotFound("The value you're attempting to use on the right hand side of the #> operator can't be automatically converted to a (NodeSeq)=>NodeSeq function. Consider binding the individual members of the class you're trying to use, or define an implicit CanBind[T] that tells Lift how to convert this to a (NodeSeq)=>NodeSeq.") trait CanBind[-T] { def apply(it: => T)(ns: NodeSeq): Seq[NodeSeq] } From b73ab324eeb8a251e506f9292b54710d375ba589 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sun, 11 Jun 2017 18:16:45 -0400 Subject: [PATCH 1543/1949] Resolve some issues with swallowing exceptions in lift-json --- .../main/scala/net/liftweb/json/Extraction.scala | 13 ++++++++----- .../main/scala/net/liftweb/json/JsonParser.scala | 8 +++++++- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/core/json/src/main/scala/net/liftweb/json/Extraction.scala b/core/json/src/main/scala/net/liftweb/json/Extraction.scala index df7a4d2d17..a178bfdab2 100644 --- a/core/json/src/main/scala/net/liftweb/json/Extraction.scala +++ b/core/json/src/main/scala/net/liftweb/json/Extraction.scala @@ -265,11 +265,14 @@ object Extraction { val instance = jconstructor.newInstance(args.map(_.asInstanceOf[AnyRef]).toArray: _*) setFields(instance.asInstanceOf[AnyRef], json, jconstructor) } catch { - case e @ (_:IllegalArgumentException | _:InstantiationException) => - fail("Parsed JSON values do not match with class constructor\nargs=" + - args.mkString(",") + "\narg types=" + args.map(a => if (a != null) - a.asInstanceOf[AnyRef].getClass.getName else "null").mkString(",") + - "\nconstructor=" + jconstructor) + case exception: Exception => + exception match { + case matchedException @ (_:IllegalArgumentException | _:InstantiationException) => + fail("Parsed JSON values do not match with class constructor\nargs=" + + args.mkString(",") + "\narg types=" + args.map(a => if (a != null) + a.asInstanceOf[AnyRef].getClass.getName else "null").mkString(",") + + "\nconstructor=" + jconstructor, matchedException) + } } } diff --git a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala index 2a4be9a06a..975233cef9 100644 --- a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala +++ b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala @@ -261,7 +261,13 @@ object JsonParser { private def convert[A](x: Any, expectedType: Class[A]): A = { if (x == null) parser.fail("expected object or array") - try { x.asInstanceOf[A] } catch { case _: ClassCastException => parser.fail("unexpected " + x) } + + try { + x.asInstanceOf[A] + } catch { + case cce: ClassCastException => + parser.fail(s"failure during class conversion. I got $x but needed a type of $expectedType", cce) + } } def peekOption = if (stack.isEmpty) None else Some(stack.peek) From dd3362317f3bcef6074a7a5aee67108db3b2d218 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Mon, 12 Jun 2017 10:52:58 -0400 Subject: [PATCH 1544/1949] Fix wording in UnavailableException error message --- .../src/main/scala/net/liftweb/http/provider/HTTPProvider.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/provider/HTTPProvider.scala b/web/webkit/src/main/scala/net/liftweb/http/provider/HTTPProvider.scala index 3def6eaa72..9d2beb7de1 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/provider/HTTPProvider.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/provider/HTTPProvider.scala @@ -95,7 +95,7 @@ trait HTTPProvider { logger.error("------------------------------------------------------------------") logger.error("------------------------------------------------------------------") logger.error("------------------------------------------------------------------") - logger.error("********** Failed to Boot! An unavailable exception was thrown an all futher boot activities are aborted", unavailableException); + logger.error("********** Failed to Boot! An UnavailableException was thrown and all futher boot activities are aborted", unavailableException); logger.error("------------------------------------------------------------------") logger.error("------------------------------------------------------------------") logger.error("------------------------------------------------------------------") From 1a5982e60adcd4d992d993536fde5a33cca22caa Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Tue, 13 Jun 2017 10:26:54 -0400 Subject: [PATCH 1545/1949] Re-throw unmatched exceptions --- core/json/src/main/scala/net/liftweb/json/Extraction.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/json/src/main/scala/net/liftweb/json/Extraction.scala b/core/json/src/main/scala/net/liftweb/json/Extraction.scala index a178bfdab2..78f9edcba6 100644 --- a/core/json/src/main/scala/net/liftweb/json/Extraction.scala +++ b/core/json/src/main/scala/net/liftweb/json/Extraction.scala @@ -272,6 +272,9 @@ object Extraction { args.mkString(",") + "\narg types=" + args.map(a => if (a != null) a.asInstanceOf[AnyRef].getClass.getName else "null").mkString(",") + "\nconstructor=" + jconstructor, matchedException) + + case unmatchedException => + throw unmatchedException } } } From 4e7eeb1c8b5785e5d1bcb9422115bc260f3e4ace Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Wed, 14 Jun 2017 21:24:59 -0400 Subject: [PATCH 1546/1949] Improve error message when exceptions are thrown in constructor --- core/json/src/main/scala/net/liftweb/json/Extraction.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/json/src/main/scala/net/liftweb/json/Extraction.scala b/core/json/src/main/scala/net/liftweb/json/Extraction.scala index 78f9edcba6..9377cd6f4f 100644 --- a/core/json/src/main/scala/net/liftweb/json/Extraction.scala +++ b/core/json/src/main/scala/net/liftweb/json/Extraction.scala @@ -17,7 +17,7 @@ package net.liftweb package json -import java.lang.reflect.{Constructor => JConstructor, Type} +import java.lang.reflect.{Constructor => JConstructor, Type, InvocationTargetException} import java.lang.{Integer => JavaInteger, Long => JavaLong, Short => JavaShort, Byte => JavaByte, Boolean => JavaBoolean, Double => JavaDouble, Float => JavaFloat} import java.util.Date import java.sql.Timestamp @@ -273,6 +273,9 @@ object Extraction { a.asInstanceOf[AnyRef].getClass.getName else "null").mkString(",") + "\nconstructor=" + jconstructor, matchedException) + case exceptionThrownInConstructor: InvocationTargetException => + fail("An exception was thrown in the class constructor during extraction", exceptionThrownInConstructor) + case unmatchedException => throw unmatchedException } From 66a302f008a1f71a47cafbbe176c0ab307c5d2a9 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Wed, 14 Jun 2017 21:25:09 -0400 Subject: [PATCH 1547/1949] Add test coverage for exception behavior --- .../net/liftweb/json/ExtractionBugs.scala | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/core/json/src/test/scala/net/liftweb/json/ExtractionBugs.scala b/core/json/src/test/scala/net/liftweb/json/ExtractionBugs.scala index ea28d18381..9830da067d 100644 --- a/core/json/src/test/scala/net/liftweb/json/ExtractionBugs.scala +++ b/core/json/src/test/scala/net/liftweb/json/ExtractionBugs.scala @@ -168,4 +168,22 @@ object ExtractionBugs extends Specification { extracted must_== ("apple", "sammich", "bacon") } + + "throw the correct exceptions when things go wrong during extraction" in { + implicit val formats = DefaultFormats + + class Holder(bacon: String) { + throw new Exception("I'm an exception!") + } + + val correctArgsAstRepresentation: JObject = JObject(List( + JField("bacon", JString("apple")) + )) + + correctArgsAstRepresentation.extract[Holder] must throwA[MappingException].like { + case e => + e.getMessage mustEqual "An exception was thrown in the class constructor during extraction" + e.getCause.getCause.getMessage mustEqual "I'm an exception!" + } + } } From edf2dc860db806d6ce39f190abe6ea6bcbae14e4 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Thu, 15 Jun 2017 10:31:42 -0400 Subject: [PATCH 1548/1949] Rename LAFuture.collect[T,A] parameters in event passing style. Also use named parameters when invoking to clarify what's going on. --- .../scala/net/liftweb/actor/LAFuture.scala | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala b/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala index 0c6e1171bc..fed1911db0 100644 --- a/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala +++ b/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala @@ -355,7 +355,7 @@ object LAFuture { * Given handlers for a value's success and failure and a set of futures, runs * the futures simultaneously and invokes either success or failure callbacks * as each future completes. When all futures are complete, if the handlers - * have not either satisfied or failed the overall result, `allValuesCompleted` + * have not either satisfied or failed the overall result, `onAllFuturesCompleted` * is called to complete it. If it *still* isn't complete, the overall result * is failed with an error. * @@ -365,15 +365,15 @@ object LAFuture { * completed. For the failure handler, the value is the `Box` of the failure. */ def collect[T, A]( - valueSucceeded: (T, LAFuture[A], ArrayBuffer[Box[T]], Int)=>Unit, - valueFailed: (Box[Nothing], LAFuture[A], ArrayBuffer[Box[T]], Int)=>Unit, - allValuesCompleted: (LAFuture[A], ArrayBuffer[Box[T]])=>Unit, + onFutureSucceeded: (T, LAFuture[A], ArrayBuffer[Box[T]], Int)=>Unit, + onFutureFailed: (Box[Nothing], LAFuture[A], ArrayBuffer[Box[T]], Int)=>Unit, + onAllFuturesCompleted: (LAFuture[A], ArrayBuffer[Box[T]])=>Unit, futures: LAFuture[T]* ): LAFuture[A] = { val result = new LAFuture[A] if (futures.isEmpty) { - allValuesCompleted(result, new ArrayBuffer[Box[T]](0)) + onAllFuturesCompleted(result, new ArrayBuffer[Box[T]](0)) } else { val sync = new Object val len = futures.length @@ -387,10 +387,10 @@ object LAFuture { future.onSuccess { value => sync.synchronized { gotCnt += 1 - valueSucceeded(value, result, accumulator, index) + onFutureSucceeded(value, result, accumulator, index) if (gotCnt >= len && ! result.complete_?) { - allValuesCompleted(result, accumulator) + onAllFuturesCompleted(result, accumulator) if (! result.complete_?) { result.fail(Failure("collect invoker did not complete result")) @@ -401,10 +401,10 @@ object LAFuture { future.onFail { failureBox => sync.synchronized { gotCnt += 1 - valueFailed(failureBox, result, accumulator, index) + onFutureFailed(failureBox, result, accumulator, index) if (gotCnt >= len && ! result.complete_?) { - allValuesCompleted(result, accumulator) + onAllFuturesCompleted(result, accumulator) if (! result.complete_?) { result.fail(Failure("collect invoker did not complete result")) @@ -426,9 +426,11 @@ object LAFuture { */ def collect[T](future: LAFuture[T]*): LAFuture[List[T]] = { collect[T, List[T]]( - { (value, result, values, index) => values.insert(index, Full(value)) }, - { (valueBox, result, values, index) => result.fail(valueBox) }, - { (result, values) => result.satisfy(values.toList.flatten) }, + onFutureSucceeded = { (value, result, values, index) => + values.insert(index, Full(value)) + }, + onFutureFailed = { (valueBox, result, values, index) => result.fail(valueBox) }, + onAllFuturesCompleted = { (result, values) => result.satisfy(values.toList.flatten) }, future: _* ) } @@ -442,7 +444,7 @@ object LAFuture { */ def collectAll[T](future: LAFuture[Box[T]]*): LAFuture[Box[List[T]]] = { collect[Box[T], Box[List[T]]]( - { (value, result, values, index) => + onFutureSucceeded = { (value, result, values, index) => value match { case Full(realValue) => values.insert(index, Full(Full(realValue))) @@ -450,8 +452,8 @@ object LAFuture { result.satisfy(other) } }, - { (valueBox, result, values, index) => result.fail(valueBox) }, - { (result: LAFuture[Box[List[T]]], values: ArrayBuffer[Box[Box[T]]]) => + onFutureFailed = { (valueBox, result, values, index) => result.fail(valueBox) }, + onAllFuturesCompleted = { (result: LAFuture[Box[List[T]]], values: ArrayBuffer[Box[Box[T]]]) => result.satisfy(Full(values.toList.flatten.flatten)) }, future: _* From 7e4d8dc1b9ec9ea41537541c703f50acee63fcff Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Thu, 15 Jun 2017 10:32:21 -0400 Subject: [PATCH 1549/1949] Fix internal uses of complete_? We deprecated it in favor of completed_?, so we should take our own advice! ;) --- .../src/main/scala/net/liftweb/actor/LAFuture.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala b/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala index fed1911db0..4ade14aa13 100644 --- a/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala +++ b/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala @@ -176,7 +176,7 @@ class LAFuture[T](val scheduler: LAScheduler = LAScheduler, context: Box[LAFutur /** * Java-friendly alias for completed_?. */ - def isCompleted: Boolean = complete_? + def isCompleted: Boolean = completed_? /** * Has the future completed? */ @@ -389,10 +389,10 @@ object LAFuture { gotCnt += 1 onFutureSucceeded(value, result, accumulator, index) - if (gotCnt >= len && ! result.complete_?) { + if (gotCnt >= len && ! result.completed_?) { onAllFuturesCompleted(result, accumulator) - if (! result.complete_?) { + if (! result.completed_?) { result.fail(Failure("collect invoker did not complete result")) } } @@ -403,10 +403,10 @@ object LAFuture { gotCnt += 1 onFutureFailed(failureBox, result, accumulator, index) - if (gotCnt >= len && ! result.complete_?) { + if (gotCnt >= len && ! result.completed_?) { onAllFuturesCompleted(result, accumulator) - if (! result.complete_?) { + if (! result.completed_?) { result.fail(Failure("collect invoker did not complete result")) } } From e2150e7031455149dec0755ec91fe95e15f026e1 Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sat, 17 Jun 2017 20:41:15 +0530 Subject: [PATCH 1550/1949] Add collect and mapFailure methods. WIP --- .../main/scala/net/liftweb/common/Box.scala | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index ad6db68cc4..83c161ab39 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -516,6 +516,61 @@ sealed abstract class Box[+A] extends Product with Serializable{ */ def foreach[U](f: A => U): Unit = {} + /** Returns a `Full` box containing the result of applying `partialFn` to this Box's contained + * value, '''if''' this Box is Full '''and''' `partialFn` is defined for that value. + * + * If this box is Full and the `partialFn` is not defined for the value it contains, + * Empty is returned. + * + * If this box is not Full, it will be returned unchanged. + * + * @example {{{ + * // Returns Full(HTTP) because the partial function covers the case. + * Full("http") collect { case "http" => "HTTP" } + * + * // Returns Empty because the partial function doesn't cover the case. + * Full("ftp") collect { case "http" => "HTTP" } + * + * // Returns Empty because the box is empty. There is no value to pass to the partial function. + * Empty collect { case value => value } + * + * // Returns Failure because the box is Failure. There is no value to pass to the partial function. + * Failure("failed") collect { case value => value } + * }}} + * + * @param partialFn the partial function. + * @return the result of applying `partialFn` to this Box's value (if possible), or this box itself without + * any changes. + */ + @inline final def collect[B](partialFn: PartialFunction[A, B]): Box[B] = this match { + case Full(value) => Box(partialFn.lift(value)) + case e: EmptyBox => e + } + + /** + * If this box is a `Failure`, returns a `Failure` box that results from passing this box + * to `fn`. Otherwise, returns this box. Used for transforming Failures without having to + * cover all three cases of Box, which is required by `map`. + * + * `map` is only applied if the box is full, `mapFailure` is only applied if this box is a `Failure`. + * + * @example {{{ + * // Returns `Failure("Changed failure")` because this box is a failure. + * Failure("Original Failure") mapFailure { failed => Failure("Changed failure") } + * + * // Returns Full("some-value") on which it was called + * Full("some-value") mapFailure { failure => Failure("Changed failure") } + * + * // Returns this Empty instance + * Empty mapFailure { case value => value } + * }}} + * + * @param fn the function that will be used to transform the Failure if this box is a failure. + * @return A `Failure` instance that results from applying `fn` if this box is a `Failure`, otherwise + * the same instance on which it was called. + */ + def mapFailure(fn: Failure => Failure): Box[A] = this + /** * If this box is `Full` and contains an object of type `B`, returns a `Full` * of type `Box[B]`. Otherwise, returns `Empty`. @@ -904,6 +959,8 @@ sealed case class Failure(msg: String, exception: Box[Throwable], chain: Box[Fai override def map[B](f: A => B): Box[B] = this + override def mapFailure(f: (Failure) => Failure): Box[A] = f(this) + override def flatMap[B](f: A => Box[B]): Box[B] = this override def isA[B](cls: Class[B]): Box[B] = this From f9ecab8777ade19a65d3368b81ba3e9a188d4739 Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sat, 17 Jun 2017 20:55:31 +0530 Subject: [PATCH 1551/1949] Remove duplicated collect method --- .../main/scala/net/liftweb/common/Box.scala | 31 ------------------- 1 file changed, 31 deletions(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index 83c161ab39..51cf499fe1 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -516,37 +516,6 @@ sealed abstract class Box[+A] extends Product with Serializable{ */ def foreach[U](f: A => U): Unit = {} - /** Returns a `Full` box containing the result of applying `partialFn` to this Box's contained - * value, '''if''' this Box is Full '''and''' `partialFn` is defined for that value. - * - * If this box is Full and the `partialFn` is not defined for the value it contains, - * Empty is returned. - * - * If this box is not Full, it will be returned unchanged. - * - * @example {{{ - * // Returns Full(HTTP) because the partial function covers the case. - * Full("http") collect { case "http" => "HTTP" } - * - * // Returns Empty because the partial function doesn't cover the case. - * Full("ftp") collect { case "http" => "HTTP" } - * - * // Returns Empty because the box is empty. There is no value to pass to the partial function. - * Empty collect { case value => value } - * - * // Returns Failure because the box is Failure. There is no value to pass to the partial function. - * Failure("failed") collect { case value => value } - * }}} - * - * @param partialFn the partial function. - * @return the result of applying `partialFn` to this Box's value (if possible), or this box itself without - * any changes. - */ - @inline final def collect[B](partialFn: PartialFunction[A, B]): Box[B] = this match { - case Full(value) => Box(partialFn.lift(value)) - case e: EmptyBox => e - } - /** * If this box is a `Failure`, returns a `Failure` box that results from passing this box * to `fn`. Otherwise, returns this box. Used for transforming Failures without having to From c1ae4c7e5872d5e7997c8397fa013f2cfe28c3d4 Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sat, 17 Jun 2017 21:13:54 +0530 Subject: [PATCH 1552/1949] Eagerly store the server-port of the original request since it's used in a lazy val, and Jetty will recycle the request instance if the lazy val is realized too late --- .../liftweb/http/provider/servlet/HTTPRequestServlet.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPRequestServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPRequestServlet.scala index 5863cc7fe7..e597a047ae 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPRequestServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPRequestServlet.scala @@ -186,6 +186,8 @@ private class OfflineRequestSnapshot(req: HTTPRequest, val provider: HTTPProvide private val _params = List(req.params :_*) + private val _serverPort = req.serverPort + def cookies: List[HTTPCookie] = _cookies @@ -234,8 +236,8 @@ private class OfflineRequestSnapshot(req: HTTPRequest, val provider: HTTPProvide val scheme: String = req.scheme - lazy val serverPort: Int = req.serverPort match { - case 80 => headers("X-SSL").flatMap(Helpers.asBoolean _).filter(a => a).map(a => 443).headOption getOrElse 80 + lazy val serverPort: Int = _serverPort match { + case 80 => headers("X-SSL").flatMap(Helpers.asBoolean).filter(identity).map(a => 443).headOption getOrElse 80 case x => x } From f6622302b3d7ce3f734e990e1e287c863b02e69b Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sat, 17 Jun 2017 21:40:09 +0530 Subject: [PATCH 1553/1949] Add optional ObjectId field --- .../mongodb/record/field/ObjectIdField.scala | 69 +++++++++++-------- 1 file changed, 41 insertions(+), 28 deletions(-) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/ObjectIdField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/ObjectIdField.scala index 9966582f21..2e04d32486 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/ObjectIdField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/ObjectIdField.scala @@ -19,30 +19,18 @@ package mongodb package record package field -import scala.xml.NodeSeq - import java.util.Date import net.liftweb.common.{Box, Empty, Failure, Full} -import net.liftweb.http.js.JE.{JsNull, JsObj, JsRaw, Str} import net.liftweb.http.S +import net.liftweb.http.js.JE.{JsNull, JsRaw} +import net.liftweb.http.js.JsExp import net.liftweb.json._ -import net.liftweb.record.{Field, FieldHelpers, MandatoryTypedField, Record} +import net.liftweb.record._ import net.liftweb.util.Helpers._ - import org.bson.types.ObjectId -/* -* Field for storing an ObjectId -*/ -class ObjectIdField[OwnerType <: BsonRecord[OwnerType]](rec: OwnerType) - extends Field[ObjectId, OwnerType] - with MandatoryTypedField[ObjectId] -{ - - def owner = rec - - def defaultValue = ObjectId.get +sealed trait ObjectIdTypedField[OwnerType <: BsonRecord[OwnerType]] extends TypedField[ObjectId] with Field[ObjectId, OwnerType] { def setFromAny(in: Any): Box[ObjectId] = in match { case oid: ObjectId => setBox(Full(oid)) @@ -64,33 +52,58 @@ class ObjectIdField[OwnerType <: BsonRecord[OwnerType]](rec: OwnerType) case other => setBox(FieldHelpers.expectedA("JObject", other)) } - def setFromString(in: String): Box[ObjectId] = - if (ObjectId.isValid(in)) - setBox(Full(new ObjectId(in))) - else - setBox(Failure("Invalid ObjectId string: "+in)) + def setFromString(in: String): Box[ObjectId] = { + if (ObjectId.isValid(in)) setBox(Full(new ObjectId(in))) + else setBox(Failure(s"Invalid ObjectId string: $in")) + } private def elem = - S.fmapFunc(S.SFuncHolder(this.setFromAny(_))){funcName => + S.fmapFunc(S.SFuncHolder(this.setFromAny(_))) { funcName => s.toString) openOr ""} tabindex={tabIndex.toString}/> } - def toForm = - uniqueFieldId match { - case Full(id) => Full(elem % ("id" -> id)) - case _ => Full(elem) - } + override def toForm = uniqueFieldId match { + case Full(id) => Full(elem % ("id" -> id)) + case _ => Full(elem) + } - def asJs = asJValue match { + override def asJs: JsExp = asJValue match { case JNothing => JsNull case jv => JsRaw(compactRender(jv)) } def asJValue: JValue = valueBox.map(v => JsonObjectId.asJValue(v, owner.meta.formats)) openOr (JNothing: JValue) +} + +class ObjectIdField[OwnerType <: BsonRecord[OwnerType]](rec: OwnerType) + extends MandatoryTypedField[ObjectId] with ObjectIdTypedField[OwnerType] { + + def this(rec: OwnerType, value: ObjectId) = { + this(rec) + setBox(Full(value)) + } + + override def defaultValue = ObjectId.get + + override def owner: OwnerType = rec + def createdAt: Date = this.get.getDate + +} + +class OptionalObjectIdField[OwnerType <: BsonRecord[OwnerType]](rec: OwnerType) + extends OptionalTypedField[ObjectId] with ObjectIdTypedField[OwnerType] { + + def this(rec: OwnerType, value: Box[ObjectId]) = { + this(rec) + setBox(value) + } + + override def owner: OwnerType = rec + } From 4690d9b2423ac7aab52954f0182e6af3e3487e61 Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sun, 18 Jun 2017 12:27:30 +0530 Subject: [PATCH 1554/1949] Make a val private [this] (suggestion from codacy) --- .../net/liftweb/http/provider/servlet/HTTPRequestServlet.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPRequestServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPRequestServlet.scala index e597a047ae..d7fec30c0c 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPRequestServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPRequestServlet.scala @@ -186,7 +186,7 @@ private class OfflineRequestSnapshot(req: HTTPRequest, val provider: HTTPProvide private val _params = List(req.params :_*) - private val _serverPort = req.serverPort + private [this] val _serverPort = req.serverPort def cookies: List[HTTPCookie] = _cookies From 60cc75a51992188a8b68215aa2169166b70b522b Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sun, 18 Jun 2017 12:41:05 +0530 Subject: [PATCH 1555/1949] Specify the overridden 'owner' field directly in the consructor --- .../net/liftweb/mongodb/record/field/ObjectIdField.scala | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/ObjectIdField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/ObjectIdField.scala index 2e04d32486..f1818e19e8 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/ObjectIdField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/ObjectIdField.scala @@ -79,7 +79,7 @@ sealed trait ObjectIdTypedField[OwnerType <: BsonRecord[OwnerType]] extends Type } -class ObjectIdField[OwnerType <: BsonRecord[OwnerType]](rec: OwnerType) +class ObjectIdField[OwnerType <: BsonRecord[OwnerType]](override val owner: OwnerType) extends MandatoryTypedField[ObjectId] with ObjectIdTypedField[OwnerType] { def this(rec: OwnerType, value: ObjectId) = { @@ -89,13 +89,11 @@ class ObjectIdField[OwnerType <: BsonRecord[OwnerType]](rec: OwnerType) override def defaultValue = ObjectId.get - override def owner: OwnerType = rec - def createdAt: Date = this.get.getDate } -class OptionalObjectIdField[OwnerType <: BsonRecord[OwnerType]](rec: OwnerType) +class OptionalObjectIdField[OwnerType <: BsonRecord[OwnerType]](override val owner: OwnerType) extends OptionalTypedField[ObjectId] with ObjectIdTypedField[OwnerType] { def this(rec: OwnerType, value: Box[ObjectId]) = { @@ -103,7 +101,5 @@ class OptionalObjectIdField[OwnerType <: BsonRecord[OwnerType]](rec: OwnerType) setBox(value) } - override def owner: OwnerType = rec - } From 3d7f992452304128c355f1335f6b81560ba18341 Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sun, 18 Jun 2017 12:41:27 +0530 Subject: [PATCH 1556/1949] Add optional version of UUID field --- .../mongodb/record/field/UUIDField.scala | 75 +++++++++++-------- 1 file changed, 43 insertions(+), 32 deletions(-) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/UUIDField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/UUIDField.scala index da646357b0..978dc32733 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/UUIDField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/UUIDField.scala @@ -18,25 +18,17 @@ package field import java.util.UUID -import scala.xml.NodeSeq - import net.liftweb.common.{Box, Empty, Failure, Full} -import net.liftweb.http.js.JE.{JsNull, JsRaw} import net.liftweb.http.S +import net.liftweb.http.js.JE.{JsNull, JsRaw} +import net.liftweb.http.js.JsExp import net.liftweb.json._ -import net.liftweb.mongodb.record._ -import net.liftweb.record.{Field, FieldHelpers, MandatoryTypedField} +import net.liftweb.record._ import net.liftweb.util.Helpers._ -class UUIDField[OwnerType <: BsonRecord[OwnerType]](rec: OwnerType) - extends Field[UUID, OwnerType] - with MandatoryTypedField[UUID] -{ - - def owner = rec - - def defaultValue = UUID.randomUUID +import scala.xml.NodeSeq +sealed trait UUIDTypedField[OwnerType <: BsonRecord[OwnerType]] extends TypedField[UUID] with Field[UUID, OwnerType] { def setFromAny(in: Any): Box[UUID] = in match { case uid: UUID => setBox(Full(uid)) case Some(uid: UUID) => setBox(Full(uid)) @@ -50,38 +42,57 @@ class UUIDField[OwnerType <: BsonRecord[OwnerType]](rec: OwnerType) case o => setFromString(o.toString) } - def setFromJValue(jvalue: JValue): Box[UUID] = jvalue match { + override def setFromJValue(jvalue: JValue): Box[UUID] = jvalue match { case JNothing|JNull if optional_? => setBox(Empty) case JObject(JField("$uuid", JString(s)) :: Nil) => setFromString(s) case other => setBox(FieldHelpers.expectedA("JObject", other)) } - def setFromString(in: String): Box[UUID] = tryo(UUID.fromString(in)) match { + override def setFromString(in: String): Box[UUID] = tryo(UUID.fromString(in)) match { case Full(uid: UUID) => setBox(Full(uid)) case f: Failure => setBox(f) - case other => setBox(Failure("Invalid UUID string: "+in)) + case _ => setBox(Failure(s"Invalid UUID string: $in")) + } + + private def elem = S.fmapFunc(S.SFuncHolder(this.setFromAny(_))) { funcName => + v.toString) openOr ""} + tabindex={tabIndex.toString}/> + } + + override def toForm: Box[NodeSeq] = uniqueFieldId match { + case Full(id) => Full(elem % ("id" -> id)) + case _ => Full(elem) } - private def elem = - S.fmapFunc(S.SFuncHolder(this.setFromAny(_))){funcName => - v.toString) openOr ""} - tabindex={tabIndex.toString}/> - } - - def toForm = - uniqueFieldId match { - case Full(id) => Full(elem % ("id" -> id)) - case _ => Full(elem) - } - - def asJs = asJValue match { + override def asJs: JsExp = asJValue match { case JNothing => JsNull case jv => JsRaw(compactRender(jv)) } - def asJValue: JValue = valueBox.map(v => JsonUUID(v)) openOr (JNothing: JValue) + override def asJValue: JValue = valueBox.map(v => JsonUUID(v)) openOr (JNothing: JValue) +} + +class UUIDField[OwnerType <: BsonRecord[OwnerType]](override val owner: OwnerType) + extends UUIDTypedField[OwnerType] with MandatoryTypedField[UUID] { + + def this(rec: OwnerType, value: UUID) = { + this(rec) + setBox(Full(value)) + } + + override def defaultValue = UUID.randomUUID + +} + +class OptionalUUIDField[OwnerType <: BsonRecord[OwnerType]](override val owner: OwnerType) + extends UUIDTypedField[OwnerType] with OptionalTypedField[UUID] { + + def this(rec: OwnerType, value: Box[UUID]) = { + this(rec) + setBox(value) + } } From 2909ee8acec833a601ab3e93a8789c44fc633145 Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sun, 18 Jun 2017 12:43:59 +0530 Subject: [PATCH 1557/1949] Optional versions of StringRefField, IntRefField, LongRefField and UUIDRefField added --- .../mongodb/record/field/MongoRefField.scala | 34 ++++++++++++++++--- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoRefField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoRefField.scala index c268250e1d..1c6f6fc715 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoRefField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoRefField.scala @@ -101,20 +101,44 @@ trait MongoRefField[RefType <: MongoRecord[RefType], MyType] extends TypedField[ class ObjectIdRefField[OwnerType <: BsonRecord[OwnerType], RefType <: MongoRecord[RefType]]( rec: OwnerType, val refMeta: MongoMetaRecord[RefType] -) extends ObjectIdField[OwnerType](rec) with MongoRefField[RefType, ObjectId] {} +) extends ObjectIdField[OwnerType](rec) with MongoRefField[RefType, ObjectId] + +class OptionalObjectIdRefField[OwnerType <: BsonRecord[OwnerType], RefType <: MongoRecord[RefType]]( + rec: OwnerType, val refMeta: MongoMetaRecord[RefType] +) extends OptionalObjectIdField[OwnerType](rec) with MongoRefField[RefType, ObjectId] + class UUIDRefField[OwnerType <: BsonRecord[OwnerType], RefType <: MongoRecord[RefType]]( rec: OwnerType, val refMeta: MongoMetaRecord[RefType] -) extends UUIDField[OwnerType](rec) with MongoRefField[RefType, UUID] {} +) extends UUIDField[OwnerType](rec) with MongoRefField[RefType, UUID] + +class OptionalUUIDRefField[OwnerType <: BsonRecord[OwnerType], RefType <: MongoRecord[RefType]]( + rec: OwnerType, val refMeta: MongoMetaRecord[RefType] +) extends OptionalUUIDField[OwnerType](rec) with MongoRefField[RefType, UUID] + class StringRefField[OwnerType <: BsonRecord[OwnerType], RefType <: MongoRecord[RefType]]( rec: OwnerType, val refMeta: MongoMetaRecord[RefType], maxLen: Int -) extends StringField[OwnerType](rec, maxLen) with MongoRefField[RefType, String] {} +) extends StringField[OwnerType](rec, maxLen) with MongoRefField[RefType, String] + +class OptionalStringRefField[OwnerType <: BsonRecord[OwnerType], RefType <: MongoRecord[RefType]]( + rec: OwnerType, val refMeta: MongoMetaRecord[RefType], maxLen: Int +) extends OptionalStringField[OwnerType](rec, maxLen) with MongoRefField[RefType, String] + class IntRefField[OwnerType <: BsonRecord[OwnerType], RefType <: MongoRecord[RefType]]( rec: OwnerType, val refMeta: MongoMetaRecord[RefType] -) extends IntField[OwnerType](rec) with MongoRefField[RefType, Int] {} +) extends IntField[OwnerType](rec) with MongoRefField[RefType, Int] + +class OptionalIntRefField[OwnerType <: BsonRecord[OwnerType], RefType <: MongoRecord[RefType]]( + rec: OwnerType, val refMeta: MongoMetaRecord[RefType] +) extends OptionalIntField[OwnerType](rec) with MongoRefField[RefType, Int] + class LongRefField[OwnerType <: BsonRecord[OwnerType], RefType <: MongoRecord[RefType]]( rec: OwnerType, val refMeta: MongoMetaRecord[RefType] -) extends LongField[OwnerType](rec) with MongoRefField[RefType, Long] {} +) extends LongField[OwnerType](rec) with MongoRefField[RefType, Long] + +class OptionalLongRefField[OwnerType <: BsonRecord[OwnerType], RefType <: MongoRecord[RefType]]( + rec: OwnerType, val refMeta: MongoMetaRecord[RefType] +) extends OptionalLongField[OwnerType](rec) with MongoRefField[RefType, Long] From f7ebd395b941ef4d64a6e8e05d0bdcb866ab9bc2 Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sun, 18 Jun 2017 12:55:12 +0530 Subject: [PATCH 1558/1949] Optional version of JObjectField --- .../mongodb/record/field/JObjectField.scala | 54 ++++++++++++------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala index 954bb9e955..f64e68cdc4 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala @@ -19,41 +19,34 @@ package mongodb package record package field +import java.util.UUID + import common._ import http.js.JE._ import json._ import util.Helpers.tryo -import net.liftweb.record.{Field, FieldHelpers, MandatoryTypedField} +import net.liftweb.record._ import scala.xml.NodeSeq - import com.mongodb._ import org.bson.Document -class JObjectField[OwnerType <: BsonRecord[OwnerType]](rec: OwnerType) -extends Field[JObject, OwnerType] -with MandatoryTypedField[JObject] -with MongoFieldFlavor[JObject] { - - def owner = rec - - def asJValue: JValue = valueBox openOr (JNothing: JValue) +sealed trait JObjectTypedField[OwnerType <: BsonRecord[OwnerType]] extends TypedField[JObject] + with Field[JObject, OwnerType] with MongoFieldFlavor[JObject] { - def setFromJValue(jvalue: JValue): Box[JObject] = jvalue match { + override def setFromJValue(jvalue: JValue): Box[JObject] = jvalue match { case JNothing|JNull if optional_? => setBox(Empty) case jo: JObject => setBox(Full(jo)) case other => setBox(FieldHelpers.expectedA("JObject", other)) } - def defaultValue = JObject(List()) - - def setFromAny(in: Any): Box[JObject] = in match { + override def setFromAny(in: Any): Box[JObject] = in match { case dbo: DBObject => setBox(setFromDBObject(dbo)) case doc: Document => setBox(setFromDocument(doc)) case jv: JObject => setBox(Full(jv)) case Some(jv: JObject) => setBox(Full(jv)) case Full(jv: JObject) => setBox(Full(jv)) - case seq: Seq[_] if !seq.isEmpty => seq.map(setFromAny).apply(0) + case seq: Seq[_] if seq.nonEmpty => seq.map(setFromAny).head case (s: String) :: _ => setFromString(s) case null => setBox(Full(null)) case s: String => setFromString(s) @@ -62,21 +55,44 @@ with MongoFieldFlavor[JObject] { } // assume string is json - def setFromString(in: String): Box[JObject] = { + override def setFromString(in: String): Box[JObject] = { // use lift-json to parse string into a JObject setBox(tryo(JsonParser.parse(in).asInstanceOf[JObject])) } - def toForm: Box[NodeSeq] = Empty + override def toForm: Box[NodeSeq] = Empty - def asDBObject: DBObject = valueBox + override def asDBObject: DBObject = valueBox .map { v => JObjectParser.parse(v)(owner.meta.formats) } .openOr(new BasicDBObject) - def setFromDBObject(obj: DBObject): Box[JObject] = + override def setFromDBObject(obj: DBObject): Box[JObject] = Full(JObjectParser.serialize(obj)(owner.meta.formats).asInstanceOf[JObject]) def setFromDocument(obj: Document): Box[JObject] = Full(JObjectParser.serialize(obj)(owner.meta.formats).asInstanceOf[JObject]) + override def asJValue: JValue = valueBox openOr (JNothing: JValue) } + +class JObjectField[OwnerType <: BsonRecord[OwnerType]](override val owner: OwnerType) + extends JObjectTypedField[OwnerType] with MandatoryTypedField[JObject] { + + def this(owner: OwnerType, value: JObject) = { + this(owner) + setBox(Full(value)) + } + + override def defaultValue = JObject(List()) + +} + +class OptionalJObjectField[OwnerType <: BsonRecord[OwnerType]](override val owner: OwnerType) + extends JObjectTypedField[OwnerType] with OptionalTypedField[JObject] { + + def this(owner: OwnerType, value: Box[JObject]) = { + this(owner) + setBox(value) + } + +} \ No newline at end of file From eacbe2d520f34cb2e5ffd6e4741362fa6c729d5e Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sun, 18 Jun 2017 12:56:58 +0530 Subject: [PATCH 1559/1949] Imports organized --- .../liftweb/mongodb/record/field/JObjectField.scala | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala index f64e68cdc4..3a0134ed03 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala @@ -19,17 +19,14 @@ package mongodb package record package field -import java.util.UUID - -import common._ -import http.js.JE._ -import json._ -import util.Helpers.tryo +import com.mongodb._ +import net.liftweb.common._ +import net.liftweb.json._ import net.liftweb.record._ +import net.liftweb.util.Helpers.tryo +import org.bson.Document import scala.xml.NodeSeq -import com.mongodb._ -import org.bson.Document sealed trait JObjectTypedField[OwnerType <: BsonRecord[OwnerType]] extends TypedField[JObject] with Field[JObject, OwnerType] with MongoFieldFlavor[JObject] { From 24ecc464d3061730eabc4362250cc5e5728d348a Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sun, 18 Jun 2017 23:03:40 +0530 Subject: [PATCH 1560/1949] Formatting --- .../http/provider/servlet/HTTPRequestServlet.scala | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPRequestServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPRequestServlet.scala index d7fec30c0c..7ef98da53d 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPRequestServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPRequestServlet.scala @@ -237,7 +237,13 @@ private class OfflineRequestSnapshot(req: HTTPRequest, val provider: HTTPProvide val scheme: String = req.scheme lazy val serverPort: Int = _serverPort match { - case 80 => headers("X-SSL").flatMap(Helpers.asBoolean).filter(identity).map(a => 443).headOption getOrElse 80 + case 80 => + headers("X-SSL") + .flatMap(Helpers.asBoolean) + .filter(identity) + .map(_ => 443) + .headOption + .getOrElse(80) case x => x } From f36d495c812f053eb5637235e70672204da5c307 Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Wed, 21 Jun 2017 10:37:23 +0530 Subject: [PATCH 1561/1949] Initial docs describing lift's DI --- docs/dependency-injection-liftweb-scala.adoc | 382 +++++++++++++++++++ 1 file changed, 382 insertions(+) create mode 100644 docs/dependency-injection-liftweb-scala.adoc diff --git a/docs/dependency-injection-liftweb-scala.adoc b/docs/dependency-injection-liftweb-scala.adoc new file mode 100644 index 0000000000..4def4dc2ba --- /dev/null +++ b/docs/dependency-injection-liftweb-scala.adoc @@ -0,0 +1,382 @@ +:idprefix: +:idseparator: - +:toc: right +:toclevels: 2 + += Dependency Injection in Lift + +== Introduction + +Dependency injection is an important topic in the JVM world. With Java, we usually go with Guice +or Spring whenever we need it. However, scala has certain advantages over Java, like functions, +that allow basic DI without resorting to entire DI frameworks. + +Scala allows the use of http://jonasboner.com/real-world-scala-dependency-injection-di/[cake pattern] +that can provide DI by composing traits. + +Although the cake pattern is helpful, it doesn't provide complete DI functionality. +The cake pattern allows you to compose complex classes out of Scala traits, but the it's less helpful +in terms of allowing you to make dynamic choices about which combination of cake to vend in a given situation. +Lift provides extra features that complete the dependency injection puzzle. + +== Why Lift DI + +Many people come to Scala from a Java background, so it's only natural to want to use Guice/Spring, +which do a great job in the java world. And we can https://groups.google.com/forum/#!topic/liftweb/_lleL2xpCFU[use those], +but there's a better, more direct way that's less magical. + +Lift's philosophy is to keep things as simple as possible (and no simpler), and allow full control +to the developers over how things work. To paraphrase David Pollak, +"The abstraction turtles have to end somewhere" and lift's DI features end those turtles quite +early. + +== Lift DI and various dependency scopes (e.g. from Spring) + +Lift's DI allows all the +https://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-scopes[four most commonly used scopes] that +are possible with spring: +https://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-scopes-singleton[singleton], +https://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-scopes-prototype[prototype], https://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-scopes-request[request], +and https://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-scopes-session[session]. The others can be achieved +relatively easily if you want. + +== Getting started + +Lift's DI is based on two main traits https://liftweb.net/api/26/api/index.html#net.liftweb.util.Injector[`Injector`] +and https://liftweb.net/api/26/api/index.html#net.liftweb.util.Maker[`Maker`]. However, most of the time, you won't need to +deal with them directly. The elements that you would use are: + +. https://liftweb.net/api/26/api/index.html#net.liftweb.http.Factory[`Factory`] trait +. https://github.com/lift/framework/blob/5033c8798d4444f81996199c10ea330770e47fbc/web/webkit/src/main/scala/net/liftweb/http/Factory.scala#L37[`FactoryMaker`], which is an abstract class inside + inside the [`Factory`] trait. +. https://liftweb.net/api/26/api/index.html#net.liftweb.util.SimpleInjector$Inject[`Inject`], an abstract class within the + `SimpleInjector` trait. + +`FactoryMaker` and `Injector` serve the same purpose, with the former +having more features. The difference is important. Notes on this are +at the end. I'll continue with `FactoryMaker` for the sake of +examples, and then note the differences between the two at the end. + +To start with, here's an example of how these are supposed to be +used: + +[source,scala] +---- +object DependencyFactory extends Factory { + private val seq = new AtomicLong(0) + + object emailService extends FactoryMaker(emailServiceImpl) + object sequenceNumberService extends FactoryMaker(seq.incrementAndGet _) + + private def emailServiceImpl: EmailService = Props.mode match { + case Props.RunModes.Production => new RealEmailService + // A stub for local development + case _ => new TestEmailService + } +} +---- + +This defines two managed dependencies. In the case of the +`emailService`, the dependency changes based on whether we are running +on production/staging mode or some other mode (e.g. development). The +`emailService` is a singleton dependency. That is, everyone +who injects this dependency will get the same instance throughout the +app. + +Note that `FactoryMaker` constructor takes a stock scala function that +returns an instance of the needed type. A http://www.scala-lang.org/api/current/scala/Function0.html[`Function0`] to +be precise. If you pass in a pre-created instance, as we are +doing in the case of `emailService`, that instance will always be +returned when this dependency is injected (scala converts that +instance to a `Function0`). This is similar to +the https://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-scopes-singleton[singleton scope] in spring DI. + +However, since this is a normal scala function, you can write whatever +you want within that function. For ex. as the `sequenceNumberService` +illustrates, you can generate a new instance every time it needs to be +injected. Instances could be generated every time, or they could be +generated fresh if some condition is met and so on and so forth. + +Here's how you can use these dependencies: + +[source,scala] +---- +class SomeClass { + private val emailService = DependencyFactory.emailService() + // Or alternatively, if you don't have the FactoryMaker for a given type + private val someType = + DependencyFactory.inject[SomeType] + .openOrThrowException("No instance of SomeType found") +} +---- + +You can use the `apply` or `vend` method on the FactoryMaker directly, +which will give you the instance you need. I'll be using `vend` for +further examples just so that it's clear what's happening. + +Calling `DependencyFactory.sequenceNumberService.vend` will return a +new number every time, since that's how it's been setup. This is, in +spring terminology, https://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-scopes-prototype[the prototype scope]. + +An alternative way is to use `DependencyFactory.inject`. But it +returns `Box` and it's only needed if don't have a FactoryMaker for a +given dependency. Which brings us to the fact that there are other +ways of registering dependencies apart from the FactoryMaker. For ex. + +[source,scala] +---- +class SomeOtherClass { + private val someTypeInstance = new SomeType + DependencyFactory.registerInjection[SomeType](() => someTypeInstance) +} +---- + +This can be used by arbitrary code in your app to register injectable +instance. Here again, a singleton is registered. In this case, when +you need to access the registered instance, you have to necessarily +use `DependencyFactory.inject[SomeType]`. + +== Overriding dependencies for sessions or requests + +If you have a scenario where you need to override a given dependency +for the duration of the current session, you can do something like +following (for ex. in your snippet code): + +[source] +---- +val customEmailService = new CustomEmailService(currentUser) +DependencyFactory.emailService.session(customEmailService) +---- + +This will set the `emailService` to always return the +`customEmailService` instance for the duration of the current session +for the current user. + +Note that is not equivalent to the https://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-scopes-session[session-scoped] +dependency in spring. This is done explicitly in your application +code. You are overriding a default dependency with some custom +instance for the duration of _this_ session. No other user is going to +see it. And as soon as this session is over, it will be gone until you +set it explicitly again. + +You can something similar when you need to override a dependency +during a given request. + +[source] +---- +val customEmailService = new CustomEmailService(currentUser) +DependencyFactory.emailService.request(customEmailService) +---- + +Again, this is not a https://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-scopes-request[request scoped dependency] as +identified by spring. + +== Session or request scoped dependencies + +The above examples only set the dependencies for the duration of a +given session or request,and only when the relevant code that sets +those dependencies was executed. + +What if you want to always create a session/request scoped dependency +for all the users. Let's talk +about https://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-scopes-session[session scoped] dependencies. The discussion +would be identical for +the https://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-scopes-request[request scoped dependencies]. With session +scoped dependencies, we want a new instance to be created for each +session, for all the users. + +=== Request scope with request lifecycle hooks + +In your Boot.scala, which is used for instantiating and configuring +various stuff in lift: + +[source,scala] +---- +class Boot { + LiftSession.onBeginServicing = ((sess: LiftSession, req: Req) => { + DependencyFactory.awesomeService.request.set(new AwesomeService {}) + }) :: LiftSession.onBeginServicing + + ... +} +---- + +This will set a new instance on every request, right at the beginning +of the request servicing. So, calling +`DependencyFactory.awesomeService.vend` will return the instance +created for the particular request. + +=== Session scope with session lifecycle hooks + +Similarly, you can do it for sessions in `Boot.scala`: + +[source,scala] +---- +class Boot { + LiftSession.afterSessionCreate = ((_: LiftSession, req: Req) => { + DependencyFactory.awesomeService.session.set(new AwesomeService {}) + }) :: LiftSession.afterSessionCreate + ... +} +---- + +That's pretty much it. + +== Overriding dependency factory for tests and mocking dependencies + +The above cases handle most of the stuff you will need. When testing, +all of your tests might need to mock some of the services, without +affecting other tests. Doing this manually would be nightmarish, +extremely prone to errors. The way to do it is to have isolated +dependency graph for your tests. The key is realizing that the +`DependencyFactory` could be just a normal scala instance that itself +can be injected as needed. See, there are no turtles all the way!. +This is what David Pollak keeps saying repeatedly about lift and its simplicity. +It's just scala code. There is no magic here. + +A trait that represents your `DependencyFactory` + +[source,scala] +---- +import net.liftweb.util.Vendor + +trait DependencyFactory extends Factory { + + object cardService extends FactoryMaker(cardServiceVendor) + ... + + // the default implementation of card-service + // this is the method you override when needed + protected def cardServiceVendor: Vendor[CardService] = new PaymentCardService + + // other such vendors + ... +} + +object DependencyFactory extends Factory { + // the default instance that will be used unless overridden + private val DefaultInstance = new DependencyFactory {} + + object instance extends FactoryMaker[DependencyFactory](DefaultInstance) + + // Allow making calls directly on DependencyFactory companion object + //instead of having to use DependencyFactory.instance + implicit def depFactoryToInstance(dft: DependencyFactory.type) + : DependencyFactory = instance.vend + // you shouldn't write code that needs this, this is just an example + def resetDefault = instance.default.set(DefaultInstance) +} +---- + +Now, when you do `DependencyFactory.cardService.vend`, it will using +the `DefaultInstance`. Your call will be implicitly translated to +`DependencyFactory.instance.vend.cardService.vend`. This is the part +that allows you to completely override everything you need in your +dependency graph. For ex. you could do this in your tests: + +[source,scala] +---- +class SomeSpec { + override def beforeAll = DependencyFactory.instance.default.set({ + new DependencyFactory { + override def cardServiceVendor: Vendor[CardService] = mock[CardService] + } + }) + + override def afterAll: Unit = DependencyFactory.resetDefault +} +---- + +However, there is a possible problem with this approach (I haven't +tested it). If your test suites are running in parallel, this +set/reset of the default instance will be problematic. I don't +recommend this approach unless you know what you are doing. + +One safe way of doing this is to use the stackable nature of the +`Makers`: + +[source,scala] +---- +private val customDepFactory = new DependencyFactory { + override def cardServiceVendor: Vendor[CardService] + = mock[CardService] +} + +DependencyFactory.instance.doWith(customDepFactory) { + // write all your tests here +} +---- + +And this would work as expected. You can try to come up with variation +on how to do this without the added indentation though. For ex. you +can do following with http://www.scalatest.org/[scalatest]: + +[source,scala] +---- +trait DependencyOverrides extends SuiteMixin { self: Suite => + + // Just override this and your tests will be executed with that overridden DependencyFactory instance. + protected def dependencyFactory: Vendor[DependencyFactory] = DependencyFactory.instance + + // Run the tests with the given dependency-factory instance. + abstract override def withFixture(test: NoArgTest): Outcome = { + DependencyFactory.instance.doWith(dependencyFactory.vend) { + super.withFixture(test) + } + } +} +---- + +Scalatest has something called http://www.scalatest.org/user_guide/sharing_fixtures[fixtures] that comes in +really handy here. Any test where you need to provide a custom +`DependencyFactory` instance should override this trait and just +override with the custom implementation. For ex. + +[source,scala] +---- +class SomeSpec extends ... with DependencyOverrides { + override val dependencyFactory: Vendor[DependencyFactory] = new DependencyFactory { + override def cardServiceVendor: Vendor[CardService] = mock[CardService] + // other overrides + ... + } +} +---- + +== Differences between FactoryMaker and Inject + +You can also declare your dependencies using +the https://liftweb.net/api/26/api/index.html#net.liftweb.util.SimpleInjector$Inject[`Inject`] class, exactly like the +`FactoryMaker`. For ex. + +[source,scala] +---- +object cardServiceFactoryMaker extends FactoryMaker(cardServiceVendor) + +object cardServiceInject extends Inject(cardServiceVendor) +---- + +Both of these can be identically used, with one major +difference: `Inject` doesn't have session/request scoped +dependencies. To https://groups.google.com/forum/#!msg/liftweb/oWPhlwqAEDE/Jb4tWrzlAwAJ[quote Antonio] (with some modification): + +____ + +`FactoryMaker` can have a very high overhead for simple injection +needs (on the order of 100+ms I think) due to the fact that it +checks for session-scoped overrides, which require synchronized +blocks. `Inject` doesn't have that overhead. + +____ + +You can see the locking https://github.com/lift/framework/blob/5033c8798d4444f81996199c10ea330770e47fbc/web/webkit/src/main/scala/net/liftweb/http/Vars.scala#L114-L124[here]. This applies to +`SessionVar` instances in general. So, there you go. Use `Inject` if +you don't need the session/request scopes. + +== Conclusion + +Most of the time, you should be able to do away with any specialized +DI framework. Lift's DI facilities provide a powerful and flexible mechanism for vending instances +based on a global function, call stack scoping, request and session scoping and provides more +flexible features than most Java-based dependency injection frameworks without resorting to XML +for configuration or byte-code rewriting magic. \ No newline at end of file From fa2c2c6e1d4f1f63879bbac871046fecd3e45e6d Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sun, 25 Jun 2017 07:38:09 +0530 Subject: [PATCH 1562/1949] Captialize nouns. Replace conjunctions like it's with it is and so on. Replace DI with the full Dependency Injection --- docs/dependency-injection-liftweb-scala.adoc | 38 ++++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/docs/dependency-injection-liftweb-scala.adoc b/docs/dependency-injection-liftweb-scala.adoc index 4def4dc2ba..c5c803c6ad 100644 --- a/docs/dependency-injection-liftweb-scala.adoc +++ b/docs/dependency-injection-liftweb-scala.adoc @@ -15,24 +15,24 @@ Scala allows the use of http://jonasboner.com/real-world-scala-dependency-inject that can provide DI by composing traits. Although the cake pattern is helpful, it doesn't provide complete DI functionality. -The cake pattern allows you to compose complex classes out of Scala traits, but the it's less helpful +The cake pattern allows you to compose complex classes out of Scala traits, but it is less helpful in terms of allowing you to make dynamic choices about which combination of cake to vend in a given situation. Lift provides extra features that complete the dependency injection puzzle. -== Why Lift DI +== Why Lift Dependency Injection -Many people come to Scala from a Java background, so it's only natural to want to use Guice/Spring, +Many people come to Scala from a Java background, so it is only natural to want to use Guice/Spring, which do a great job in the java world. And we can https://groups.google.com/forum/#!topic/liftweb/_lleL2xpCFU[use those], -but there's a better, more direct way that's less magical. +but there's a better, more direct way that is less magical. Lift's philosophy is to keep things as simple as possible (and no simpler), and allow full control to the developers over how things work. To paraphrase David Pollak, -"The abstraction turtles have to end somewhere" and lift's DI features end those turtles quite +"The abstraction turtles have to end somewhere" and Lift's Dependency Injection features end those turtles quite early. -== Lift DI and various dependency scopes (e.g. from Spring) +== Lift's dependency injection and various dependency scopes (e.g. from Spring) -Lift's DI allows all the +Lift's dependency injection allows all the https://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-scopes[four most commonly used scopes] that are possible with spring: https://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-scopes-singleton[singleton], @@ -42,7 +42,7 @@ relatively easily if you want. == Getting started -Lift's DI is based on two main traits https://liftweb.net/api/26/api/index.html#net.liftweb.util.Injector[`Injector`] +Lift's dependency injection is based on two main traits https://liftweb.net/api/26/api/index.html#net.liftweb.util.Injector[`Injector`] and https://liftweb.net/api/26/api/index.html#net.liftweb.util.Maker[`Maker`]. However, most of the time, you won't need to deal with them directly. The elements that you would use are: @@ -52,7 +52,7 @@ deal with them directly. The elements that you would use are: . https://liftweb.net/api/26/api/index.html#net.liftweb.util.SimpleInjector$Inject[`Inject`], an abstract class within the `SimpleInjector` trait. -`FactoryMaker` and `Injector` serve the same purpose, with the former +`FactoryMaker` and `Inject` serve the same purpose, with the former having more features. The difference is important. Notes on this are at the end. I'll continue with `FactoryMaker` for the sake of examples, and then note the differences between the two at the end. @@ -89,7 +89,7 @@ be precise. If you pass in a pre-created instance, as we are doing in the case of `emailService`, that instance will always be returned when this dependency is injected (scala converts that instance to a `Function0`). This is similar to -the https://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-scopes-singleton[singleton scope] in spring DI. +the https://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-scopes-singleton[singleton scope] in Spring dependency injection. However, since this is a normal scala function, you can write whatever you want within that function. For ex. as the `sequenceNumberService` @@ -112,14 +112,14 @@ class SomeClass { You can use the `apply` or `vend` method on the FactoryMaker directly, which will give you the instance you need. I'll be using `vend` for -further examples just so that it's clear what's happening. +further examples just so that it is clear what is happening. Calling `DependencyFactory.sequenceNumberService.vend` will return a -new number every time, since that's how it's been setup. This is, in +new number every time, since that is how it has been setup. This is, in spring terminology, https://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-scopes-prototype[the prototype scope]. An alternative way is to use `DependencyFactory.inject`. But it -returns `Box` and it's only needed if don't have a FactoryMaker for a +returns `Box` and it is only needed if don't have a FactoryMaker for a given dependency. Which brings us to the fact that there are other ways of registering dependencies apart from the FactoryMaker. For ex. @@ -188,7 +188,7 @@ session, for all the users. === Request scope with request lifecycle hooks In your Boot.scala, which is used for instantiating and configuring -various stuff in lift: +various stuff in Lift: [source,scala] ---- @@ -220,7 +220,7 @@ class Boot { } ---- -That's pretty much it. +That is pretty much it. == Overriding dependency factory for tests and mocking dependencies @@ -229,10 +229,10 @@ all of your tests might need to mock some of the services, without affecting other tests. Doing this manually would be nightmarish, extremely prone to errors. The way to do it is to have isolated dependency graph for your tests. The key is realizing that the -`DependencyFactory` could be just a normal scala instance that itself +`DependencyFactory` could be just a normal Scala instance that itself can be injected as needed. See, there are no turtles all the way!. -This is what David Pollak keeps saying repeatedly about lift and its simplicity. -It's just scala code. There is no magic here. +This is what David Pollak keeps saying repeatedly about Lift and its simplicity. +It is just Scala code. There is no magic here. A trait that represents your `DependencyFactory` @@ -376,7 +376,7 @@ you don't need the session/request scopes. == Conclusion Most of the time, you should be able to do away with any specialized -DI framework. Lift's DI facilities provide a powerful and flexible mechanism for vending instances +dependency injection framework. Lift's dependency injection facilities provide a powerful and flexible mechanism for vending instances based on a global function, call stack scoping, request and session scoping and provides more flexible features than most Java-based dependency injection frameworks without resorting to XML for configuration or byte-code rewriting magic. \ No newline at end of file From f3c18b83d9d467f44f35b9c7a86ed63f3c2ed1ae Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sun, 25 Jun 2017 08:41:53 +0530 Subject: [PATCH 1563/1949] Move Spring comparison to a section at the end. Remove spring references from the rest of the stuff. Clear up some sentence strucutres --- docs/dependency-injection-liftweb-scala.adoc | 127 +++++++++---------- 1 file changed, 62 insertions(+), 65 deletions(-) diff --git a/docs/dependency-injection-liftweb-scala.adoc b/docs/dependency-injection-liftweb-scala.adoc index c5c803c6ad..ea6f8282a6 100644 --- a/docs/dependency-injection-liftweb-scala.adoc +++ b/docs/dependency-injection-liftweb-scala.adoc @@ -8,57 +8,45 @@ == Introduction Dependency injection is an important topic in the JVM world. With Java, we usually go with Guice -or Spring whenever we need it. However, scala has certain advantages over Java, like functions, -that allow basic DI without resorting to entire DI frameworks. +or Spring whenever we need it. However, Scala provides some unique advantages that allow many of the +features needed for dependency injection without requiring an entire framework. -Scala allows the use of http://jonasboner.com/real-world-scala-dependency-injection-di/[cake pattern] -that can provide DI by composing traits. +Scala allows the use of http://jonasboner.com/real-world-scala-dependency-injection-di/[cake pattern], which +facilitates composing complex classes out of Scala traits; however, it doesn't provide complete dependency +injection functionality. In particular, it is less helpful in terms of allowing you to make dynamic +choices about which combination of dependencies to vend in a given situation. Lift provides some additional +features that complete the dependency injection puzzle. -Although the cake pattern is helpful, it doesn't provide complete DI functionality. -The cake pattern allows you to compose complex classes out of Scala traits, but it is less helpful -in terms of allowing you to make dynamic choices about which combination of cake to vend in a given situation. -Lift provides extra features that complete the dependency injection puzzle. - -== Why Lift Dependency Injection +== Why Lift's Dependency Injection Many people come to Scala from a Java background, so it is only natural to want to use Guice/Spring, -which do a great job in the java world. And we can https://groups.google.com/forum/#!topic/liftweb/_lleL2xpCFU[use those], -but there's a better, more direct way that is less magical. +which do a great job in the java world. While https://groups.google.com/forum/#!topic/liftweb/_lleL2xpCFU[those can be used], +there is a better, more direct way that is less magical. Lift's philosophy is to keep things as simple as possible (and no simpler), and allow full control to the developers over how things work. To paraphrase David Pollak, "The abstraction turtles have to end somewhere" and Lift's Dependency Injection features end those turtles quite early. -== Lift's dependency injection and various dependency scopes (e.g. from Spring) - -Lift's dependency injection allows all the -https://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-scopes[four most commonly used scopes] that -are possible with spring: -https://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-scopes-singleton[singleton], -https://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-scopes-prototype[prototype], https://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-scopes-request[request], -and https://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-scopes-session[session]. The others can be achieved -relatively easily if you want. - == Getting started -Lift's dependency injection is based on two main traits https://liftweb.net/api/26/api/index.html#net.liftweb.util.Injector[`Injector`] -and https://liftweb.net/api/26/api/index.html#net.liftweb.util.Maker[`Maker`]. However, most of the time, you won't need to -deal with them directly. The elements that you would use are: +Lift's dependency injection is based on two main traits: https://liftweb.net/api/26/api/index.html#net.liftweb.util.Injector[`Injector`] +and https://liftweb.net/api/26/api/index.html#net.liftweb.util.Maker[`Maker`]; however, most of the time, +three higher level elements can be used: -. https://liftweb.net/api/26/api/index.html#net.liftweb.http.Factory[`Factory`] trait -. https://github.com/lift/framework/blob/5033c8798d4444f81996199c10ea330770e47fbc/web/webkit/src/main/scala/net/liftweb/http/Factory.scala#L37[`FactoryMaker`], which is an abstract class inside - inside the [`Factory`] trait. +. The https://liftweb.net/api/26/api/index.html#net.liftweb.http.Factory[`Factory`] trait +. The https://github.com/lift/framework/blob/5033c8798d4444f81996199c10ea330770e47fbc/web/webkit/src/main/scala/net/liftweb/http/Factory.scala#L37[`FactoryMaker`], an abstract class inside + inside the `Factory` trait. . https://liftweb.net/api/26/api/index.html#net.liftweb.util.SimpleInjector$Inject[`Inject`], an abstract class within the `SimpleInjector` trait. `FactoryMaker` and `Inject` serve the same purpose, with the former -having more features. The difference is important. Notes on this are -at the end. I'll continue with `FactoryMaker` for the sake of -examples, and then note the differences between the two at the end. +having more features. An important difference between them is related to performance, +and is discussed in the section <>. +Note that the following examples will continue using the `FactoryMaker` trait; however, +you should be able to use `Inject` identically. -To start with, here's an example of how these are supposed to be -used: +To start with, here is an example of how these are supposed to be used: [source,scala] ---- @@ -81,20 +69,19 @@ This defines two managed dependencies. In the case of the on production/staging mode or some other mode (e.g. development). The `emailService` is a singleton dependency. That is, everyone who injects this dependency will get the same instance throughout the -app. +app. This is sometimes referred to as the `singleton` scope. -Note that `FactoryMaker` constructor takes a stock scala function that -returns an instance of the needed type. A http://www.scala-lang.org/api/current/scala/Function0.html[`Function0`] to +Note that the `FactoryMaker` constructor takes a stock scala function that +returns an instance of the needed type -- a http://www.scala-lang.org/api/current/scala/Function0.html[`Function0`] to be precise. If you pass in a pre-created instance, as we are doing in the case of `emailService`, that instance will always be -returned when this dependency is injected (scala converts that -instance to a `Function0`). This is similar to -the https://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-scopes-singleton[singleton scope] in Spring dependency injection. +returned when this dependency is injected (scala promotes that +instance to a `Function0`). However, since this is a normal scala function, you can write whatever you want within that function. For ex. as the `sequenceNumberService` illustrates, you can generate a new instance every time it needs to be -injected. Instances could be generated every time, or they could be +injected. This is sometimes called the `prototype` scope. Instances could be generated every time, or they could be generated fresh if some condition is met and so on and so forth. Here's how you can use these dependencies: @@ -111,15 +98,14 @@ class SomeClass { ---- You can use the `apply` or `vend` method on the FactoryMaker directly, -which will give you the instance you need. I'll be using `vend` for -further examples just so that it is clear what is happening. +which will give you the instance you need. The following examples will be +using `vend` just so that it is clear what is happening. Calling `DependencyFactory.sequenceNumberService.vend` will return a -new number every time, since that is how it has been setup. This is, in -spring terminology, https://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-scopes-prototype[the prototype scope]. +new number every time, since that is how it has been setup. An alternative way is to use `DependencyFactory.inject`. But it -returns `Box` and it is only needed if don't have a FactoryMaker for a +returns `Box` and it is only needed if you don't have a `FactoryMaker` for a given dependency. Which brings us to the fact that there are other ways of registering dependencies apart from the FactoryMaker. For ex. @@ -152,14 +138,13 @@ This will set the `emailService` to always return the `customEmailService` instance for the duration of the current session for the current user. -Note that is not equivalent to the https://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-scopes-session[session-scoped] -dependency in spring. This is done explicitly in your application -code. You are overriding a default dependency with some custom -instance for the duration of _this_ session. No other user is going to +Note that is not a session-scoped dependency (as recognized by Spring, for ex.). +This is done explicitly in your application code. You are overriding a default dependency +with some custom instance for the duration of _this_ session. No other user is going to see it. And as soon as this session is over, it will be gone until you set it explicitly again. -You can something similar when you need to override a dependency +You can do something similar when you need to override a dependency during a given request. [source] @@ -168,26 +153,23 @@ val customEmailService = new CustomEmailService(currentUser) DependencyFactory.emailService.request(customEmailService) ---- -Again, this is not a https://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-scopes-request[request scoped dependency] as -identified by spring. +Again, this is not a request scoped dependency, it is an override for the duration of a given request. == Session or request scoped dependencies The above examples only set the dependencies for the duration of a -given session or request,and only when the relevant code that sets +given session or request, and only when the relevant code that sets those dependencies was executed. What if you want to always create a session/request scoped dependency -for all the users. Let's talk -about https://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-scopes-session[session scoped] dependencies. The discussion -would be identical for -the https://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-scopes-request[request scoped dependencies]. With session +for all the users. Let's talk about session scoped dependencies. The discussion +would be identical for request scoped dependencies. With session scoped dependencies, we want a new instance to be created for each session, for all the users. === Request scope with request lifecycle hooks -In your Boot.scala, which is used for instantiating and configuring +In your `Boot.scala`, which is generally used for instantiating and configuring various stuff in Lift: [source,scala] @@ -268,7 +250,7 @@ object DependencyFactory extends Factory { } ---- -Now, when you do `DependencyFactory.cardService.vend`, it will using +Now, when you do `DependencyFactory.cardService.vend`, it will use the `DefaultInstance`. Your call will be implicitly translated to `DependencyFactory.instance.vend.cardService.vend`. This is the part that allows you to completely override everything you need in your @@ -287,10 +269,10 @@ class SomeSpec { } ---- -However, there is a possible problem with this approach (I haven't -tested it). If your test suites are running in parallel, this -set/reset of the default instance will be problematic. I don't -recommend this approach unless you know what you are doing. +However, there is a potential problem with this approach. +If your test suites are running in parallel, this +set/reset of the default instance will be problematic. This approach is not +recommended unless you know what you are doing. One safe way of doing this is to use the stackable nature of the `Makers`: @@ -373,10 +355,25 @@ You can see the locking https://github.com/lift/framework/blob/5033c8798d4444f81 `SessionVar` instances in general. So, there you go. Use `Inject` if you don't need the session/request scopes. +== Lift's dependency injection and Spring's dependency scopes + +Lift's dependency injection easily allows all the +https://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-scopes[four most commonly used scopes] that +are possible with spring: +https://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-scopes-singleton[singleton], +https://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-scopes-prototype[prototype], https://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-scopes-request[request], +and https://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-scopes-session[session], and then some! + +For ex. the https://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-scopes-singleton[singleton scope] is achieved when +the function (the `Vendor`) used for constructing the `FactoryMaker` or `Inject` returns the same instance every time. +The https://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-scopes-prototype[prototype scope] is +achieved when that function returns a new instance every time it is called. Request and session scopes are explained +in the section <>. The other scopes can be achieved relatively easily if needed. + == Conclusion Most of the time, you should be able to do away with any specialized -dependency injection framework. Lift's dependency injection facilities provide a powerful and flexible mechanism for vending instances +dependency injection framework. Lift provides powerful and flexible mechanisms for vending instances based on a global function, call stack scoping, request and session scoping and provides more -flexible features than most Java-based dependency injection frameworks without resorting to XML +flexible features than most Java-based dependency injection frameworks, without resorting to XML for configuration or byte-code rewriting magic. \ No newline at end of file From ba011d3ba45d42b93422bd4915823d11255a0db9 Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sun, 25 Jun 2017 09:23:36 +0530 Subject: [PATCH 1564/1949] Tests for the mapFailure method --- .../scala/net/liftweb/common/BoxSpec.scala | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala index bcdd55a97f..bb7a4534f1 100644 --- a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala +++ b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala @@ -67,8 +67,8 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { Full(1) reduceLeft {(x: Int, y: Int) => x + y} must_== 1 } "be used as an Option" in { - Full(1) orElse Some(2) must_== Some(1) - Empty orElse Some(2) must_== Some(2) + Full(1) orElse Some(2) must beSome(1) + Empty orElse Some(2) must beSome(2) } "be implicitly defined from an Option. The openOrThrowException method can be used on an Option for example" in { Some(1).openOrThrowException("This is a test") must_== 1 @@ -152,6 +152,9 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { Full(Empty).flatten must_== Empty } } + "define a 'mapFailure' method returning itself." in { + Full(1).mapFailure(identity) must_== Full(1) + } "define an 'elements' method returning an iterator containing its value" in { Full(1).elements.next must_== 1 } @@ -159,7 +162,7 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { Full(1).toList must_== List(1) } "define a 'toOption' method returning a Some object containing its value" in { - Full(1).toOption must_== Some(1) + Full(1).toOption must beSome(1) } "return itself if asked for its status with the operator ?~" in { Full(1) ?~ "error" must_== Full(1) @@ -298,6 +301,9 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { "define a 'flatten' method returning Empty" in { Empty.flatten must beEmpty } + "define a 'mapFailure' method returning itself." in { + Empty.mapFailure(identity) must beEmpty + } "define an 'elements' method returning an empty iterator" in { Empty.elements.hasNext must beFalse } @@ -305,7 +311,7 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { Empty.toList must_== Nil } "define a 'toOption' method returning None" in { - Empty.toOption must_== None + Empty.toOption must beNone } "return a failure with a message if asked for its status with the operator ?~" in { Empty ?~ "nothing" must_== Failure("nothing", Empty, Empty) @@ -358,7 +364,11 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { Failure("error", Empty, Empty) flatMap {x: String => Full(x.toString)} must_== Failure("error", Empty, Empty) Failure("error", Empty, Empty).flatten must_== Failure("error", Empty, Empty) } - "return a itself when asked for its status with the operator ?~" in { + "define a 'mapFailure' method that transforms it into another Failure instance." in { + val exception = new Exception("transformed") + Failure("error", Empty, Empty) mapFailure { _ => Failure("new-error", Full(exception), Empty) } must_=== Failure("new-error", Full(exception), Empty) + } + "return itself when asked for its status with the operator ?~" in { Failure("error", Empty, Empty) ?~ "nothing" must_== Failure("error", Empty, Empty) } "create a new failure with a chained message if asked for its status with the operator ?~!" in { From d02d5ec831838fe14b499a2a98cbc6300fe4bd3b Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sun, 25 Jun 2017 10:22:44 +0530 Subject: [PATCH 1565/1949] Throwaway test to demo incorrect impl. --- .../provider/servlet/HTTPRequestServlet.scala | 10 +++- .../servlet/OfflineRequestSnapshotSpec.scala | 57 +++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 web/webkit/src/test/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshotSpec.scala diff --git a/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPRequestServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPRequestServlet.scala index 7ef98da53d..a04c777401 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPRequestServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPRequestServlet.scala @@ -178,7 +178,7 @@ class HTTPRequestServlet(val req: HttpServletRequest, val provider: HTTPProvider } } -private class OfflineRequestSnapshot(req: HTTPRequest, val provider: HTTPProvider) extends HTTPRequest { +private [servlet] class OfflineRequestSnapshot(req: HTTPRequest, val provider: HTTPProvider) extends HTTPRequest { private val _cookies = List(req.cookies :_*) @@ -195,6 +195,14 @@ private class OfflineRequestSnapshot(req: HTTPRequest, val provider: HTTPProvide def headers(name: String): List[String] = _headers.filter(_.name == name).map(_.name) + // Just as a sample, to be replaced later + def _newheaders(name: String): List[String] = { + _headers + .find(_.name.equalsIgnoreCase(name)) + .map(_.values) + .getOrElse(Nil) + } + def headers: List[HTTPParam] = _headers val contextPath: String = req.contextPath diff --git a/web/webkit/src/test/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshotSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshotSpec.scala new file mode 100644 index 0000000000..95c4ae682e --- /dev/null +++ b/web/webkit/src/test/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshotSpec.scala @@ -0,0 +1,57 @@ +/* + * Copyright 2010-2011 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.http.provider.servlet + +import net.liftweb.http.provider._ +import net.liftweb.mockweb.WebSpec +import org.mockito.Mockito._ +import org.specs2.mock.Mockito + + +/** + * System under specification for Req. + */ +object OfflineRequestSnapshotSpec extends WebSpec with Mockito { + + "A snapshot request should" in { + val mockHttpRequest = mock[HTTPRequest] + val mockProvider = mock[HTTPProvider] + val headers = HTTPParam("X-SSL", List("true")) :: Nil + when(mockHttpRequest.headers).thenReturn(headers) + when(mockHttpRequest.cookies).thenReturn(Nil) + when(mockHttpRequest.params).thenReturn(Nil) + when(mockHttpRequest.serverPort).thenReturn(80) + val snapshotReq = new OfflineRequestSnapshot(mockHttpRequest, mockProvider) + + "have a headers method that returns the list of headers with a given name" in { + snapshotReq.headers("X-SSL") shouldEqual List("true") + + // this test shouldn't be successful + snapshotReq.headers("X-SSL") shouldEqual List("X-SSL") + } + + "the new headers implementation should work correctly" in { + snapshotReq._newheaders("X-SSL") shouldEqual List("true") + } + + "return server-port 443 when the X-SSL header is present" in { + snapshotReq.serverPort shouldEqual 443 + } + } + +} + From dbf0a5209d788b43e40bb0d64f9b8da9cee3b0ae Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sun, 25 Jun 2017 10:27:18 +0530 Subject: [PATCH 1566/1949] Removed an unneeded comment --- .../http/provider/servlet/OfflineRequestSnapshotSpec.scala | 3 --- 1 file changed, 3 deletions(-) diff --git a/web/webkit/src/test/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshotSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshotSpec.scala index 95c4ae682e..baac8155cb 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshotSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshotSpec.scala @@ -22,9 +22,6 @@ import org.mockito.Mockito._ import org.specs2.mock.Mockito -/** - * System under specification for Req. - */ object OfflineRequestSnapshotSpec extends WebSpec with Mockito { "A snapshot request should" in { From 7bfa94e18dc44f0942e287ff064a2f254f5aee0e Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sun, 25 Jun 2017 11:09:03 +0530 Subject: [PATCH 1567/1949] Optional version for MongoCaseClassField --- .../record/field/MongoCaseClassField.scala | 34 +++++++++++++++---- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoCaseClassField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoCaseClassField.scala index 67aaa09ef6..4f81824c3b 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoCaseClassField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoCaseClassField.scala @@ -30,24 +30,22 @@ import reflect.Manifest import net.liftweb.http.js.JsExp import org.bson.Document import scala.collection.JavaConverters._ -class MongoCaseClassField[OwnerType <: Record[OwnerType],CaseType](rec: OwnerType)( implicit mf: Manifest[CaseType]) extends Field[CaseType, OwnerType] with MandatoryTypedField[CaseType] with MongoFieldFlavor[CaseType] { + +sealed abstract class CaseClassTypedField[OwnerType <: Record[OwnerType], CaseType](override val owner: OwnerType)(implicit mf: Manifest[CaseType]) + extends Field[CaseType, OwnerType] with MongoFieldFlavor[CaseType] { // override this for custom formats def formats: Formats = DefaultFormats + implicit lazy val _formats = formats override type MyType = CaseType - def owner = rec - def asXHtml = Text(value.toString) def toForm: Box[NodeSeq] = Empty - override def defaultValue = null.asInstanceOf[MyType] - override def optional_? = true - - def asJValue: JValue = valueBox.map(v => Extraction.decompose(v)) openOr (JNothing: JValue) + def asJValue: JValue = valueBox.map(Extraction.decompose) openOr (JNothing: JValue) def setFromJValue(jvalue: JValue): Box[CaseType] = jvalue match { case JNothing|JNull => setBox(Empty) @@ -81,6 +79,28 @@ class MongoCaseClassField[OwnerType <: Record[OwnerType],CaseType](rec: OwnerTyp case (failure: Failure) => setBox(failure) case _ => setBox(defaultValueBox) } + + +} + +class MongoCaseClassField[OwnerType <: Record[OwnerType], CaseType](rec: OwnerType)(implicit mf: Manifest[CaseType]) + extends CaseClassTypedField[OwnerType, CaseType](rec) with MandatoryTypedField[CaseType] { + + def this(owner: OwnerType, value: CaseType)(implicit mf: Manifest[CaseType]) = { + this(owner) + setBox(Full(value)) + } + + override def defaultValue = null.asInstanceOf[MyType] +} + +class OptionalMongoCaseClassField[OwnerType <: Record[OwnerType], CaseType](rec: OwnerType)(implicit mf: Manifest[CaseType]) + extends CaseClassTypedField[OwnerType, CaseType](rec) with OptionalTypedField[CaseType] { + + def this(owner: OwnerType, value: Box[CaseType])(implicit mf: Manifest[CaseType]) = { + this(owner) + setBox(value) + } } class MongoCaseClassListField[OwnerType <: Record[OwnerType],CaseType](rec: OwnerType)( implicit mf: Manifest[CaseType]) extends Field[List[CaseType], OwnerType] with MandatoryTypedField[List[CaseType]] with MongoFieldFlavor[List[CaseType]] { From 5c607f9138b91fb6c8a9bd9c4161c6110cc0ef36 Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Mon, 26 Jun 2017 09:25:00 +0530 Subject: [PATCH 1568/1949] change mapFailure tests to use a valid Failure param when testing with Empty or Full. Replace must_=== with must_== --- .../src/test/scala/net/liftweb/common/BoxSpec.scala | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala index bb7a4534f1..d80613b820 100644 --- a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala +++ b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala @@ -153,7 +153,8 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { } } "define a 'mapFailure' method returning itself." in { - Full(1).mapFailure(identity) must_== Full(1) + val failure = Failure("new-error", Empty, Empty) + Full(1).mapFailure(_ => failure) must_== Full(1) } "define an 'elements' method returning an iterator containing its value" in { Full(1).elements.next must_== 1 @@ -302,7 +303,8 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { Empty.flatten must beEmpty } "define a 'mapFailure' method returning itself." in { - Empty.mapFailure(identity) must beEmpty + val failure = Failure("new-error", Empty, Empty) + Empty.mapFailure(_ => failure) must beEmpty } "define an 'elements' method returning an empty iterator" in { Empty.elements.hasNext must beFalse @@ -366,7 +368,7 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { } "define a 'mapFailure' method that transforms it into another Failure instance." in { val exception = new Exception("transformed") - Failure("error", Empty, Empty) mapFailure { _ => Failure("new-error", Full(exception), Empty) } must_=== Failure("new-error", Full(exception), Empty) + Failure("error", Empty, Empty) mapFailure { _ => Failure("new-error", Full(exception), Empty) } must_== Failure("new-error", Full(exception), Empty) } "return itself when asked for its status with the operator ?~" in { Failure("error", Empty, Empty) ?~ "nothing" must_== Failure("error", Empty, Empty) From 42bbf08af0ca67141ac0ed9a7ee57fbeeb0466f5 Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Mon, 26 Jun 2017 09:58:57 +0530 Subject: [PATCH 1569/1949] Change the collect function to be a bit more readable. Add tests for `collect`. --- .../src/main/scala/net/liftweb/common/Box.scala | 6 +----- .../test/scala/net/liftweb/common/BoxSpec.scala | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index 51cf499fe1..1c59586327 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -797,11 +797,7 @@ sealed abstract class Box[+A] extends Product with Serializable{ * If the partial function is defined at the current Box's value, apply the * partial function. */ - final def collect[B](pf: PartialFunction[A, B]): Box[B] = { - flatMap(value => - if (pf.isDefinedAt(value)) Full(pf(value)) - else Empty) - } + final def collect[B](pf: PartialFunction[A, B]): Box[B] = filter(pf.isDefinedAt).map(pf) /** * An alias for `collect`. diff --git a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala index d80613b820..9e4bca1118 100644 --- a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala +++ b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala @@ -156,6 +156,14 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { val failure = Failure("new-error", Empty, Empty) Full(1).mapFailure(_ => failure) must_== Full(1) } + "define a 'collect' method that takes a PartialFunction to transform its contents" in { + "If the partial-function is defined for the contents of this box, returns a full box containing the result of applying that partial function to this Box's contents" in { + Full("Albus") collect { case "Albus" => "Dumbledore"} must_== Full("Dumbledore") + } + "If the partial-function is not defined for the contents of this box, returns Empty" in { + Full("Hermione") collect { case "Albus" => "Dumbledore"} must beEmpty + } + } "define an 'elements' method returning an iterator containing its value" in { Full(1).elements.next must_== 1 } @@ -306,6 +314,9 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { val failure = Failure("new-error", Empty, Empty) Empty.mapFailure(_ => failure) must beEmpty } + "define a 'collect' method returning Empty" in { + Empty collect { case _ => "Some Value"} must beEmpty + } "define an 'elements' method returning an empty iterator" in { Empty.elements.hasNext must beFalse } @@ -370,6 +381,9 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { val exception = new Exception("transformed") Failure("error", Empty, Empty) mapFailure { _ => Failure("new-error", Full(exception), Empty) } must_== Failure("new-error", Full(exception), Empty) } + "define a 'collect' method returning itself" in { + Failure("error", Empty, Empty) collect { case _ => "Some Value"} must_== Failure("error", Empty, Empty) + } "return itself when asked for its status with the operator ?~" in { Failure("error", Empty, Empty) ?~ "nothing" must_== Failure("error", Empty, Empty) } From a5b3972afd14fa4a6c3148e2019163d06725f88d Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Mon, 26 Jun 2017 10:23:39 +0530 Subject: [PATCH 1570/1949] Corrected spacing --- core/common/src/test/scala/net/liftweb/common/BoxSpec.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala index 9e4bca1118..02de73a878 100644 --- a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala +++ b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala @@ -315,7 +315,7 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { Empty.mapFailure(_ => failure) must beEmpty } "define a 'collect' method returning Empty" in { - Empty collect { case _ => "Some Value"} must beEmpty + Empty collect { case _ => "Some Value" } must beEmpty } "define an 'elements' method returning an empty iterator" in { Empty.elements.hasNext must beFalse @@ -382,7 +382,7 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { Failure("error", Empty, Empty) mapFailure { _ => Failure("new-error", Full(exception), Empty) } must_== Failure("new-error", Full(exception), Empty) } "define a 'collect' method returning itself" in { - Failure("error", Empty, Empty) collect { case _ => "Some Value"} must_== Failure("error", Empty, Empty) + Failure("error", Empty, Empty) collect { case _ => "Some Value" } must_== Failure("error", Empty, Empty) } "return itself when asked for its status with the operator ?~" in { Failure("error", Empty, Empty) ?~ "nothing" must_== Failure("error", Empty, Empty) From 6e1822eef9e9350db75f0ae881ba1d0e35327bbb Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Mon, 26 Jun 2017 13:17:15 +0530 Subject: [PATCH 1571/1949] Move OfflineRequestSnapshot to its own file --- .../provider/servlet/HTTPRequestServlet.scala | 117 +---------------- .../servlet/OfflineRequestSnapshot.scala | 118 ++++++++++++++++++ 2 files changed, 122 insertions(+), 113 deletions(-) create mode 100644 web/webkit/src/main/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshot.scala diff --git a/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPRequestServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPRequestServlet.scala index a04c777401..966970e593 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPRequestServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPRequestServlet.scala @@ -19,9 +19,9 @@ package http package provider package servlet -import java.io.{InputStream} -import java.util.{Locale} -import javax.servlet.http.{HttpServletRequest} +import java.io.InputStream +import java.util.Locale +import javax.servlet.http.HttpServletRequest import org.apache.commons.fileupload.servlet._ import org.apache.commons.fileupload.ProgressListener import net.liftweb.common._ @@ -127,7 +127,7 @@ class HTTPRequestServlet(val req: HttpServletRequest, val provider: HTTPProvider } yield id def extractFiles: List[ParamHolder] = (new Iterator[ParamHolder] { - val mimeUpload = (new ServletFileUpload) + val mimeUpload = new ServletFileUpload mimeUpload.setProgressListener(new ProgressListener { lazy val progList: (Long, Long, Int) => Unit = S.session.flatMap(_.progressListener) openOr LiftRules.progressListener @@ -178,113 +178,4 @@ class HTTPRequestServlet(val req: HttpServletRequest, val provider: HTTPProvider } } -private [servlet] class OfflineRequestSnapshot(req: HTTPRequest, val provider: HTTPProvider) extends HTTPRequest { - private val _cookies = List(req.cookies :_*) - - private val _headers = List(req.headers :_*) - - private val _params = List(req.params :_*) - - private [this] val _serverPort = req.serverPort - - - def cookies: List[HTTPCookie] = _cookies - - val authType: Box[String] = req.authType - - def headers(name: String): List[String] = _headers.filter(_.name == name).map(_.name) - - // Just as a sample, to be replaced later - def _newheaders(name: String): List[String] = { - _headers - .find(_.name.equalsIgnoreCase(name)) - .map(_.values) - .getOrElse(Nil) - } - - def headers: List[HTTPParam] = _headers - - val contextPath: String = req.contextPath - - val context: HTTPContext = req.context - - val contentType: Box[String] = req.contentType - - val uri: String = req.uri - - val url: String = req.url - - val queryString: Box[String] = req.queryString - - def param(name: String): List[String] = _params.filter(_.name == name).map(_.name) - - def params: List[HTTPParam] = _params - - def paramNames: List[String] = _params.map(_.name) - - /** - * Destroy the underlying servlet session... null for offline requests - */ - def destroyServletSession() { - // do nothing here - } - - val session: HTTPSession = req.session - - val sessionId: Box[String] = req.sessionId - - val remoteAddress: String = req.remoteAddress - - val remotePort: Int = req.remotePort - - val remoteHost: String = req.remoteHost - - val serverName: String = req.serverName - - val scheme: String = req.scheme - - lazy val serverPort: Int = _serverPort match { - case 80 => - headers("X-SSL") - .flatMap(Helpers.asBoolean) - .filter(identity) - .map(_ => 443) - .headOption - .getOrElse(80) - case x => x - } - - val method: String = req.method - - val resumeInfo : Option[(Req, LiftResponse)] = req.resumeInfo - - def suspend(timeout: Long): RetryState.Value = - throw new UnsupportedOperationException("Cannot suspend a snapshot") - - def resume(what: (Req, LiftResponse)): Boolean = - throw new UnsupportedOperationException("Cannot resume a snapshot") - - def suspendResumeSupport_? = false - - def inputStream: InputStream = - throw new UnsupportedOperationException("InputStream is not available") - - val multipartContent_? : Boolean = req.multipartContent_? - - def extractFiles: List[ParamHolder] = - throw new UnsupportedOperationException("It is unsafe to extract files") - - val locale: Box[Locale] = req.locale - - def setCharacterEncoding(encoding: String) = - throw new UnsupportedOperationException("It is unsafe to set the character encoding ") - - def snapshot = this - - /** - * The User-Agent of the request - */ - lazy val userAgent: Box[String] = headers find (_.name equalsIgnoreCase "user-agent") flatMap (_.values.headOption) - -} diff --git a/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshot.scala b/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshot.scala new file mode 100644 index 0000000000..1a0a4bd584 --- /dev/null +++ b/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshot.scala @@ -0,0 +1,118 @@ +package net.liftweb.http.provider.servlet + +import java.io.InputStream +import java.util.Locale + +import net.liftweb.common.Box +import net.liftweb.http.provider._ +import net.liftweb.http.{LiftResponse, ParamHolder, Req} +import net.liftweb.util.Helpers + +private [servlet] class OfflineRequestSnapshot(req: HTTPRequest, val provider: HTTPProvider) extends HTTPRequest { + + private val _cookies = List(req.cookies :_*) + + private val _headers = List(req.headers :_*) + + private val _params = List(req.params :_*) + + private [this] val _serverPort = req.serverPort + + + def cookies: List[HTTPCookie] = _cookies + + val authType: Box[String] = req.authType + + // Just as a sample, to be replaced later + def headers(name: String): List[String] = { + _headers + .find(_.name.equalsIgnoreCase(name)) + .map(_.values) + .getOrElse(Nil) + } + + def headers: List[HTTPParam] = _headers + + val contextPath: String = req.contextPath + + val context: HTTPContext = req.context + + val contentType: Box[String] = req.contentType + + val uri: String = req.uri + + val url: String = req.url + + val queryString: Box[String] = req.queryString + + def param(name: String): List[String] = _params.filter(_.name == name).map(_.name) + + def params: List[HTTPParam] = _params + + def paramNames: List[String] = _params.map(_.name) + + /** + * Destroy the underlying servlet session... null for offline requests + */ + def destroyServletSession() { + // do nothing here + } + + val session: HTTPSession = req.session + + val sessionId: Box[String] = req.sessionId + + val remoteAddress: String = req.remoteAddress + + val remotePort: Int = req.remotePort + + val remoteHost: String = req.remoteHost + + val serverName: String = req.serverName + + val scheme: String = req.scheme + + lazy val serverPort: Int = _serverPort match { + case 80 => + headers("X-SSL") + .flatMap(Helpers.asBoolean) + .filter(identity) + .map(_ => 443) + .headOption + .getOrElse(80) + case x => x + } + + val method: String = req.method + + val resumeInfo : Option[(Req, LiftResponse)] = req.resumeInfo + + def suspend(timeout: Long): RetryState.Value = + throw new UnsupportedOperationException("Cannot suspend a snapshot") + + def resume(what: (Req, LiftResponse)): Boolean = + throw new UnsupportedOperationException("Cannot resume a snapshot") + + def suspendResumeSupport_? = false + + def inputStream: InputStream = + throw new UnsupportedOperationException("InputStream is not available") + + val multipartContent_? : Boolean = req.multipartContent_? + + def extractFiles: List[ParamHolder] = + throw new UnsupportedOperationException("It is unsafe to extract files") + + val locale: Box[Locale] = req.locale + + def setCharacterEncoding(encoding: String) = + throw new UnsupportedOperationException("It is unsafe to set the character encoding ") + + def snapshot = this + + /** + * The User-Agent of the request + */ + lazy val userAgent: Box[String] = headers find (_.name equalsIgnoreCase "user-agent") flatMap (_.values.headOption) + +} From 875a787e0c912d748c53ed550005d0226693af7f Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Mon, 26 Jun 2017 13:17:41 +0530 Subject: [PATCH 1572/1949] Better tests --- .../servlet/OfflineRequestSnapshotSpec.scala | 50 ++++++++++++------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/web/webkit/src/test/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshotSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshotSpec.scala index baac8155cb..123725d857 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshotSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshotSpec.scala @@ -24,30 +24,42 @@ import org.specs2.mock.Mockito object OfflineRequestSnapshotSpec extends WebSpec with Mockito { - "A snapshot request should" in { - val mockHttpRequest = mock[HTTPRequest] - val mockProvider = mock[HTTPProvider] - val headers = HTTPParam("X-SSL", List("true")) :: Nil - when(mockHttpRequest.headers).thenReturn(headers) - when(mockHttpRequest.cookies).thenReturn(Nil) - when(mockHttpRequest.params).thenReturn(Nil) - when(mockHttpRequest.serverPort).thenReturn(80) - val snapshotReq = new OfflineRequestSnapshot(mockHttpRequest, mockProvider) + private val X_SSL = "X-SSL" - "have a headers method that returns the list of headers with a given name" in { - snapshotReq.headers("X-SSL") shouldEqual List("true") - - // this test shouldn't be successful - snapshotReq.headers("X-SSL") shouldEqual List("X-SSL") + "OfflineRequestSnapshot" should { + "have a 'headers' method that returns the list of headers with a given name" in { + val req = getRequestSnapshot(originalPort = 80) + req.headers("X-SSL") shouldEqual List("true") + req.headers("Unknown") must beEmpty } - "the new headers implementation should work correctly" in { - snapshotReq._newheaders("X-SSL") shouldEqual List("true") + "have the serverPort value" in { + "443 when the 'X-SSL' header is set to the string 'true' (case-insensitive) and original port is 80" in { + val port80Req = getRequestSnapshot(originalPort = 80) + port80Req.serverPort shouldEqual 443 + } + s"equal to the original request-port when the '$X_SSL' header is absent or not set to the string 'true' (case-insensitive), or if the original port is not 80" in { + val req = getRequestSnapshot(originalPort = 90) + val nonSSLReq = getRequestSnapshot(originalPort = 80, headers = Nil) + val falseSSLHeaderReq = getRequestSnapshot(originalPort = 90, headers = HTTPParam(X_SSL, List("anything")) :: Nil) + req.serverPort shouldEqual 90 + nonSSLReq.serverPort shouldEqual 80 + falseSSLHeaderReq.serverPort shouldEqual 90 + } } + } - "return server-port 443 when the X-SSL header is present" in { - snapshotReq.serverPort shouldEqual 443 - } + private val xSSLHeader = HTTPParam(X_SSL, List("true")) :: Nil + + private def getRequestSnapshot(originalPort: Int, headers: List[HTTPParam] = xSSLHeader) = { + val mockHttpRequest = mock[HTTPRequest] + val httpProvider = mock[HTTPProvider] + + when(mockHttpRequest.headers).thenReturn(headers) + when(mockHttpRequest.cookies).thenReturn(Nil) + when(mockHttpRequest.params).thenReturn(Nil) + when(mockHttpRequest.serverPort).thenReturn(originalPort) + new OfflineRequestSnapshot(mockHttpRequest, httpProvider) } } From 487628d4e396940bbe78a0117b50a7a7d0ef0188 Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Mon, 26 Jun 2017 13:25:04 +0530 Subject: [PATCH 1573/1949] Clearer tests. Removed a placeholder/demo implementation --- .../http/provider/servlet/OfflineRequestSnapshotSpec.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/web/webkit/src/test/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshotSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshotSpec.scala index 123725d857..dca3c88ccb 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshotSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshotSpec.scala @@ -26,6 +26,8 @@ object OfflineRequestSnapshotSpec extends WebSpec with Mockito { private val X_SSL = "X-SSL" + var xx: Int = _ + "OfflineRequestSnapshot" should { "have a 'headers' method that returns the list of headers with a given name" in { val req = getRequestSnapshot(originalPort = 80) From 19a64b21e61d905bfb181fc87f2c760b083346cc Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Mon, 26 Jun 2017 19:03:10 +0530 Subject: [PATCH 1574/1949] Rename MongoCaseClassField to CaseClassField, providing a backward compatible implementation marked deprecated Rename MongoCaseClassListField to CaseClassListField, providing a backward compatible implementation marked deprecated --- .../record/field/MongoCaseClassField.scala | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoCaseClassField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoCaseClassField.scala index 4f81824c3b..5a077e6e1a 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoCaseClassField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoCaseClassField.scala @@ -42,7 +42,6 @@ sealed abstract class CaseClassTypedField[OwnerType <: Record[OwnerType], CaseTy override type MyType = CaseType - def toForm: Box[NodeSeq] = Empty def asJValue: JValue = valueBox.map(Extraction.decompose) openOr (JNothing: JValue) @@ -79,11 +78,9 @@ sealed abstract class CaseClassTypedField[OwnerType <: Record[OwnerType], CaseTy case (failure: Failure) => setBox(failure) case _ => setBox(defaultValueBox) } - - } -class MongoCaseClassField[OwnerType <: Record[OwnerType], CaseType](rec: OwnerType)(implicit mf: Manifest[CaseType]) +class CaseClassField[OwnerType <: Record[OwnerType], CaseType](rec: OwnerType)(implicit mf: Manifest[CaseType]) extends CaseClassTypedField[OwnerType, CaseType](rec) with MandatoryTypedField[CaseType] { def this(owner: OwnerType, value: CaseType)(implicit mf: Manifest[CaseType]) = { @@ -94,7 +91,11 @@ class MongoCaseClassField[OwnerType <: Record[OwnerType], CaseType](rec: OwnerTy override def defaultValue = null.asInstanceOf[MyType] } -class OptionalMongoCaseClassField[OwnerType <: Record[OwnerType], CaseType](rec: OwnerType)(implicit mf: Manifest[CaseType]) +@deprecated("Use the more consistently named 'CaseClassField' instead", "3.1") +class MongoCaseClassField[OwnerType <: Record[OwnerType], CaseType](rec: OwnerType)(implicit mf: Manifest[CaseType]) + extends CaseClassField[OwnerType, CaseType](rec) + +class OptionalCaseClassField[OwnerType <: Record[OwnerType], CaseType](rec: OwnerType)(implicit mf: Manifest[CaseType]) extends CaseClassTypedField[OwnerType, CaseType](rec) with OptionalTypedField[CaseType] { def this(owner: OwnerType, value: Box[CaseType])(implicit mf: Manifest[CaseType]) = { @@ -103,7 +104,9 @@ class OptionalMongoCaseClassField[OwnerType <: Record[OwnerType], CaseType](rec: } } -class MongoCaseClassListField[OwnerType <: Record[OwnerType],CaseType](rec: OwnerType)( implicit mf: Manifest[CaseType]) extends Field[List[CaseType], OwnerType] with MandatoryTypedField[List[CaseType]] with MongoFieldFlavor[List[CaseType]] { + +class CaseClassListField[OwnerType <: Record[OwnerType], CaseType](override val owner: OwnerType)(implicit mf: Manifest[CaseType]) + extends Field[List[CaseType], OwnerType] with MandatoryTypedField[List[CaseType]] with MongoFieldFlavor[List[CaseType]] { // override this for custom formats def formats: Formats = DefaultFormats @@ -111,14 +114,11 @@ class MongoCaseClassListField[OwnerType <: Record[OwnerType],CaseType](rec: Owne override type MyType = List[CaseType] - def owner = rec - def asXHtml = Text(value.toString) def toForm: Box[NodeSeq] = Empty override def defaultValue: MyType = Nil - override def optional_? = true def asJValue: JValue = JArray(value.map(v => Extraction.decompose(v))) @@ -128,7 +128,7 @@ class MongoCaseClassListField[OwnerType <: Record[OwnerType],CaseType](rec: Owne } def setFromDocumentList(list: java.util.List[Document]): Box[MyType] = { - val objs = list.asScala.map{ d => JObjectParser.serialize(d) } + val objs = list.asScala.map { JObjectParser.serialize } setFromJValue(JArray(objs.toList)) } @@ -172,3 +172,6 @@ class MongoCaseClassListField[OwnerType <: Record[OwnerType],CaseType](rec: Owne } } +@deprecated("Please use the more consistently named 'CaseClassListField' instead", "3.1") +class MongoCaseClassListField[OwnerType <: Record[OwnerType], CaseType](owner: OwnerType)(implicit mf: Manifest[CaseType]) + extends CaseClassListField[OwnerType, CaseType](owner) \ No newline at end of file From 2b15cc51b8429dec909126794e8e48f8f1c43716 Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Mon, 26 Jun 2017 19:22:10 +0530 Subject: [PATCH 1575/1949] Optional version of BsonRecordField. Removed a deprecation warning --- .../record/field/BsonRecordField.scala | 72 +++++++++++-------- .../record/field/MongoCaseClassField.scala | 1 - 2 files changed, 41 insertions(+), 32 deletions(-) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala index 69d1e0d594..41725ecd1d 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala @@ -27,51 +27,62 @@ import net.liftweb.record._ import com.mongodb._ import org.bson.Document +import scala.reflect.Manifest import scala.xml._ /** Field that contains an entire record represented as an inline object value. Inspired by JSONSubRecordField */ -class BsonRecordField[OwnerType <: BsonRecord[OwnerType], SubRecordType <: BsonRecord[SubRecordType]] - (rec: OwnerType, valueMeta: BsonMetaRecord[SubRecordType])(implicit subRecordType: Manifest[SubRecordType]) - extends Field[SubRecordType, OwnerType] - with MandatoryTypedField[SubRecordType] -{ - def this(rec: OwnerType, valueMeta: BsonMetaRecord[SubRecordType], value: SubRecordType) - (implicit subRecordType: Manifest[SubRecordType]) = { - this(rec, value.meta) - set(value) - } +sealed abstract class BsonRecordTypedField[OwnerType <: BsonRecord[OwnerType], SubRecordType <: BsonRecord[SubRecordType]] +(override val owner: OwnerType, valueMeta: BsonMetaRecord[SubRecordType])(implicit subRecordType: Manifest[SubRecordType]) + extends Field[SubRecordType, OwnerType] { def this(rec: OwnerType, valueMeta: BsonMetaRecord[SubRecordType], value: Box[SubRecordType]) - (implicit subRecordType: Manifest[SubRecordType]) = { - this(rec, valueMeta) - setBox(value) + (implicit subRecordType: Manifest[SubRecordType]) = { + this(rec, valueMeta) + setBox(value) } - def owner = rec - def asJs = asJValue match { + override def asJs = asJValue match { case JNothing => JsNull case jv => new JsExp { lazy val toJsCmd = compactRender(jv) } } - def toForm: Box[NodeSeq] = Empty - def defaultValue = valueMeta.createRecord - def setFromString(s: String): Box[SubRecordType] = valueMeta.fromJsonString(s) + override def toForm: Box[NodeSeq] = Empty - def setFromAny(in: Any): Box[SubRecordType] = in match { + override def setFromString(s: String): Box[SubRecordType] = valueMeta.fromJsonString(s) + + override def setFromAny(in: Any): Box[SubRecordType] = in match { case dbo: DBObject => setBox(Full(valueMeta.fromDBObject(dbo))) case dbo: Document => setBox(Full(valueMeta.fromDocument(dbo))) case _ => genericSetFromAny(in) } - def asJValue: JValue = valueBox.map(_.asJValue) openOr (JNothing: JValue) - def setFromJValue(jvalue: JValue): Box[SubRecordType] = jvalue match { + override def asJValue: JValue = valueBox.map(_.asJValue) openOr (JNothing: JValue) + + override def setFromJValue(jvalue: JValue): Box[SubRecordType] = jvalue match { case JNothing|JNull if optional_? => setBox(Empty) case _ => setBox(valueMeta.fromJValue(jvalue)) } } +class BsonRecordField[OwnerType <: BsonRecord[OwnerType], SubRecordType <: BsonRecord[SubRecordType]] +(rec: OwnerType, valueMeta: BsonMetaRecord[SubRecordType])(implicit subRecordType: Manifest[SubRecordType]) + extends BsonRecordTypedField(rec, valueMeta) with MandatoryTypedField[SubRecordType] { + + def this(rec: OwnerType, valueMeta: BsonMetaRecord[SubRecordType], value: SubRecordType)(implicit subRecordType: Manifest[SubRecordType]) = { + this(rec, value.meta) + set(value) + } + + override def defaultValue = valueMeta.createRecord +} + +class OptionalBsonRecordField[OwnerType <: BsonRecord[OwnerType], SubRecordType <: BsonRecord[SubRecordType]] +(rec: OwnerType, valueMeta: BsonMetaRecord[SubRecordType])(implicit subRecordType: Manifest[SubRecordType]) + extends BsonRecordTypedField(rec, valueMeta) with OptionalTypedField[SubRecordType] + + /* * List of BsonRecords */ @@ -79,7 +90,7 @@ class BsonRecordListField[OwnerType <: BsonRecord[OwnerType], SubRecordType <: B (rec: OwnerType, valueMeta: BsonMetaRecord[SubRecordType])(implicit mf: Manifest[SubRecordType]) extends MongoListField[OwnerType, SubRecordType](rec: OwnerType) { - import scala.collection.JavaConversions._ + import scala.collection.JavaConverters._ override def validations = ((elems: ValueType) => elems.flatMap(_.validate)) :: super.validations @@ -89,25 +100,24 @@ class BsonRecordListField[OwnerType <: BsonRecord[OwnerType], SubRecordType <: B dbl } - override def setFromDBObject(dbo: DBObject): Box[List[SubRecordType]] = - setBox(Full(dbo.keySet.toList.map(k => { - valueMeta.fromDBObject(dbo.get(k.toString).asInstanceOf[DBObject]) - }))) + override def setFromDBObject(dbo: DBObject): Box[List[SubRecordType]] = { + setBox(Full(dbo.keySet.asScala.toList.map { k => + valueMeta.fromDBObject(dbo.get(k).asInstanceOf[DBObject]) + })) + } override def asJValue: JValue = JArray(value.map(_.asJValue)) override def setFromJValue(jvalue: JValue) = jvalue match { case JNothing|JNull if optional_? => setBox(Empty) - case JArray(arr) => setBox(Full(arr.map( jv => { + case JArray(arr) => setBox(Full(arr.map { jv => valueMeta.fromJValue(jv) openOr valueMeta.createRecord - }))) + })) case other => setBox(FieldHelpers.expectedA("JArray", other)) } override def setFromDocumentList(list: java.util.List[Document]): Box[List[SubRecordType]] = { - setBox(Full(list.map{ doc => - valueMeta.fromDocument(doc) - }.toList)) + setBox(Full(list.asScala.map { valueMeta.fromDocument }.toList)) } } diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoCaseClassField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoCaseClassField.scala index 5a077e6e1a..5cff812623 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoCaseClassField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoCaseClassField.scala @@ -104,7 +104,6 @@ class OptionalCaseClassField[OwnerType <: Record[OwnerType], CaseType](rec: Owne } } - class CaseClassListField[OwnerType <: Record[OwnerType], CaseType](override val owner: OwnerType)(implicit mf: Manifest[CaseType]) extends Field[List[CaseType], OwnerType] with MandatoryTypedField[List[CaseType]] with MongoFieldFlavor[List[CaseType]] { From 9b9ae1e6cd0d8c445140e4c83ff6997e3ea29b9e Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Mon, 26 Jun 2017 19:30:38 +0530 Subject: [PATCH 1576/1949] Optional version of PatternField --- .../mongodb/record/field/PatternField.scala | 39 +++++++++++-------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/PatternField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/PatternField.scala index f3a34130da..6166a0b4b8 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/PatternField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/PatternField.scala @@ -17,25 +17,17 @@ package record package field import java.util.regex.Pattern -import scala.xml.NodeSeq import net.liftweb.common.{Box, Empty, Failure, Full} import net.liftweb.http.js.JE.{JsNull, Str} import net.liftweb.json._ -import net.liftweb.mongodb.record._ -import net.liftweb.record.{Field, FieldHelpers, MandatoryTypedField} +import net.liftweb.record.{Field, FieldHelpers, MandatoryTypedField, OptionalTypedField} import net.liftweb.util.Helpers.tryo -class PatternField[OwnerType <: BsonRecord[OwnerType]](rec: OwnerType) - extends Field[Pattern, OwnerType] - with MandatoryTypedField[Pattern] -{ - - def owner = rec - - def defaultValue = Pattern.compile("") +import scala.xml.NodeSeq - def setFromAny(in: Any): Box[Pattern] = in match { +sealed abstract class PatternTypedField[OwnerType <: BsonRecord[OwnerType]](override val owner: OwnerType) extends Field[Pattern, OwnerType] { + override def setFromAny(in: Any): Box[Pattern] = in match { case p: Pattern => setBox(Full(p)) case Some(p: Pattern) => setBox(Full(p)) case Full(p: Pattern) => setBox(Full(p)) @@ -48,7 +40,7 @@ class PatternField[OwnerType <: BsonRecord[OwnerType]](rec: OwnerType) case o => setFromString(o.toString) } - def setFromJValue(jvalue: JValue): Box[Pattern] = jvalue match { + override def setFromJValue(jvalue: JValue): Box[Pattern] = jvalue match { case JNothing|JNull if optional_? => setBox(Empty) case JObject(JField("$regex", JString(s)) :: JField("$flags", JInt(f)) :: Nil) => setBox(Full(Pattern.compile(s, f.intValue))) @@ -56,15 +48,15 @@ class PatternField[OwnerType <: BsonRecord[OwnerType]](rec: OwnerType) } // parse String into a JObject - def setFromString(in: String): Box[Pattern] = tryo(JsonParser.parse(in)) match { + override def setFromString(in: String): Box[Pattern] = tryo(JsonParser.parse(in)) match { case Full(jv: JValue) => setFromJValue(jv) case f: Failure => setBox(f) case other => setBox(Failure("Error parsing String into a JValue: "+in)) } - def toForm: Box[NodeSeq] = Empty + override def toForm: Box[NodeSeq] = Empty - def asJs = asJValue match { + override def asJs = asJValue match { case JNothing => JsNull case jv => Str(compactRender(jv)) } @@ -72,3 +64,18 @@ class PatternField[OwnerType <: BsonRecord[OwnerType]](rec: OwnerType) def asJValue: JValue = valueBox.map(v => JsonRegex(v)) openOr (JNothing: JValue) } +class PatternField[OwnerType <: BsonRecord[OwnerType]](owner: OwnerType) extends PatternTypedField[OwnerType](owner) with MandatoryTypedField[Pattern] { + def this(rec: OwnerType, value: Pattern) = { + this(rec) + setBox(Full(value)) + } + + override def defaultValue = Pattern.compile("") +} + +class OptionalPatternField[OwnerType <: BsonRecord[OwnerType]](rec: OwnerType) extends PatternTypedField[OwnerType](rec) with OptionalTypedField[Pattern] { + def this(rec: OwnerType, value: Box[Pattern]) = { + this(rec) + setBox(value) + } +} From 2af50bb957ebffacda8be4124c52de392b2f9083 Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Mon, 26 Jun 2017 21:12:43 +0530 Subject: [PATCH 1577/1949] Replace the mock with a stub implementation --- .../http/provider/servlet/OfflineRequestSnapshotSpec.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/web/webkit/src/test/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshotSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshotSpec.scala index dca3c88ccb..f0a878f8c0 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshotSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshotSpec.scala @@ -55,7 +55,9 @@ object OfflineRequestSnapshotSpec extends WebSpec with Mockito { private def getRequestSnapshot(originalPort: Int, headers: List[HTTPParam] = xSSLHeader) = { val mockHttpRequest = mock[HTTPRequest] - val httpProvider = mock[HTTPProvider] + val httpProvider = new HTTPProvider { + override protected def context: HTTPContext = null + } when(mockHttpRequest.headers).thenReturn(headers) when(mockHttpRequest.cookies).thenReturn(Nil) From 5865ef93d3793b15e6307b99f63f1001885f15bd Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Mon, 26 Jun 2017 21:15:10 +0530 Subject: [PATCH 1578/1949] Removed an unused var, mistakenly added --- .../http/provider/servlet/OfflineRequestSnapshotSpec.scala | 2 -- 1 file changed, 2 deletions(-) diff --git a/web/webkit/src/test/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshotSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshotSpec.scala index f0a878f8c0..ba6dab6525 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshotSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshotSpec.scala @@ -26,8 +26,6 @@ object OfflineRequestSnapshotSpec extends WebSpec with Mockito { private val X_SSL = "X-SSL" - var xx: Int = _ - "OfflineRequestSnapshot" should { "have a 'headers' method that returns the list of headers with a given name" in { val req = getRequestSnapshot(originalPort = 80) From 72beb188ae95580b77c07c20cbcc939deb2ef392 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Mon, 26 Jun 2017 13:05:23 -0400 Subject: [PATCH 1579/1949] Update various parts of the readme Various pieces of our readme were super out of date, so I updated them. --- README.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index f375e7c48d..3c4b930e11 100644 --- a/README.md +++ b/README.md @@ -37,24 +37,24 @@ For more details, see [CONTRIBUTING.md](https://github.com/lift/framework/blob/m You can create a new Lift project using your favorite build system by adding Lift as a dependency: -#### sbt 0.12.1 +#### sbt + +We recommend using the latest sbt version, which is currently 0.13.15, but anything 0.13.6+ will work +with the instructions below. Create or update your `project/plugins.sbt` file with the `xsbt-web-plugin`: - libraryDependencies <+= sbtVersion(v => "com.github.siasia" %% "xsbt-web-plugin" % ("0.12.0-0.2.11.1")) + addSbtPlugin("com.github.siasia" %% "xsbt-web-plugin" % "3.0.2") Then, add the plugin and Lift to your `build.sbt` file: - seq(webSettings :_*) + enablePlugins(JettyPlugin) libraryDependencies ++= { - val liftVersion = "2.5-RC1" + val liftVersion = "3.0.1" Seq( "net.liftweb" %% "lift-webkit" % liftVersion % "compile", - "net.liftmodules" %% "lift-jquery-module" % (liftVersion + "-2.2"), - "org.eclipse.jetty" % "jetty-webapp" % "8.1.7.v20120910" % "container,test", - "org.eclipse.jetty.orbit" % "javax.servlet" % "3.0.0.v201112011016" % "container,test" artifacts Artifact("javax.servlet", "jar", "jar"), - "ch.qos.logback" % "logback-classic" % "1.0.6" + "ch.qos.logback" % "logback-classic" % "1.1.3" ) } @@ -68,17 +68,18 @@ Or, you can add Lift to your `pom.xml` like so: net.liftweb - lift-mapper_${scala.version} - 2.5.1 + lift-webkit_${scala.version} + 3.0.1 -Where `${scala.version}` is `2.9.1` etc. For scala 2.10.x, which is binary compatible, you just use `2.10`, and that will work for `2.10.0` ,`2.10.1` ,`2.10.2` +Where `${scala.version}` is `2.11` or `2.12`. Individual patch releases of the Scala compiler (e.g. 2.12.2) +are binary compatible with everything in their release series, so you only need the first two version parts. You can [learn more on the wiki](http://www.assembla.com/wiki/show/liftweb/Using_Maven). ## Project Organization -The Lift Framework is divided into several Git repositories, which in turn are divided into several components that are published independently. This organization enables you to use just the elements of Lift necessary for your project and no more. +The Lift Framework is divided into several components that are published independently. This organization enables you to use just the elements of Lift necessary for your project and no more. ### This Repository @@ -138,7 +139,7 @@ The Lift wiki is hosted on Assembla and can be found at [http://www.assembla.com ### ScalaDocs -The ScalaDocs for each release of Lift, in additional to the actual JARs, are available on the Liftweb.net site. You can access the source code–based documentation for releases via the site's homepage or by navigating directly to the URL for the specific release. For instance, the Lift 2.5 release can be accessed at [http://liftweb.net/api/25/api/](http://liftweb.net/api/25/api/). +The ScalaDocs for each release of Lift, in additional to the actual JARs, are available on the Liftweb.net site. You can access the source code–based documentation for releases via the site's homepage or by navigating directly to the URL for the specific release. For instance, the Lift 3.0 release can be accessed at [http://liftweb.net/api/25/api/](http://liftweb.net/api/30/api/). ### Cookbook @@ -146,8 +147,7 @@ You can find up-to-date information on the [Lift Cookbook](http://cookbook.liftw ## License -Lift is open source software released under the **Apache 2.0 license**. Generally speaking, you must be a committer with signed committer agreement to submit significant -changes to Lift. We do, however, accept some small changes and bugfixes into Lift from non-committers as outlined above in the Pull Requests section. +Lift is open source software released under the **Apache 2.0 license**. ## Continuous Integration From c619c90969046cc56615370080238c43d7a08bc1 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Mon, 26 Jun 2017 13:06:35 -0400 Subject: [PATCH 1580/1949] Update liftsh to pull sbt 0.13.15. --- liftsh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/liftsh b/liftsh index 6bcb425bd3..8b47803def 100755 --- a/liftsh +++ b/liftsh @@ -2,8 +2,8 @@ # Make sure to change the name of the launcher jar and the source when bumping sbt version # so that the existence test below fails and we download the new jar. -SBT_LAUNCHER_PATH="project/sbt-launch-0.13.8.jar" -SBT_LAUNCHER_SOURCE="https://dl.bintray.com/sbt/native-packages/sbt/0.13.8/sbt-0.13.8.tgz" +SBT_LAUNCHER_PATH="project/sbt-launch-0.13.15.jar" +SBT_LAUNCHER_SOURCE="https://dl.bintray.com/sbt/native-packages/sbt/0.13.15/sbt-0.13.15.tgz" # Download the sbt launcher on-the-fly if it's not already in the repository. if test ! -f $SBT_LAUNCHER_PATH; then From 9d63cad14a8888cf106074dceb0dd2a23d32d890 Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Tue, 27 Jun 2017 20:48:48 +0530 Subject: [PATCH 1581/1949] Change private to private[this] --- .../http/provider/servlet/OfflineRequestSnapshot.scala | 8 ++++---- .../provider/servlet/OfflineRequestSnapshotSpec.scala | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshot.scala b/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshot.scala index 1a0a4bd584..ed29d641b3 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshot.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshot.scala @@ -10,13 +10,13 @@ import net.liftweb.util.Helpers private [servlet] class OfflineRequestSnapshot(req: HTTPRequest, val provider: HTTPProvider) extends HTTPRequest { - private val _cookies = List(req.cookies :_*) + private[this] val _cookies = List(req.cookies :_*) - private val _headers = List(req.headers :_*) + private[this] val _headers = List(req.headers :_*) - private val _params = List(req.params :_*) + private[this] val _params = List(req.params :_*) - private [this] val _serverPort = req.serverPort + private[this] val _serverPort = req.serverPort def cookies: List[HTTPCookie] = _cookies diff --git a/web/webkit/src/test/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshotSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshotSpec.scala index ba6dab6525..52e3dcd64d 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshotSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshotSpec.scala @@ -24,7 +24,7 @@ import org.specs2.mock.Mockito object OfflineRequestSnapshotSpec extends WebSpec with Mockito { - private val X_SSL = "X-SSL" + private[this] val X_SSL = "X-SSL" "OfflineRequestSnapshot" should { "have a 'headers' method that returns the list of headers with a given name" in { @@ -49,7 +49,7 @@ object OfflineRequestSnapshotSpec extends WebSpec with Mockito { } } - private val xSSLHeader = HTTPParam(X_SSL, List("true")) :: Nil + private[this] val xSSLHeader = HTTPParam(X_SSL, List("true")) :: Nil private def getRequestSnapshot(originalPort: Int, headers: List[HTTPParam] = xSSLHeader) = { val mockHttpRequest = mock[HTTPRequest] From 17cc1d8e1a665d988738297ac247ffaab9b8916b Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Tue, 27 Jun 2017 21:07:26 +0530 Subject: [PATCH 1582/1949] Correct the implementation of the 'param' method, and add a test for it --- .../provider/servlet/OfflineRequestSnapshot.scala | 8 ++++++-- .../servlet/OfflineRequestSnapshotSpec.scala | 15 +++++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshot.scala b/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshot.scala index ed29d641b3..c7097fa33b 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshot.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshot.scala @@ -23,7 +23,6 @@ private [servlet] class OfflineRequestSnapshot(req: HTTPRequest, val provider: H val authType: Box[String] = req.authType - // Just as a sample, to be replaced later def headers(name: String): List[String] = { _headers .find(_.name.equalsIgnoreCase(name)) @@ -45,7 +44,12 @@ private [servlet] class OfflineRequestSnapshot(req: HTTPRequest, val provider: H val queryString: Box[String] = req.queryString - def param(name: String): List[String] = _params.filter(_.name == name).map(_.name) + def param(name: String): List[String] = { + _params + .find(_.name == name) + .map(_.values) + .getOrElse(Nil) + } def params: List[HTTPParam] = _params diff --git a/web/webkit/src/test/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshotSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshotSpec.scala index 52e3dcd64d..fd3daba304 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshotSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshotSpec.scala @@ -47,11 +47,22 @@ object OfflineRequestSnapshotSpec extends WebSpec with Mockito { falseSSLHeaderReq.serverPort shouldEqual 90 } } + + "have a 'param' method that returns the list of parameters with a given name (case-sensitive)" in { + val tennisParams = List("Roger Federer", "Raphael Nadal") + val swimmingParams = List("Michael Phelps", "Ian Thorpe") + val params = HTTPParam("tennis", tennisParams) :: HTTPParam("swimming", swimmingParams) :: Nil + val snapshot = getRequestSnapshot(80, params = params) + + snapshot.param("tennis") shouldEqual tennisParams + snapshot.param("Tennis") should beEmpty + snapshot.param("swimming") shouldEqual swimmingParams + } } private[this] val xSSLHeader = HTTPParam(X_SSL, List("true")) :: Nil - private def getRequestSnapshot(originalPort: Int, headers: List[HTTPParam] = xSSLHeader) = { + private[this] def getRequestSnapshot(originalPort: Int, headers: List[HTTPParam] = xSSLHeader, params: List[HTTPParam] = Nil) = { val mockHttpRequest = mock[HTTPRequest] val httpProvider = new HTTPProvider { override protected def context: HTTPContext = null @@ -59,7 +70,7 @@ object OfflineRequestSnapshotSpec extends WebSpec with Mockito { when(mockHttpRequest.headers).thenReturn(headers) when(mockHttpRequest.cookies).thenReturn(Nil) - when(mockHttpRequest.params).thenReturn(Nil) + when(mockHttpRequest.params).thenReturn(params) when(mockHttpRequest.serverPort).thenReturn(originalPort) new OfflineRequestSnapshot(mockHttpRequest, httpProvider) } From 70c4f991fbccecc0b1b12b9bc20b24cb90a6af23 Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Tue, 27 Jun 2017 21:21:42 +0530 Subject: [PATCH 1583/1949] Remove 'sealed' nature of the base field classes. Change deprecation version to 3.2. --- .../net/liftweb/mongodb/record/field/BsonRecordField.scala | 2 +- .../net/liftweb/mongodb/record/field/JObjectField.scala | 2 +- .../liftweb/mongodb/record/field/MongoCaseClassField.scala | 6 +++--- .../net/liftweb/mongodb/record/field/ObjectIdField.scala | 2 +- .../net/liftweb/mongodb/record/field/PatternField.scala | 2 +- .../scala/net/liftweb/mongodb/record/field/UUIDField.scala | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala index 41725ecd1d..3a3dde47ad 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala @@ -31,7 +31,7 @@ import scala.reflect.Manifest import scala.xml._ /** Field that contains an entire record represented as an inline object value. Inspired by JSONSubRecordField */ -sealed abstract class BsonRecordTypedField[OwnerType <: BsonRecord[OwnerType], SubRecordType <: BsonRecord[SubRecordType]] +abstract class BsonRecordTypedField[OwnerType <: BsonRecord[OwnerType], SubRecordType <: BsonRecord[SubRecordType]] (override val owner: OwnerType, valueMeta: BsonMetaRecord[SubRecordType])(implicit subRecordType: Manifest[SubRecordType]) extends Field[SubRecordType, OwnerType] { diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala index 3a0134ed03..ae4a421327 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala @@ -28,7 +28,7 @@ import org.bson.Document import scala.xml.NodeSeq -sealed trait JObjectTypedField[OwnerType <: BsonRecord[OwnerType]] extends TypedField[JObject] +trait JObjectTypedField[OwnerType <: BsonRecord[OwnerType]] extends TypedField[JObject] with Field[JObject, OwnerType] with MongoFieldFlavor[JObject] { override def setFromJValue(jvalue: JValue): Box[JObject] = jvalue match { diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoCaseClassField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoCaseClassField.scala index 5cff812623..b838080100 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoCaseClassField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoCaseClassField.scala @@ -31,7 +31,7 @@ import net.liftweb.http.js.JsExp import org.bson.Document import scala.collection.JavaConverters._ -sealed abstract class CaseClassTypedField[OwnerType <: Record[OwnerType], CaseType](override val owner: OwnerType)(implicit mf: Manifest[CaseType]) +abstract class CaseClassTypedField[OwnerType <: Record[OwnerType], CaseType](override val owner: OwnerType)(implicit mf: Manifest[CaseType]) extends Field[CaseType, OwnerType] with MongoFieldFlavor[CaseType] { // override this for custom formats @@ -91,7 +91,7 @@ class CaseClassField[OwnerType <: Record[OwnerType], CaseType](rec: OwnerType)(i override def defaultValue = null.asInstanceOf[MyType] } -@deprecated("Use the more consistently named 'CaseClassField' instead", "3.1") +@deprecated("Use the more consistently named 'CaseClassField' instead", "3.2") class MongoCaseClassField[OwnerType <: Record[OwnerType], CaseType](rec: OwnerType)(implicit mf: Manifest[CaseType]) extends CaseClassField[OwnerType, CaseType](rec) @@ -171,6 +171,6 @@ class CaseClassListField[OwnerType <: Record[OwnerType], CaseType](override val } } -@deprecated("Please use the more consistently named 'CaseClassListField' instead", "3.1") +@deprecated("Please use the more consistently named 'CaseClassListField' instead", "3.2") class MongoCaseClassListField[OwnerType <: Record[OwnerType], CaseType](owner: OwnerType)(implicit mf: Manifest[CaseType]) extends CaseClassListField[OwnerType, CaseType](owner) \ No newline at end of file diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/ObjectIdField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/ObjectIdField.scala index f1818e19e8..4a86eca1dd 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/ObjectIdField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/ObjectIdField.scala @@ -30,7 +30,7 @@ import net.liftweb.record._ import net.liftweb.util.Helpers._ import org.bson.types.ObjectId -sealed trait ObjectIdTypedField[OwnerType <: BsonRecord[OwnerType]] extends TypedField[ObjectId] with Field[ObjectId, OwnerType] { +trait ObjectIdTypedField[OwnerType <: BsonRecord[OwnerType]] extends TypedField[ObjectId] with Field[ObjectId, OwnerType] { def setFromAny(in: Any): Box[ObjectId] = in match { case oid: ObjectId => setBox(Full(oid)) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/PatternField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/PatternField.scala index 6166a0b4b8..396de18509 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/PatternField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/PatternField.scala @@ -26,7 +26,7 @@ import net.liftweb.util.Helpers.tryo import scala.xml.NodeSeq -sealed abstract class PatternTypedField[OwnerType <: BsonRecord[OwnerType]](override val owner: OwnerType) extends Field[Pattern, OwnerType] { +abstract class PatternTypedField[OwnerType <: BsonRecord[OwnerType]](override val owner: OwnerType) extends Field[Pattern, OwnerType] { override def setFromAny(in: Any): Box[Pattern] = in match { case p: Pattern => setBox(Full(p)) case Some(p: Pattern) => setBox(Full(p)) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/UUIDField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/UUIDField.scala index 978dc32733..2854d9a60e 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/UUIDField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/UUIDField.scala @@ -28,7 +28,7 @@ import net.liftweb.util.Helpers._ import scala.xml.NodeSeq -sealed trait UUIDTypedField[OwnerType <: BsonRecord[OwnerType]] extends TypedField[UUID] with Field[UUID, OwnerType] { +trait UUIDTypedField[OwnerType <: BsonRecord[OwnerType]] extends TypedField[UUID] with Field[UUID, OwnerType] { def setFromAny(in: Any): Box[UUID] = in match { case uid: UUID => setBox(Full(uid)) case Some(uid: UUID) => setBox(Full(uid)) From 6a0d723bf4333241fd40418b623c2bada01c6a0f Mon Sep 17 00:00:00 2001 From: Joe Barnes Date: Thu, 29 Jun 2017 08:11:40 -0500 Subject: [PATCH 1584/1949] Bumping logback version to 1.2.3 for a security vulnerability fix --- project/Dependencies.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 80084971eb..538698c8ff 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -57,7 +57,7 @@ object Dependencies { // Provided scope: // Scope provided by container, available only in compile and test classpath, non-transitive by default. - lazy val logback = "ch.qos.logback" % "logback-classic" % "1.1.5" % "provided" + lazy val logback = "ch.qos.logback" % "logback-classic" % "1.2.3" % "provided" lazy val log4j = "log4j" % "log4j" % "1.2.17" % "provided" lazy val slf4j_log4j12 = "org.slf4j" % "slf4j-log4j12" % slf4jVersion % "provided" lazy val persistence_api = "javax.persistence" % "persistence-api" % "1.0.2" % "provided" From da9ab638d1b27a1e3dbb03e03dc292bc378ac3ac Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Thu, 29 Jun 2017 13:27:11 -0400 Subject: [PATCH 1585/1949] Update for 3.1.0, since we'll merge when it goes final --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3c4b930e11..72f54db68c 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ Then, add the plugin and Lift to your `build.sbt` file: enablePlugins(JettyPlugin) libraryDependencies ++= { - val liftVersion = "3.0.1" + val liftVersion = "3.1.0" Seq( "net.liftweb" %% "lift-webkit" % liftVersion % "compile", "ch.qos.logback" % "logback-classic" % "1.1.3" @@ -69,7 +69,7 @@ Or, you can add Lift to your `pom.xml` like so: net.liftweb lift-webkit_${scala.version} - 3.0.1 + 3.1.0 Where `${scala.version}` is `2.11` or `2.12`. Individual patch releases of the Scala compiler (e.g. 2.12.2) @@ -139,7 +139,7 @@ The Lift wiki is hosted on Assembla and can be found at [http://www.assembla.com ### ScalaDocs -The ScalaDocs for each release of Lift, in additional to the actual JARs, are available on the Liftweb.net site. You can access the source code–based documentation for releases via the site's homepage or by navigating directly to the URL for the specific release. For instance, the Lift 3.0 release can be accessed at [http://liftweb.net/api/25/api/](http://liftweb.net/api/30/api/). +The ScalaDocs for each release of Lift, in additional to the actual JARs, are available on the Liftweb.net site. You can access the source code–based documentation for releases via the site's homepage or by navigating directly to the URL for the specific release. For instance, the Lift 3.0 release can be accessed at [http://liftweb.net/api/31/api/](http://liftweb.net/api/31/api/). ### Cookbook From 5a274d0a8d979ddd68589e073b1d80d22f6ed2b2 Mon Sep 17 00:00:00 2001 From: ricsirigu Date: Sun, 2 Jul 2017 09:32:41 +0200 Subject: [PATCH 1586/1949] Added support for http PATCH method --- .../src/main/scala/net/liftweb/http/Req.scala | 2 + .../scala/net/liftweb/http/RequestType.scala | 7 +++ .../net/liftweb/http/rest/RestHelper.scala | 51 +++++++++++++++++++ 3 files changed, 60 insertions(+) diff --git a/web/webkit/src/main/scala/net/liftweb/http/Req.scala b/web/webkit/src/main/scala/net/liftweb/http/Req.scala index 75df13d62d..639f133c77 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Req.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Req.scala @@ -1187,6 +1187,8 @@ class Req(val path: ParsePath, val post_? = requestType.post_? + val patch_? = requestType.patch_? + val get_? = requestType.get_? val put_? = requestType.put_? diff --git a/web/webkit/src/main/scala/net/liftweb/http/RequestType.scala b/web/webkit/src/main/scala/net/liftweb/http/RequestType.scala index 5f76ed74b0..0e230e9d85 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/RequestType.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/RequestType.scala @@ -28,6 +28,8 @@ abstract class RequestType extends Serializable{ def put_? : Boolean = false + def patch_? : Boolean = false + def delete_? : Boolean = false def options_? : Boolean = false @@ -51,6 +53,10 @@ case object PutRequest extends RequestType { override def put_? = true val method = "PUT" } +case object PatchRequest extends RequestType{ + override def patch_? : Boolean = true + val method: String = "PATCH" +} case object DeleteRequest extends RequestType { override def delete_? = true val method = "DELETE" @@ -68,6 +74,7 @@ object RequestType { case "POST" => PostRequest case "HEAD" => HeadRequest case "PUT" => PutRequest + case "PATCH" => PatchRequest case "DELETE" => DeleteRequest case "OPTIONS" => OptionsRequest case meth => UnknownRequest(meth) diff --git a/web/webkit/src/main/scala/net/liftweb/http/rest/RestHelper.scala b/web/webkit/src/main/scala/net/liftweb/http/rest/RestHelper.scala index 61201f9bb8..a0c5a04a83 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/rest/RestHelper.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/rest/RestHelper.scala @@ -232,6 +232,43 @@ trait RestHelper extends LiftRules.DispatchPF { def body(r: Req): Box[T] } + + /** + * A trait that defines the TestPatch extractor. Is + * the request a PATCH, has JSON or XML data in the post body + * and something that expects JSON or XML in the response. + * Subclass this trait to change the behavior + */ + protected trait TestPatch[T] { + /** + * Test to see if the request is a PATCH, has JSON data in the + * body and expecting JSON in the response. + * The path, JSON Data and the Req instance are extracted. + */ + def unapply(r: Req): Option[(List[String], (T, Req))] = + if (r.patch_? && testResponse_?(r)) + body(r).toOption.map(t => (r.path.partPath -> (t -> r))) + else None + + + def testResponse_?(r: Req): Boolean + + def body(r: Req): Box[T] + } + + /** + * The stable identifier for JsonPatch. You can use it + * as an extractor. + */ + protected lazy val JsonPatch = new TestPatch[JValue] with JsonTest with JsonBody + + /** + * The stable identifier for XmlPatch. You can use it + * as an extractor. + */ + protected lazy val XmlPatch = new TestPatch[Elem] with XmlTest with XmlBody + + /** * a trait that extracts the JSON body from a request It is * composed with a TestXXX to get the correct thing for the extractor @@ -290,6 +327,20 @@ trait RestHelper extends LiftRules.DispatchPF { } + /** + * An extractor that tests the request to see if it's a PATCH and + * if it is, the path and the request are extracted. It can + * be used as:
              + *
              case "api" :: id :: _ Patch req => ...

              + * or
              + *
              case Patch("api" :: id :: _, req) => ...

              + */ + protected object Patch { + def unapply(r: Req): Option[(List[String], Req)] = + if (r.patch_?) Some(r.path.partPath -> r) else None + + } + /** * An extractor that tests the request to see if it's a DELETE and * if it is, the path and the request are extracted. It can From 92ada6795fd98f01ddf0d234ee37d2a08f5631d0 Mon Sep 17 00:00:00 2001 From: ricsirigu Date: Sun, 2 Jul 2017 09:45:54 +0200 Subject: [PATCH 1587/1949] Updated contributors --- contributors.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/contributors.md b/contributors.md index 714048de1d..221c5359ea 100644 --- a/contributors.md +++ b/contributors.md @@ -257,3 +257,9 @@ Paweł Mruk ### Email: ### mroocoo at gmail dot com + +### Name: ### +Riccardo Sirigu + +### Email: ### +me@riccardosirigu.com \ No newline at end of file From 3282b54674833a1134214f825beeee906c98aaa4 Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sun, 2 Jul 2017 16:06:56 +0530 Subject: [PATCH 1588/1949] Add spaces around an operator. Remove redundant braces --- .../liftweb/mongodb/record/field/MongoCaseClassField.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoCaseClassField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoCaseClassField.scala index b838080100..774c59a65b 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoCaseClassField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoCaseClassField.scala @@ -47,7 +47,7 @@ abstract class CaseClassTypedField[OwnerType <: Record[OwnerType], CaseType](ove def asJValue: JValue = valueBox.map(Extraction.decompose) openOr (JNothing: JValue) def setFromJValue(jvalue: JValue): Box[CaseType] = jvalue match { - case JNothing|JNull => setBox(Empty) + case JNothing | JNull => setBox(Empty) case s => setBox(Helpers.tryo[CaseType]{ s.extract[CaseType] }) } @@ -65,8 +65,8 @@ abstract class CaseClassTypedField[OwnerType <: Record[OwnerType], CaseType](ove setFromJValue(jvalue) } - override def setFromString(in: String): Box[CaseType] = { - Helpers.tryo{ JsonParser.parse(in).extract[CaseType] } + override def setFromString(in: String): Box[CaseType] = Helpers.tryo { + JsonParser.parse(in).extract[CaseType] } def setFromAny(in: Any): Box[CaseType] = in match { From d8f1114a0d9c80d7e1e1e96cd5345e9629b584cb Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sun, 2 Jul 2017 16:38:58 +0530 Subject: [PATCH 1589/1949] Replace deprecated MongoCaseClassField with CaseClassField --- .../src/test/scala/net/liftweb/mongodb/record/Fixtures.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala index 8d38e1940e..d7d5b4b048 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala @@ -228,7 +228,7 @@ class MongoFieldTypeTestRecord private () extends MongoRecord[MongoFieldTypeTest object mandatoryUUIDField extends UUIDField(this) object legacyOptionalUUIDField extends UUIDField(this) { override def optional_? = true } - object mandatoryMongoCaseClassField extends MongoCaseClassField[MongoFieldTypeTestRecord, MongoCaseClassTestObject](this) { + object mandatoryMongoCaseClassField extends CaseClassField[MongoFieldTypeTestRecord, MongoCaseClassTestObject](this) { override def formats = owner.meta.formats } } From 64060ca87c87296a620c9b8a088062694bc5bc64 Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sun, 2 Jul 2017 16:39:25 +0530 Subject: [PATCH 1590/1949] Handling nulls when the field is mandatory --- .../record/field/MongoCaseClassField.scala | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoCaseClassField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoCaseClassField.scala index 774c59a65b..371209d74c 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoCaseClassField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoCaseClassField.scala @@ -47,12 +47,14 @@ abstract class CaseClassTypedField[OwnerType <: Record[OwnerType], CaseType](ove def asJValue: JValue = valueBox.map(Extraction.decompose) openOr (JNothing: JValue) def setFromJValue(jvalue: JValue): Box[CaseType] = jvalue match { - case JNothing | JNull => setBox(Empty) - case s => setBox(Helpers.tryo[CaseType]{ s.extract[CaseType] }) + case JNothing | JNull if optional_? => setBox(Empty) + case JNothing | JNull => setBox(Full(null.asInstanceOf[CaseType])) + case s => setBox(Helpers.tryo[CaseType] { s.extract[CaseType] }) } - def asDBObject: DBObject = { - JObjectParser.parse(asJValue.asInstanceOf[JObject]) + def asDBObject: DBObject = asJValue match { + case JNothing | JNull => null + case other => JObjectParser.parse(other.asInstanceOf[JObject]) } def setFromDocument(doc: Document): Box[CaseType] = { @@ -83,12 +85,17 @@ abstract class CaseClassTypedField[OwnerType <: Record[OwnerType], CaseType](ove class CaseClassField[OwnerType <: Record[OwnerType], CaseType](rec: OwnerType)(implicit mf: Manifest[CaseType]) extends CaseClassTypedField[OwnerType, CaseType](rec) with MandatoryTypedField[CaseType] { + def this(owner: OwnerType, value: CaseType)(implicit mf: Manifest[CaseType]) = { this(owner) setBox(Full(value)) } override def defaultValue = null.asInstanceOf[MyType] + + // Prevent null from being stored accidentally. Can't use anything except null as a default + override def defaultValueBox: Box[MyType] = + if (optional_?) Empty else Box !! defaultValue } @deprecated("Use the more consistently named 'CaseClassField' instead", "3.2") From 56ddfbd336a014d4b784ea6f9c9ef2c3c33981c3 Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sun, 2 Jul 2017 16:57:19 +0530 Subject: [PATCH 1591/1949] Rollback null handling related change --- .../liftweb/mongodb/record/field/MongoCaseClassField.scala | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoCaseClassField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoCaseClassField.scala index 371209d74c..638e9fe18b 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoCaseClassField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoCaseClassField.scala @@ -47,8 +47,7 @@ abstract class CaseClassTypedField[OwnerType <: Record[OwnerType], CaseType](ove def asJValue: JValue = valueBox.map(Extraction.decompose) openOr (JNothing: JValue) def setFromJValue(jvalue: JValue): Box[CaseType] = jvalue match { - case JNothing | JNull if optional_? => setBox(Empty) - case JNothing | JNull => setBox(Full(null.asInstanceOf[CaseType])) + case JNothing | JNull => setBox(Empty) case s => setBox(Helpers.tryo[CaseType] { s.extract[CaseType] }) } @@ -92,10 +91,6 @@ class CaseClassField[OwnerType <: Record[OwnerType], CaseType](rec: OwnerType)(i } override def defaultValue = null.asInstanceOf[MyType] - - // Prevent null from being stored accidentally. Can't use anything except null as a default - override def defaultValueBox: Box[MyType] = - if (optional_?) Empty else Box !! defaultValue } @deprecated("Use the more consistently named 'CaseClassField' instead", "3.2") From fac2f5a42dd59d9dc5328cca506052661242db61 Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sun, 2 Jul 2017 17:41:55 +0530 Subject: [PATCH 1592/1949] Change deprecated MongoCaseClassListField to CaseClassListField. Rename corresponding val from mongoCaseClassListField to caseClassListField --- .../test/scala/net/liftweb/mongodb/record/Fixtures.scala | 2 +- .../scala/net/liftweb/mongodb/record/MongoFieldSpec.scala | 4 ++-- .../net/liftweb/mongodb/record/MongoRecordSpec.scala | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala index d7d5b4b048..c3dba52e0b 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala @@ -268,7 +268,7 @@ class ListTestRecord private () extends MongoRecord[ListTestRecord] with UUIDPk[ object mandatoryMongoRefListField extends ObjectIdRefListField(this, FieldTypeTestRecord) object mandatoryIntListField extends MongoListField[ListTestRecord, Int](this) object mandatoryMongoJsonObjectListField extends MongoJsonObjectListField(this, TypeTestJsonObject) - object mongoCaseClassListField extends MongoCaseClassListField[ListTestRecord, MongoCaseClassTestObject](this) { + object caseClassListField extends CaseClassListField[ListTestRecord, MongoCaseClassTestObject](this) { override def formats = owner.meta.formats } } diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala index cc4faa91b9..9c63a5127f 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala @@ -507,8 +507,8 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundEach { "setFromAny a List" in { val rec = ListTestRecord.createRecord val lst = List(MongoCaseClassTestObject(1,"str1", MyTestEnum.THREE)) - rec.mongoCaseClassListField.setFromAny(lst) - rec.mongoCaseClassListField.value must_== lst + rec.caseClassListField.setFromAny(lst) + rec.caseClassListField.value must_== lst } } diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala index 2aeccf1bdc..b8e459f06c 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala @@ -235,7 +235,7 @@ class MongoRecordSpec extends Specification with MongoTestKit { .mandatoryStringListField(List("abc", "def", "ghi")) .mandatoryIntListField(List(4, 5, 6)) .mandatoryMongoJsonObjectListField(List(TypeTestJsonObject(1, "jsonobj1", Map("x" -> "1")), TypeTestJsonObject(2, "jsonobj2", Map("x" -> "2")))) - .mongoCaseClassListField(List(MongoCaseClassTestObject(1,"str",MyTestEnum.TWO))) + .caseClassListField(List(MongoCaseClassTestObject(1,"str",MyTestEnum.TWO))) .mandatoryMongoRefListField(Nil) val ltrJson = @@ -246,7 +246,7 @@ class MongoRecordSpec extends Specification with MongoTestKit { (("intField" -> 1) ~ ("stringField" -> "jsonobj1") ~ ("mapField" -> ("x" -> "1"))), (("intField" -> 2) ~ ("stringField" -> "jsonobj2") ~ ("mapField" -> ("x" -> "2"))) )) ~ - ("mongoCaseClassListField" -> List( + ("caseClassListField" -> List( ("intField" -> 1) ~ ("stringField" -> "str") ~ ("enum" -> 1) )) ~ ("mandatoryMongoRefListField" -> JArray(Nil)) @@ -849,8 +849,8 @@ class MongoRecordSpec extends Specification with MongoTestKit { ltr.mandatoryMongoJsonObjectListField(List(TypeTestJsonObject(1, "jsonobj1", Map("x" -> "1")), TypeTestJsonObject(2, "jsonobj2", Map("x" -> "2")))) ltr.mandatoryMongoJsonObjectListField.dirty_? must_== true - ltr.mongoCaseClassListField(List(MongoCaseClassTestObject(1,"str",MyTestEnum.TWO))) - ltr.mongoCaseClassListField.dirty_? must_== true + ltr.caseClassListField(List(MongoCaseClassTestObject(1,"str",MyTestEnum.TWO))) + ltr.caseClassListField.dirty_? must_== true ltr.dirty_? must_== true ltr.update From 4f85b8eca25d1a9536bce6596c29ef496d5ba659 Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sun, 2 Jul 2017 17:48:59 +0530 Subject: [PATCH 1593/1949] Move MongoJsonObjectListField to the file containing JsonObjectField, which is consistent with how other such fields are organized --- .../record/field/JsonObjectField.scala | 37 ++++++++++++-- .../mongodb/record/field/MongoListField.scala | 51 ++++--------------- 2 files changed, 42 insertions(+), 46 deletions(-) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JsonObjectField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JsonObjectField.scala index edd15000fe..204b561642 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JsonObjectField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JsonObjectField.scala @@ -16,13 +16,14 @@ package mongodb package record package field -import scala.xml.{NodeSeq, Text} +import scala.xml.NodeSeq import net.liftweb.common.{Box, Empty, Failure, Full} -import net.liftweb.http.js.JE.{JsNull, Str} import net.liftweb.json._ -import net.liftweb.record.{Field, FieldHelpers, MandatoryTypedField, Record} +import net.liftweb.record.{Field, FieldHelpers, MandatoryTypedField} import net.liftweb.util.Helpers.tryo -import com.mongodb.DBObject +import com.mongodb.{BasicDBList, DBObject} + +import scala.collection.JavaConverters._ abstract class JsonObjectField[OwnerType <: BsonRecord[OwnerType], JObjectType <: JsonObject[JObjectType]] (rec: OwnerType, valueMeta: JsonObjectMeta[JObjectType]) @@ -82,3 +83,31 @@ abstract class JsonObjectField[OwnerType <: BsonRecord[OwnerType], JObjectType < } +/* +* List of JsonObject case classes +*/ +class MongoJsonObjectListField[OwnerType <: BsonRecord[OwnerType], JObjectType <: JsonObject[JObjectType]] +(rec: OwnerType, valueMeta: JsonObjectMeta[JObjectType])(implicit mf: Manifest[JObjectType]) + extends MongoListField[OwnerType, JObjectType](rec: OwnerType) { + + override def asDBObject: DBObject = { + val dbl = new BasicDBList + value.foreach { v => dbl.add(JObjectParser.parse(v.asJObject()(owner.meta.formats))(owner.meta.formats)) } + dbl + } + + override def setFromDBObject(dbo: DBObject): Box[List[JObjectType]] = + setBox(Full(dbo.keySet.asScala.toList.map(k => { + valueMeta.create(JObjectParser.serialize(dbo.get(k.toString))(owner.meta.formats).asInstanceOf[JObject])(owner.meta.formats) + }))) + + override def asJValue: JValue = JArray(value.map(_.asJObject()(owner.meta.formats))) + + override def setFromJValue(jvalue: JValue) = jvalue match { + case JNothing|JNull if optional_? => setBox(Empty) + case JArray(arr) => setBox(Full(arr.map( jv => { + valueMeta.create(jv.asInstanceOf[JObject])(owner.meta.formats) + }))) + case other => setBox(FieldHelpers.expectedA("JArray", other)) + } +} diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoListField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoListField.scala index b49086a865..e6af74a8e4 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoListField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoListField.scala @@ -19,20 +19,16 @@ package mongodb package record package field -import scala.collection.JavaConversions._ -import scala.xml.NodeSeq - -import common.{Box, Empty, Failure, Full} -import http.SHtml -import http.js.JE.{JsNull, JsRaw} -import json._ -import net.liftweb.record.{Field, FieldHelpers, MandatoryTypedField, Record} -import util.Helpers._ - import com.mongodb._ +import net.liftweb.common.{Box, Empty, Failure, Full} +import net.liftweb.http.SHtml +import net.liftweb.json._ +import net.liftweb.record.{Field, FieldHelpers, MandatoryTypedField} +import net.liftweb.util.Helpers._ import org.bson.Document -import org.bson.types.ObjectId -import org.joda.time.DateTime + +import scala.collection.JavaConversions._ +import scala.xml.NodeSeq /** * List field. @@ -165,33 +161,4 @@ class MongoListField[OwnerType <: BsonRecord[OwnerType], ListType: Manifest](rec throw new RuntimeException("Warning, setting Document as field with no conversion, probably not something you want to do") } -} - -/* -* List of JsonObject case classes -*/ -class MongoJsonObjectListField[OwnerType <: BsonRecord[OwnerType], JObjectType <: JsonObject[JObjectType]] - (rec: OwnerType, valueMeta: JsonObjectMeta[JObjectType])(implicit mf: Manifest[JObjectType]) - extends MongoListField[OwnerType, JObjectType](rec: OwnerType) { - - override def asDBObject: DBObject = { - val dbl = new BasicDBList - value.foreach { v => dbl.add(JObjectParser.parse(v.asJObject()(owner.meta.formats))(owner.meta.formats)) } - dbl - } - - override def setFromDBObject(dbo: DBObject): Box[List[JObjectType]] = - setBox(Full(dbo.keySet.toList.map(k => { - valueMeta.create(JObjectParser.serialize(dbo.get(k.toString))(owner.meta.formats).asInstanceOf[JObject])(owner.meta.formats) - }))) - - override def asJValue: JValue = JArray(value.map(_.asJObject()(owner.meta.formats))) - - override def setFromJValue(jvalue: JValue) = jvalue match { - case JNothing|JNull if optional_? => setBox(Empty) - case JArray(arr) => setBox(Full(arr.map( jv => { - valueMeta.create(jv.asInstanceOf[JObject])(owner.meta.formats) - }))) - case other => setBox(FieldHelpers.expectedA("JArray", other)) - } -} +} \ No newline at end of file From fdf872487de15aa5d161bcd2c033a6c20f943a44 Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sun, 2 Jul 2017 17:52:35 +0530 Subject: [PATCH 1594/1949] A bit of formatting. Replace deprecated JavaConversions with JavaConverters --- .../mongodb/record/field/JsonObjectField.scala | 12 ++++++------ .../mongodb/record/field/MongoListField.scala | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JsonObjectField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JsonObjectField.scala index 204b561642..9230cf5c06 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JsonObjectField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JsonObjectField.scala @@ -97,17 +97,17 @@ class MongoJsonObjectListField[OwnerType <: BsonRecord[OwnerType], JObjectType < } override def setFromDBObject(dbo: DBObject): Box[List[JObjectType]] = - setBox(Full(dbo.keySet.asScala.toList.map(k => { - valueMeta.create(JObjectParser.serialize(dbo.get(k.toString))(owner.meta.formats).asInstanceOf[JObject])(owner.meta.formats) - }))) + setBox(Full(dbo.keySet.asScala.toList.map { k => + valueMeta.create(JObjectParser.serialize(dbo.get(k))(owner.meta.formats).asInstanceOf[JObject])(owner.meta.formats) + })) override def asJValue: JValue = JArray(value.map(_.asJObject()(owner.meta.formats))) override def setFromJValue(jvalue: JValue) = jvalue match { - case JNothing|JNull if optional_? => setBox(Empty) - case JArray(arr) => setBox(Full(arr.map( jv => { + case JNothing | JNull if optional_? => setBox(Empty) + case JArray(arr) => setBox(Full(arr.map { jv => valueMeta.create(jv.asInstanceOf[JObject])(owner.meta.formats) - }))) + })) case other => setBox(FieldHelpers.expectedA("JArray", other)) } } diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoListField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoListField.scala index e6af74a8e4..c2cc4320be 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoListField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoListField.scala @@ -27,7 +27,7 @@ import net.liftweb.record.{Field, FieldHelpers, MandatoryTypedField} import net.liftweb.util.Helpers._ import org.bson.Document -import scala.collection.JavaConversions._ +import scala.collection.JavaConverters._ import scala.xml.NodeSeq /** @@ -74,7 +74,7 @@ class MongoListField[OwnerType <: BsonRecord[OwnerType], ListType: Manifest](rec if(elem.isInstanceOf[Document]) { setFromDocumentList(jlist.asInstanceOf[java.util.List[Document]]) } else { - setBox(Full(jlist.toList.asInstanceOf[MyType])) + setBox(Full(jlist.asScala.toList.asInstanceOf[MyType])) } } else { setBox(Full(Nil)) @@ -155,7 +155,7 @@ class MongoListField[OwnerType <: BsonRecord[OwnerType], ListType: Manifest](rec // set this field's value using a DBObject returned from Mongo. def setFromDBObject(dbo: DBObject): Box[MyType] = - setBox(Full(dbo.asInstanceOf[BasicDBList].toList.asInstanceOf[MyType])) + setBox(Full(dbo.asInstanceOf[BasicDBList].asScala.toList.asInstanceOf[MyType])) def setFromDocumentList(list: java.util.List[Document]): Box[MyType] = { throw new RuntimeException("Warning, setting Document as field with no conversion, probably not something you want to do") From 161960e8a0e89ae8b1eb28d2d7bb34a1ee70b8ac Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sun, 2 Jul 2017 18:32:23 +0530 Subject: [PATCH 1595/1949] Rename MongoJsonObjectListField to JsonObjectListField This allows more consistent names. Also, rename the vals of this type accordingly where appropriate. --- .../liftweb/mongodb/record/field/JsonObjectField.scala | 6 +++++- .../liftweb/mongodb/record/CustomSerializersSpec.scala | 8 ++++---- .../test/scala/net/liftweb/mongodb/record/Fixtures.scala | 6 +++--- .../scala/net/liftweb/mongodb/record/MongoFieldSpec.scala | 4 ++-- .../liftweb/mongodb/record/MongoRecordExamplesSpec.scala | 7 ++++--- .../net/liftweb/mongodb/record/MongoRecordSpec.scala | 8 ++++---- 6 files changed, 22 insertions(+), 17 deletions(-) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JsonObjectField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JsonObjectField.scala index 9230cf5c06..b7f77cfefe 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JsonObjectField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JsonObjectField.scala @@ -86,7 +86,7 @@ abstract class JsonObjectField[OwnerType <: BsonRecord[OwnerType], JObjectType < /* * List of JsonObject case classes */ -class MongoJsonObjectListField[OwnerType <: BsonRecord[OwnerType], JObjectType <: JsonObject[JObjectType]] +class JsonObjectListField[OwnerType <: BsonRecord[OwnerType], JObjectType <: JsonObject[JObjectType]] (rec: OwnerType, valueMeta: JsonObjectMeta[JObjectType])(implicit mf: Manifest[JObjectType]) extends MongoListField[OwnerType, JObjectType](rec: OwnerType) { @@ -111,3 +111,7 @@ class MongoJsonObjectListField[OwnerType <: BsonRecord[OwnerType], JObjectType < case other => setBox(FieldHelpers.expectedA("JArray", other)) } } + +@deprecated("Use the more consistently named 'JsonObjectListField' instead", "3.2") +class MongoJsonObjectListField[OwnerType <: BsonRecord[OwnerType], JObjectType <: JsonObject[JObjectType]] +(rec: OwnerType, valueMeta: JsonObjectMeta[JObjectType])(implicit mf: Manifest[JObjectType]) extends JsonObjectListField(rec, valueMeta) \ No newline at end of file diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/CustomSerializersSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/CustomSerializersSpec.scala index d715d2097e..bb69fb439f 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/CustomSerializersSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/CustomSerializersSpec.scala @@ -48,7 +48,7 @@ object Child extends JsonObjectMeta[Child] class Person extends MongoRecord[Person] with ObjectIdPk[Person] { def meta = Person - object children extends MongoJsonObjectListField(this, Child) + object children extends JsonObjectListField(this, Child) object firstBorn extends JsonObjectField(this, Child) { def defaultValue = Child("", now) @@ -64,7 +64,7 @@ object Person extends Person with MongoMetaRecord[Person] class Person2 extends MongoRecord[Person2] with ObjectIdPk[Person2] { def meta = Person2 - object children extends MongoJsonObjectListField(this, Child) + object children extends JsonObjectListField(this, Child) object firstBorn extends JsonObjectField(this, Child) { def defaultValue = Child("", now) @@ -97,7 +97,7 @@ object Team extends JsonObjectMeta[Team] class League extends MongoRecord[League] with ObjectIdPk[League] { def meta = League - object teams extends MongoJsonObjectListField(this, Team) + object teams extends JsonObjectListField(this, Team) object champion extends JsonObjectField(this, Team) { def defaultValue = Team("", "", "") @@ -119,7 +119,7 @@ object Team2 extends JsonObjectMeta[Team2] class League2 extends MongoRecord[League2] with ObjectIdPk[League2] { def meta = League2 - object teams extends MongoJsonObjectListField(this, Team2) + object teams extends JsonObjectListField(this, Team2) object champion extends JsonObjectField(this, Team2) { def defaultValue = Team2(ObjectId.get, "", ObjectId.get) diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala index c3dba52e0b..a665413559 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala @@ -267,7 +267,7 @@ class ListTestRecord private () extends MongoRecord[ListTestRecord] with UUIDPk[ object mandatoryStringListField extends MongoListField[ListTestRecord, String](this) object mandatoryMongoRefListField extends ObjectIdRefListField(this, FieldTypeTestRecord) object mandatoryIntListField extends MongoListField[ListTestRecord, Int](this) - object mandatoryMongoJsonObjectListField extends MongoJsonObjectListField(this, TypeTestJsonObject) + object mandatoryJsonObjectListField extends JsonObjectListField(this, TypeTestJsonObject) object caseClassListField extends CaseClassListField[ListTestRecord, MongoCaseClassTestObject](this) { override def formats = owner.meta.formats } @@ -387,7 +387,7 @@ class NullTestRecord private () extends MongoRecord[NullTestRecord] with IntPk[N object jsonobj extends JsonObjectField[NullTestRecord, JsonObj](this, JsonObj) { def defaultValue = JsonObj("1", null) } - object jsonobjlist extends MongoJsonObjectListField[NullTestRecord, JsonObj](this, JsonObj) + object jsonobjlist extends JsonObjectListField[NullTestRecord, JsonObj](this, JsonObj) } object NullTestRecord extends NullTestRecord with MongoMetaRecord[NullTestRecord] @@ -404,7 +404,7 @@ class BoxTestRecord private () extends MongoRecord[BoxTestRecord] with LongPk[Bo object jsonobj extends JsonObjectField[BoxTestRecord, BoxTestJsonObj](this, BoxTestJsonObj) { def defaultValue = BoxTestJsonObj("0", Empty, Full("Full String"), Failure("Failure")) } - object jsonobjlist extends MongoJsonObjectListField[BoxTestRecord, BoxTestJsonObj](this, BoxTestJsonObj) + object jsonobjlist extends JsonObjectListField[BoxTestRecord, BoxTestJsonObj](this, BoxTestJsonObj) } object BoxTestRecord extends BoxTestRecord with MongoMetaRecord[BoxTestRecord] { diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala index 9c63a5127f..2a7e57b2d8 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala @@ -490,10 +490,10 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundEach { ("intField" -> 1) ~ ("stringField" -> "jsonobj1") ~ ("mapField" -> (("x" -> "1"))), ("intField" -> 2) ~ ("stringField" -> "jsonobj2") ~ ("mapField" -> (("x" -> "2"))) ) - passBasicTests(lst, lst2, rec.mandatoryMongoJsonObjectListField, Empty) + passBasicTests(lst, lst2, rec.mandatoryJsonObjectListField, Empty) passConversionTests( lst, - rec.mandatoryMongoJsonObjectListField, + rec.mandatoryJsonObjectListField, new JsExp { def toJsCmd = compactRender(json) }, diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordExamplesSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordExamplesSpec.scala index 1869cccf94..d44f7d1761 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordExamplesSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordExamplesSpec.scala @@ -24,7 +24,6 @@ import java.util.regex.Pattern import net.liftweb.common.{Box, Empty, Failure, Full} import net.liftweb.json.DefaultFormats import net.liftweb.json.JsonDSL._ -import net.liftweb.json.JsonAST.JObject import net.liftweb.record.field._ import net.liftweb.util.TimeHelpers._ import net.liftweb.mongodb.record.field._ @@ -113,7 +112,7 @@ package mongotestrecords { object binarylist extends MongoListField[ListDoc, Array[Byte]](this) // specialized list types - object jsonobjlist extends MongoJsonObjectListField(this, JsonDoc) + object jsonobjlist extends JsonObjectListField(this, JsonDoc) // these require custom setFromDBObject methods object maplist extends MongoListField[ListDoc, Map[String, String]](this) { @@ -202,7 +201,7 @@ class MongoRecordExamplesSpec extends Specification with MongoTestKit { checkMongoIsRunning - S.initIfUninitted(session){ + S.initIfUninitted(session) { val pwd = "test" val cal = Calendar.getInstance @@ -231,6 +230,8 @@ class MongoRecordExamplesSpec extends Specification with MongoTestKit { fromDb.isDefined must_== true + + for (t <- fromDb) { t.id.value must_== tr.id.value t.booleanfield.value must_== tr.booleanfield.value diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala index b8e459f06c..ccba4a0d16 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala @@ -234,7 +234,7 @@ class MongoRecordSpec extends Specification with MongoTestKit { val ltr = ListTestRecord.createRecord .mandatoryStringListField(List("abc", "def", "ghi")) .mandatoryIntListField(List(4, 5, 6)) - .mandatoryMongoJsonObjectListField(List(TypeTestJsonObject(1, "jsonobj1", Map("x" -> "1")), TypeTestJsonObject(2, "jsonobj2", Map("x" -> "2")))) + .mandatoryJsonObjectListField(List(TypeTestJsonObject(1, "jsonobj1", Map("x" -> "1")), TypeTestJsonObject(2, "jsonobj2", Map("x" -> "2")))) .caseClassListField(List(MongoCaseClassTestObject(1,"str",MyTestEnum.TWO))) .mandatoryMongoRefListField(Nil) @@ -242,7 +242,7 @@ class MongoRecordSpec extends Specification with MongoTestKit { ("_id" -> ("$uuid" -> ltr.id.toString)) ~ ("mandatoryStringListField" -> List("abc", "def", "ghi")) ~ ("mandatoryIntListField" -> List(4, 5, 6)) ~ - ("mandatoryMongoJsonObjectListField" -> List( + ("mandatoryJsonObjectListField" -> List( (("intField" -> 1) ~ ("stringField" -> "jsonobj1") ~ ("mapField" -> ("x" -> "1"))), (("intField" -> 2) ~ ("stringField" -> "jsonobj2") ~ ("mapField" -> ("x" -> "2"))) )) ~ @@ -846,8 +846,8 @@ class MongoRecordSpec extends Specification with MongoTestKit { ltr.mandatoryIntListField(List(4, 5, 6)) ltr.mandatoryIntListField.dirty_? must_== true - ltr.mandatoryMongoJsonObjectListField(List(TypeTestJsonObject(1, "jsonobj1", Map("x" -> "1")), TypeTestJsonObject(2, "jsonobj2", Map("x" -> "2")))) - ltr.mandatoryMongoJsonObjectListField.dirty_? must_== true + ltr.mandatoryJsonObjectListField(List(TypeTestJsonObject(1, "jsonobj1", Map("x" -> "1")), TypeTestJsonObject(2, "jsonobj2", Map("x" -> "2")))) + ltr.mandatoryJsonObjectListField.dirty_? must_== true ltr.caseClassListField(List(MongoCaseClassTestObject(1,"str",MyTestEnum.TWO))) ltr.caseClassListField.dirty_? must_== true From faa3af6f79ccb32643467164d156c3281ee362cb Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sun, 2 Jul 2017 18:33:26 +0530 Subject: [PATCH 1596/1949] Change private to private[this]. Small formatting change. --- .../net/liftweb/mongodb/record/field/MongoRefField.scala | 6 +++--- .../net/liftweb/mongodb/record/field/ObjectIdField.scala | 4 ++-- .../scala/net/liftweb/mongodb/record/field/UUIDField.scala | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoRefField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoRefField.scala index 1c6f6fc715..abf9f5d395 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoRefField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoRefField.scala @@ -65,8 +65,8 @@ trait MongoRefField[RefType <: MongoRecord[RefType], MyType] extends TypedField[ _calcedObj = true } - private var _obj: Box[RefType] = Empty - private var _calcedObj = false + private[this] var _obj: Box[RefType] = Empty + private[this] var _calcedObj = false override def setBox(in: Box[MyType]): Box[MyType] = synchronized { _calcedObj = false // invalidate the cache @@ -83,7 +83,7 @@ trait MongoRefField[RefType <: MongoRecord[RefType], MyType] extends TypedField[ if (optional_?) (Empty, emptyOptionLabel)::options else options } - private def elem = SHtml.selectObj[Box[MyType]]( + private[this] def elem = SHtml.selectObj[Box[MyType]]( buildDisplayList, Full(valueBox), setBox(_) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/ObjectIdField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/ObjectIdField.scala index 4a86eca1dd..f88cdd3969 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/ObjectIdField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/ObjectIdField.scala @@ -46,7 +46,7 @@ trait ObjectIdTypedField[OwnerType <: BsonRecord[OwnerType]] extends TypedField[ } def setFromJValue(jvalue: JValue): Box[ObjectId] = jvalue match { - case JNothing|JNull if optional_? => setBox(Empty) + case JNothing | JNull if optional_? => setBox(Empty) case JObject(JField("$oid", JString(s)) :: Nil) => setFromString(s) case JString(s) => setFromString(s) case other => setBox(FieldHelpers.expectedA("JObject", other)) @@ -87,7 +87,7 @@ class ObjectIdField[OwnerType <: BsonRecord[OwnerType]](override val owner: Owne setBox(Full(value)) } - override def defaultValue = ObjectId.get + override def defaultValue = new ObjectId def createdAt: Date = this.get.getDate diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/UUIDField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/UUIDField.scala index 2854d9a60e..e79335fd66 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/UUIDField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/UUIDField.scala @@ -54,7 +54,7 @@ trait UUIDTypedField[OwnerType <: BsonRecord[OwnerType]] extends TypedField[UUID case _ => setBox(Failure(s"Invalid UUID string: $in")) } - private def elem = S.fmapFunc(S.SFuncHolder(this.setFromAny(_))) { funcName => + private[this] def elem = S.fmapFunc(S.SFuncHolder(this.setFromAny(_))) { funcName => v.toString) openOr ""} From 5c4120c3856b4cffcbd2361e376ae188a5f4d5ac Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Mon, 3 Jul 2017 09:43:49 -0400 Subject: [PATCH 1597/1949] Mention in the README that Java 8 is required --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 72f54db68c..1bdb229cad 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,10 @@ For more details, see [CONTRIBUTING.md](https://github.com/lift/framework/blob/m ## Getting Started +**Compatibility note:** +As of Lift 3.0, you'll need to be running Java 8 to use Lift. For those using Java 6 or Java 7, +you'll need to use Lift 2.6 until you can upgrade your Java installation. + You can create a new Lift project using your favorite build system by adding Lift as a dependency: #### sbt From 868227571467e8355dce8c9ca93935e94f512a27 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Mon, 3 Jul 2017 09:54:28 -0400 Subject: [PATCH 1598/1949] Bump version to 3.2.0-SNAPSHOT for forward development --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 797b308195..2b41d45198 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ import Dependencies._ organization in ThisBuild := "net.liftweb" -version in ThisBuild := "3.1-SNAPSHOT" +version in ThisBuild := "3.2.0-SNAPSHOT" homepage in ThisBuild := Some(url("http://www.liftweb.net")) From 46bb3a825bb4f8b118c65da3c17b33b438d67698 Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Mon, 3 Jul 2017 20:25:07 +0530 Subject: [PATCH 1599/1949] Separate out the test for the headers into three seperate parts. Set the default value for the `headers` param in `getRequestSnapshot` to Nil --- .../servlet/OfflineRequestSnapshotSpec.scala | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/web/webkit/src/test/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshotSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshotSpec.scala index fd3daba304..b57cafbfdd 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshotSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/provider/servlet/OfflineRequestSnapshotSpec.scala @@ -25,26 +25,36 @@ import org.specs2.mock.Mockito object OfflineRequestSnapshotSpec extends WebSpec with Mockito { private[this] val X_SSL = "X-SSL" + private[this] val xSSLHeader = HTTPParam(X_SSL, List("true")) :: Nil "OfflineRequestSnapshot" should { "have a 'headers' method that returns the list of headers with a given name" in { - val req = getRequestSnapshot(originalPort = 80) + val req = getRequestSnapshot(originalPort = 80, headers = xSSLHeader) req.headers("X-SSL") shouldEqual List("true") req.headers("Unknown") must beEmpty } "have the serverPort value" in { "443 when the 'X-SSL' header is set to the string 'true' (case-insensitive) and original port is 80" in { - val port80Req = getRequestSnapshot(originalPort = 80) + val port80Req = getRequestSnapshot(originalPort = 80, headers = xSSLHeader) port80Req.serverPort shouldEqual 443 } - s"equal to the original request-port when the '$X_SSL' header is absent or not set to the string 'true' (case-insensitive), or if the original port is not 80" in { - val req = getRequestSnapshot(originalPort = 90) - val nonSSLReq = getRequestSnapshot(originalPort = 80, headers = Nil) - val falseSSLHeaderReq = getRequestSnapshot(originalPort = 90, headers = HTTPParam(X_SSL, List("anything")) :: Nil) - req.serverPort shouldEqual 90 - nonSSLReq.serverPort shouldEqual 80 - falseSSLHeaderReq.serverPort shouldEqual 90 + + s"equal to the original request-port when" in { + s"the '$X_SSL' header is absent" in { + val nonSSLReq = getRequestSnapshot(originalPort = 80) + nonSSLReq.serverPort shouldEqual 80 + } + + s"the '$X_SSL' header is not set to the string 'true' (case-insensitive)" in { + val falseSSLHeaderReq = getRequestSnapshot(originalPort = 90, headers = HTTPParam(X_SSL, List("anything")) :: Nil) + falseSSLHeaderReq.serverPort shouldEqual 90 + } + + "the original request-port is not 80" in { + val req = getRequestSnapshot(originalPort = 90, headers = xSSLHeader) + req.serverPort shouldEqual 90 + } } } @@ -60,9 +70,8 @@ object OfflineRequestSnapshotSpec extends WebSpec with Mockito { } } - private[this] val xSSLHeader = HTTPParam(X_SSL, List("true")) :: Nil - private[this] def getRequestSnapshot(originalPort: Int, headers: List[HTTPParam] = xSSLHeader, params: List[HTTPParam] = Nil) = { + private[this] def getRequestSnapshot(originalPort: Int, headers: List[HTTPParam] = Nil, params: List[HTTPParam] = Nil) = { val mockHttpRequest = mock[HTTPRequest] val httpProvider = new HTTPProvider { override protected def context: HTTPContext = null @@ -76,4 +85,3 @@ object OfflineRequestSnapshotSpec extends WebSpec with Mockito { } } - From 9114bc4d6375e9676bf77505c5546a1f3a2d78df Mon Sep 17 00:00:00 2001 From: ricsirigu Date: Mon, 3 Jul 2017 19:26:09 +0200 Subject: [PATCH 1600/1949] First test --- .../scala/net/liftweb/http/rest/RestHelperSpec.scala | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/web/webkit/src/test/scala/net/liftweb/http/rest/RestHelperSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/rest/RestHelperSpec.scala index a9566b07f2..1c3c6ef1ba 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/rest/RestHelperSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/rest/RestHelperSpec.scala @@ -21,12 +21,17 @@ class RestHelperSpec extends WebSpec(RestHelperSpecBoot.boot _) { "RestHelper" should { val testOptionsUrl = "http://foo.com/api/info" + val testPatchUrl = "http://foo.com/api/patched" val testFutureUrl = "http://foo.com/api/futured" val testOptionsReq = new MockHttpServletRequest(testOptionsUrl){ method = "OPTIONS" } + val testPatchReq = new MockHttpServletRequest(testPatchUrl){ + method = "PATCH" + } + val testFutureReq = new MockHttpServletRequest(testFutureUrl){ method = "GET" } @@ -41,6 +46,10 @@ class RestHelperSpec extends WebSpec(RestHelperSpecBoot.boot _) { } } + "set PATCH method" withReqFor testPatchReq in { req => + req.patch_? must_== true + } + "respond async with something that CanResolveAsync" withReqFor testFutureReq in { req => val helper = FutureRestSpecHelper() @@ -68,6 +77,7 @@ class RestHelperSpec extends WebSpec(RestHelperSpecBoot.boot _) { object RestHelperSpecRest extends RestHelper { serve { case "api" :: "info" :: Nil Options req => OkResponse() + case "api" :: "patched" :: Nil Patch req => OkResponse() } } From c3df0aac625408f2dc5760b1c37f46dd913ff20f Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sun, 9 Jul 2017 13:06:48 +0530 Subject: [PATCH 1601/1949] - Set the deprecatedName annotation for constructor params where the name was changed. - Divide a method body into multiple line to make it more readable --- .../mongodb/record/field/BsonRecordField.scala | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala index 3a3dde47ad..bd11205757 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala @@ -67,8 +67,8 @@ abstract class BsonRecordTypedField[OwnerType <: BsonRecord[OwnerType], SubRecor } class BsonRecordField[OwnerType <: BsonRecord[OwnerType], SubRecordType <: BsonRecord[SubRecordType]] -(rec: OwnerType, valueMeta: BsonMetaRecord[SubRecordType])(implicit subRecordType: Manifest[SubRecordType]) - extends BsonRecordTypedField(rec, valueMeta) with MandatoryTypedField[SubRecordType] { +(@deprecatedName('rec, "Lift 3.2") owner: OwnerType, valueMeta: BsonMetaRecord[SubRecordType])(implicit subRecordType: Manifest[SubRecordType]) + extends BsonRecordTypedField(owner, valueMeta) with MandatoryTypedField[SubRecordType] { def this(rec: OwnerType, valueMeta: BsonMetaRecord[SubRecordType], value: SubRecordType)(implicit subRecordType: Manifest[SubRecordType]) = { this(rec, value.meta) @@ -79,16 +79,16 @@ class BsonRecordField[OwnerType <: BsonRecord[OwnerType], SubRecordType <: BsonR } class OptionalBsonRecordField[OwnerType <: BsonRecord[OwnerType], SubRecordType <: BsonRecord[SubRecordType]] -(rec: OwnerType, valueMeta: BsonMetaRecord[SubRecordType])(implicit subRecordType: Manifest[SubRecordType]) - extends BsonRecordTypedField(rec, valueMeta) with OptionalTypedField[SubRecordType] +(owner: OwnerType, valueMeta: BsonMetaRecord[SubRecordType])(implicit subRecordType: Manifest[SubRecordType]) + extends BsonRecordTypedField(owner, valueMeta) with OptionalTypedField[SubRecordType] /* * List of BsonRecords */ class BsonRecordListField[OwnerType <: BsonRecord[OwnerType], SubRecordType <: BsonRecord[SubRecordType]] - (rec: OwnerType, valueMeta: BsonMetaRecord[SubRecordType])(implicit mf: Manifest[SubRecordType]) - extends MongoListField[OwnerType, SubRecordType](rec: OwnerType) { + (@deprecatedName('rec, "Lift 3.2") owner: OwnerType, valueMeta: BsonMetaRecord[SubRecordType])(implicit mf: Manifest[SubRecordType]) + extends MongoListField[OwnerType, SubRecordType](owner: OwnerType) { import scala.collection.JavaConverters._ @@ -117,7 +117,8 @@ class BsonRecordListField[OwnerType <: BsonRecord[OwnerType], SubRecordType <: B } override def setFromDocumentList(list: java.util.List[Document]): Box[List[SubRecordType]] = { - setBox(Full(list.asScala.map { valueMeta.fromDocument }.toList)) + setBox(Full( + list.asScala.toList.map { valueMeta.fromDocument } + )) } - } From 66eeb0321680bda6f7de3177b7a8587b354caf66 Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sun, 9 Jul 2017 13:08:11 +0530 Subject: [PATCH 1602/1949] Add a newline at the end --- .../scala/net/liftweb/mongodb/record/field/JObjectField.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala index ae4a421327..42171277bf 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala @@ -92,4 +92,4 @@ class OptionalJObjectField[OwnerType <: BsonRecord[OwnerType]](override val owne setBox(value) } -} \ No newline at end of file +} From f02c29c4587211f53900d20c1fe613b5532b3f1e Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sun, 9 Jul 2017 13:14:16 +0530 Subject: [PATCH 1603/1949] Rename the file to be consistent with the new class name (old class name was deprecated) --- .../field/{MongoCaseClassField.scala => CaseClassField.scala} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/{MongoCaseClassField.scala => CaseClassField.scala} (100%) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoCaseClassField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/CaseClassField.scala similarity index 100% rename from persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoCaseClassField.scala rename to persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/CaseClassField.scala From 1470b179f02266e08ae47b0705dc6b74ecbf683e Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sun, 9 Jul 2017 13:15:25 +0530 Subject: [PATCH 1604/1949] Put the 'then' parts of an if-else statement in braces --- .../net/liftweb/mongodb/record/field/ObjectIdField.scala | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/ObjectIdField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/ObjectIdField.scala index f88cdd3969..916bd736e9 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/ObjectIdField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/ObjectIdField.scala @@ -53,8 +53,11 @@ trait ObjectIdTypedField[OwnerType <: BsonRecord[OwnerType]] extends TypedField[ } def setFromString(in: String): Box[ObjectId] = { - if (ObjectId.isValid(in)) setBox(Full(new ObjectId(in))) - else setBox(Failure(s"Invalid ObjectId string: $in")) + if (ObjectId.isValid(in)) { + setBox (Full(new ObjectId(in))) + } else { + setBox(Failure(s"Invalid ObjectId string: $in")) + } } private def elem = @@ -102,4 +105,3 @@ class OptionalObjectIdField[OwnerType <: BsonRecord[OwnerType]](override val own } } - From 7cc9146408dcba53b062e7960a682aa43197ebe8 Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sun, 9 Jul 2017 13:18:08 +0530 Subject: [PATCH 1605/1949] Change 'deprecated' annotation to include the expected version when the deprecated versions will be removed. Add a new line at the end where missing --- .../net/liftweb/mongodb/record/field/CaseClassField.scala | 6 +++--- .../net/liftweb/mongodb/record/field/JsonObjectField.scala | 4 ++-- .../net/liftweb/mongodb/record/field/MongoListField.scala | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/CaseClassField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/CaseClassField.scala index 638e9fe18b..7386e3eb54 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/CaseClassField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/CaseClassField.scala @@ -93,7 +93,7 @@ class CaseClassField[OwnerType <: Record[OwnerType], CaseType](rec: OwnerType)(i override def defaultValue = null.asInstanceOf[MyType] } -@deprecated("Use the more consistently named 'CaseClassField' instead", "3.2") +@deprecated("Use the more consistently named 'CaseClassField' instead. This class will be removed in Lift 4.", "3.2") class MongoCaseClassField[OwnerType <: Record[OwnerType], CaseType](rec: OwnerType)(implicit mf: Manifest[CaseType]) extends CaseClassField[OwnerType, CaseType](rec) @@ -173,6 +173,6 @@ class CaseClassListField[OwnerType <: Record[OwnerType], CaseType](override val } } -@deprecated("Please use the more consistently named 'CaseClassListField' instead", "3.2") +@deprecated("Please use the more consistently named 'CaseClassListField' instead. This class will be removed in Lift 4.", "3.2") class MongoCaseClassListField[OwnerType <: Record[OwnerType], CaseType](owner: OwnerType)(implicit mf: Manifest[CaseType]) - extends CaseClassListField[OwnerType, CaseType](owner) \ No newline at end of file + extends CaseClassListField[OwnerType, CaseType](owner) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JsonObjectField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JsonObjectField.scala index b7f77cfefe..55ba5eefde 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JsonObjectField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JsonObjectField.scala @@ -112,6 +112,6 @@ class JsonObjectListField[OwnerType <: BsonRecord[OwnerType], JObjectType <: Jso } } -@deprecated("Use the more consistently named 'JsonObjectListField' instead", "3.2") +@deprecated("Use the more consistently named 'JsonObjectListField' instead. This class will be removed in Lift 4.", "3.2") class MongoJsonObjectListField[OwnerType <: BsonRecord[OwnerType], JObjectType <: JsonObject[JObjectType]] -(rec: OwnerType, valueMeta: JsonObjectMeta[JObjectType])(implicit mf: Manifest[JObjectType]) extends JsonObjectListField(rec, valueMeta) \ No newline at end of file +(rec: OwnerType, valueMeta: JsonObjectMeta[JObjectType])(implicit mf: Manifest[JObjectType]) extends JsonObjectListField(rec, valueMeta) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoListField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoListField.scala index c2cc4320be..0257b48a5b 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoListField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoListField.scala @@ -161,4 +161,4 @@ class MongoListField[OwnerType <: BsonRecord[OwnerType], ListType: Manifest](rec throw new RuntimeException("Warning, setting Document as field with no conversion, probably not something you want to do") } -} \ No newline at end of file +} From f79d6334d10263a7611be3fbb5dafbed8e53cec5 Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sun, 9 Jul 2017 13:23:24 +0530 Subject: [PATCH 1606/1949] Add 'deprecatedName' annotation for changing constructor param names. Change constructor param name from 'rec' to more instructive 'owner' --- .../scala/net/liftweb/mongodb/record/field/JObjectField.scala | 2 +- .../scala/net/liftweb/mongodb/record/field/PatternField.scala | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala index 42171277bf..7dcaf04a9e 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala @@ -72,7 +72,7 @@ trait JObjectTypedField[OwnerType <: BsonRecord[OwnerType]] extends TypedField[J override def asJValue: JValue = valueBox openOr (JNothing: JValue) } -class JObjectField[OwnerType <: BsonRecord[OwnerType]](override val owner: OwnerType) +class JObjectField[OwnerType <: BsonRecord[OwnerType]](@deprecatedName('rec, "Lift 3.2") override val owner: OwnerType) extends JObjectTypedField[OwnerType] with MandatoryTypedField[JObject] { def this(owner: OwnerType, value: JObject) = { diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/PatternField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/PatternField.scala index 396de18509..6fac5b1777 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/PatternField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/PatternField.scala @@ -64,7 +64,7 @@ abstract class PatternTypedField[OwnerType <: BsonRecord[OwnerType]](override va def asJValue: JValue = valueBox.map(v => JsonRegex(v)) openOr (JNothing: JValue) } -class PatternField[OwnerType <: BsonRecord[OwnerType]](owner: OwnerType) extends PatternTypedField[OwnerType](owner) with MandatoryTypedField[Pattern] { +class PatternField[OwnerType <: BsonRecord[OwnerType]](@deprecatedName('rec, "Lift 3.2") owner: OwnerType) extends PatternTypedField[OwnerType](owner) with MandatoryTypedField[Pattern] { def this(rec: OwnerType, value: Pattern) = { this(rec) setBox(Full(value)) @@ -73,7 +73,7 @@ class PatternField[OwnerType <: BsonRecord[OwnerType]](owner: OwnerType) extends override def defaultValue = Pattern.compile("") } -class OptionalPatternField[OwnerType <: BsonRecord[OwnerType]](rec: OwnerType) extends PatternTypedField[OwnerType](rec) with OptionalTypedField[Pattern] { +class OptionalPatternField[OwnerType <: BsonRecord[OwnerType]](owner: OwnerType) extends PatternTypedField[OwnerType](owner) with OptionalTypedField[Pattern] { def this(rec: OwnerType, value: Box[Pattern]) = { this(rec) setBox(value) From 73ce109724745d0b7ab8683634cadc9cce560b25 Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sun, 9 Jul 2017 14:09:21 +0530 Subject: [PATCH 1607/1949] Change method to use collect instead of using map and filter with nulls. Remove deprecation warning for JavaConversions implicits --- .../mongodb/record/MongoRecordExamplesSpec.scala | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordExamplesSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordExamplesSpec.scala index d44f7d1761..b93279d509 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordExamplesSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordExamplesSpec.scala @@ -98,7 +98,7 @@ package mongotestrecords { class ListDoc private () extends MongoRecord[ListDoc] with ObjectIdPk[ListDoc] { def meta = ListDoc - import scala.collection.JavaConversions._ + import scala.collection.JavaConverters._ // standard list types object name extends StringField(this, 10) @@ -136,13 +136,10 @@ package mongotestrecords { override def setFromDBObject(dbo: DBObject): Box[List[Map[String, String]]] = { val lst: List[Map[String, String]] = - dbo.keySet.toList.map(k => { - dbo.get(k.toString) match { - case bdbo: BasicDBObject if (bdbo.containsField("name") && bdbo.containsField("type")) => - Map("name"-> bdbo.getString("name"), "type" -> bdbo.getString("type")) - case _ => null - } - }).filter(_ != null) + dbo.keySet.asScala.toList.map(dbo.get).collect { + case bdbo: BasicDBObject if bdbo.containsField("name") && bdbo.containsField("type") => + Map("name"-> bdbo.getString("name"), "type" -> bdbo.getString("type")) + } Full(set(lst)) } } From 2cc1ac8dfff1c1b8b22342e1e28ba3489b720947 Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sun, 9 Jul 2017 14:53:47 +0530 Subject: [PATCH 1608/1949] Initial pass at `collectFailure` method --- core/common/src/main/scala/net/liftweb/common/Box.scala | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index 1c59586327..2c2072a5f4 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -808,6 +808,11 @@ sealed abstract class Box[+A] extends Product with Serializable{ final def collectFirst[B](pf: PartialFunction[A, B]): Box[B] = { collect(pf) } + + final def collectFailure[B](pf: PartialFunction[Failure, Failure]): Box[Failure] = this match { + case f: Failure if pf.isDefinedAt(f) => Full(pf(f)) + case _ => Empty + } } /** From 86c603ac897160c45a29c586c2cd2759331f6889 Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sun, 9 Jul 2017 15:16:31 +0530 Subject: [PATCH 1609/1949] Remove the additional string arg from `deprecatedName` annotation since it's only available since scala 2.12 --- .../net/liftweb/mongodb/record/field/BsonRecordField.scala | 4 ++-- .../scala/net/liftweb/mongodb/record/field/JObjectField.scala | 2 +- .../scala/net/liftweb/mongodb/record/field/PatternField.scala | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala index bd11205757..b54c09bb3c 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala @@ -67,7 +67,7 @@ abstract class BsonRecordTypedField[OwnerType <: BsonRecord[OwnerType], SubRecor } class BsonRecordField[OwnerType <: BsonRecord[OwnerType], SubRecordType <: BsonRecord[SubRecordType]] -(@deprecatedName('rec, "Lift 3.2") owner: OwnerType, valueMeta: BsonMetaRecord[SubRecordType])(implicit subRecordType: Manifest[SubRecordType]) +(@deprecatedName('rec) owner: OwnerType, valueMeta: BsonMetaRecord[SubRecordType])(implicit subRecordType: Manifest[SubRecordType]) extends BsonRecordTypedField(owner, valueMeta) with MandatoryTypedField[SubRecordType] { def this(rec: OwnerType, valueMeta: BsonMetaRecord[SubRecordType], value: SubRecordType)(implicit subRecordType: Manifest[SubRecordType]) = { @@ -87,7 +87,7 @@ class OptionalBsonRecordField[OwnerType <: BsonRecord[OwnerType], SubRecordType * List of BsonRecords */ class BsonRecordListField[OwnerType <: BsonRecord[OwnerType], SubRecordType <: BsonRecord[SubRecordType]] - (@deprecatedName('rec, "Lift 3.2") owner: OwnerType, valueMeta: BsonMetaRecord[SubRecordType])(implicit mf: Manifest[SubRecordType]) + (@deprecatedName('rec) owner: OwnerType, valueMeta: BsonMetaRecord[SubRecordType])(implicit mf: Manifest[SubRecordType]) extends MongoListField[OwnerType, SubRecordType](owner: OwnerType) { import scala.collection.JavaConverters._ diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala index 7dcaf04a9e..2e869db9fd 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala @@ -72,7 +72,7 @@ trait JObjectTypedField[OwnerType <: BsonRecord[OwnerType]] extends TypedField[J override def asJValue: JValue = valueBox openOr (JNothing: JValue) } -class JObjectField[OwnerType <: BsonRecord[OwnerType]](@deprecatedName('rec, "Lift 3.2") override val owner: OwnerType) +class JObjectField[OwnerType <: BsonRecord[OwnerType]](@deprecatedName('rec) override val owner: OwnerType) extends JObjectTypedField[OwnerType] with MandatoryTypedField[JObject] { def this(owner: OwnerType, value: JObject) = { diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/PatternField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/PatternField.scala index 6fac5b1777..ee549cfdc0 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/PatternField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/PatternField.scala @@ -64,7 +64,7 @@ abstract class PatternTypedField[OwnerType <: BsonRecord[OwnerType]](override va def asJValue: JValue = valueBox.map(v => JsonRegex(v)) openOr (JNothing: JValue) } -class PatternField[OwnerType <: BsonRecord[OwnerType]](@deprecatedName('rec, "Lift 3.2") owner: OwnerType) extends PatternTypedField[OwnerType](owner) with MandatoryTypedField[Pattern] { +class PatternField[OwnerType <: BsonRecord[OwnerType]](@deprecatedName('rec) owner: OwnerType) extends PatternTypedField[OwnerType](owner) with MandatoryTypedField[Pattern] { def this(rec: OwnerType, value: Pattern) = { this(rec) setBox(Full(value)) From a10b78e4a7539f95e06d11dbba681458fd804d55 Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Mon, 10 Jul 2017 09:11:55 +0530 Subject: [PATCH 1610/1949] Correction in collectFailure method signature. Add tests and comments --- .../main/scala/net/liftweb/common/Box.scala | 22 ++++++++++++++++++- .../scala/net/liftweb/common/BoxSpec.scala | 15 +++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index 2c2072a5f4..554988e114 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -809,7 +809,27 @@ sealed abstract class Box[+A] extends Product with Serializable{ collect(pf) } - final def collectFailure[B](pf: PartialFunction[Failure, Failure]): Box[Failure] = this match { + /** + * If this box is a `Failure` and if `pf` is defined for this `Failure` instance, returns + * a `Full` box containing the result of applying `pf` to this `Failure`, otherwise, returns + * `Empty`. + * + * @example {{{ + * // Returns Full("alternative") because the partial function covers the case. + * Failure("error") collectFailure { case Failure("error", Empty, Empty) => "alternative" } + * + * // Returns Empty because the partial function doesn't cover the case. + * Failure("another-error") collectFailure { case Failure("error", Empty, Empty) => "alternative" } + * + * // Returns Empty for an Empty box + * Empty collectFailure { case _ => Failure("error") } + * + * // Returns Empty for a Full box + * Full(1) collectFailure { case _ => Failure("error") } + * + * }}} + */ + final def collectFailure[B](pf: PartialFunction[Failure, B]): Box[B] = this match { case f: Failure if pf.isDefinedAt(f) => Full(pf(f)) case _ => Empty } diff --git a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala index 02de73a878..bfb24f6b26 100644 --- a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala +++ b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala @@ -164,6 +164,9 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { Full("Hermione") collect { case "Albus" => "Dumbledore"} must beEmpty } } + "define a 'collectFailure' method returning Empty" in { + Full(1) collectFailure { case _ => Failure("error") } must_== Empty + } "define an 'elements' method returning an iterator containing its value" in { Full(1).elements.next must_== 1 } @@ -317,6 +320,9 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { "define a 'collect' method returning Empty" in { Empty collect { case _ => "Some Value" } must beEmpty } + "define a 'collectFailure' method returning Empty" in { + Empty collectFailure { case _ => Failure("error") } must_== Empty + } "define an 'elements' method returning an empty iterator" in { Empty.elements.hasNext must beFalse } @@ -384,6 +390,15 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { "define a 'collect' method returning itself" in { Failure("error", Empty, Empty) collect { case _ => "Some Value" } must_== Failure("error", Empty, Empty) } + "define a 'collectFailure' method that takes a PartialFunction to transform this Failure into something else" in { + "If the partial-function is defined for this Failure, returns a full box containing the result of applying the partial function to it" in { + Failure("The Phantom Menace") collectFailure { case Failure("The Phantom Menace", Empty, Empty) => "Return Of The Jedi" } must_== Full("Return Of The Jedi") + Failure("The Phantom Menace") collectFailure { case Failure("The Phantom Menace", Empty, Empty) => Failure("Attack") } must_== Full(Failure("Attack")) + } + "If the partial-function is not defined for this Failure, returns Empty" in { + Failure("Attack Of The Clones") collectFailure { case Failure("The Phantom Menace", Empty, Empty) => "Return Of The Jedi" } must_== Empty + } + } "return itself when asked for its status with the operator ?~" in { Failure("error", Empty, Empty) ?~ "nothing" must_== Failure("error", Empty, Empty) } From a4b0858a9712fde6b9bd8c73e977c211398b726e Mon Sep 17 00:00:00 2001 From: Christopher Webster Date: Wed, 19 Jul 2017 21:06:41 -0700 Subject: [PATCH 1611/1949] This change contains two optimizations: In appendEscapedString requires a boxing operation to transform Char into Character for the Set.contains(java.lang.Object) operation, even is the escapedChars isEmpty (the default) The patch eliminates this check when the settings.escapeChars isEmpty using && to short circuit the Set.contains method In Meta memoize there is an extra lookup and Option creation as both Map.contains and Map.apply are implemented using Map.get for the case where the key has already been cached (one for contains and another for get, which is creating additional Option instances). The patch uses getOrElse to avoid the second lookup (the or else populates the cache as a side effect). --- core/json/src/main/scala/net/liftweb/json/JsonAST.scala | 5 +++-- core/json/src/main/scala/net/liftweb/json/Meta.scala | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/core/json/src/main/scala/net/liftweb/json/JsonAST.scala b/core/json/src/main/scala/net/liftweb/json/JsonAST.scala index 6edc5da6c6..b766e122f1 100644 --- a/core/json/src/main/scala/net/liftweb/json/JsonAST.scala +++ b/core/json/src/main/scala/net/liftweb/json/JsonAST.scala @@ -827,7 +827,8 @@ object JsonAST { case '\n' => "\\n" case '\r' => "\\r" case '\t' => "\\t" - case c if ((c >= '\u0000' && c < '\u0020')) || settings.escapeChars.contains(c) => + // Set.contains will cause boxing of c to Character, try and avoid this + case c if ((c >= '\u0000' && c < '\u0020')) || (settings.escapeChars.nonEmpty && settings.escapeChars.contains(c)) => "\\u%04x".format(c: Int) case _ => "" @@ -947,7 +948,7 @@ object JsonAST { */ case class RenderSettings( indent: Int, - escapeChars: Set[Char] = Set(), + escapeChars: Set[Char] = Set.empty, spaceAfterFieldName: Boolean = false, doubleRenderer: DoubleRenderer = RenderSpecialDoubleValuesAsNull ) { diff --git a/core/json/src/main/scala/net/liftweb/json/Meta.scala b/core/json/src/main/scala/net/liftweb/json/Meta.scala index 7161f14c9f..e2bf287933 100644 --- a/core/json/src/main/scala/net/liftweb/json/Meta.scala +++ b/core/json/src/main/scala/net/liftweb/json/Meta.scala @@ -231,11 +231,12 @@ private[json] object Meta { def memoize(x: A, f: A => R): R = { val c = cache.get - if (c contains x) c(x) else { + def addToCache() = { val ret = f(x) cache.set(c + (x -> ret)) ret } + c.getOrElse(x, addToCache) } } From 3fc0e1d67f7c500daee38daed415080f988122a8 Mon Sep 17 00:00:00 2001 From: Christopher Webster Date: Fri, 21 Jul 2017 15:39:26 -0700 Subject: [PATCH 1612/1949] The Extraction mkMapping and decompose methods have code to determine if a tuple is being used and whether this should be considered as formatted as array. The tuple_? method is a Seq that contains the classes for the 22 varieties of tuples and the tuple_? method does a O(n) scan on this frequently during the case class serialization / deserialization process. Currently, the code is performing contains first and then checking whether Tuples are formatted as arrays (where the default is false). This change reverses the order to short circuit this check. --- core/json/src/main/scala/net/liftweb/json/Extraction.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/json/src/main/scala/net/liftweb/json/Extraction.scala b/core/json/src/main/scala/net/liftweb/json/Extraction.scala index 9377cd6f4f..e429a87619 100644 --- a/core/json/src/main/scala/net/liftweb/json/Extraction.scala +++ b/core/json/src/main/scala/net/liftweb/json/Extraction.scala @@ -83,7 +83,7 @@ object Extraction { case x: Iterable[_] => JArray(x.toList map decompose) case x if (x.getClass.isArray) => JArray(x.asInstanceOf[Array[_]].toList map decompose) case x: Option[_] => x.flatMap[JValue] { y => Some(decompose(y)) }.getOrElse(JNothing) - case x: Product if tuple_?(x.getClass) && formats.tuplesAsArrays => + case x: Product if formats.tuplesAsArrays && tuple_?(x.getClass) => JArray(x.productIterator.toList.map(decompose)) case x => val fields = getDeclaredFields(x.getClass) @@ -192,7 +192,7 @@ object Extraction { Col(TypeInfo(clazz, None), mkMapping(typeArgs.head, typeArgs.tail)) } else if (clazz == classOf[Map[_, _]]) { Dict(mkMapping(typeArgs.tail.head, typeArgs.tail.tail)) - } else if (tuple_?(clazz) && formats.tuplesAsArrays) { + } else if (formats.tuplesAsArrays && tuple_?(clazz)) { val childMappings = typeArgs.map(c => mkMapping(c, Nil)).toList HCol(TypeInfo(clazz, None), childMappings) } else { From e5435b19734e18a4089f272098d48c1bb7a565b5 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 22 Jul 2017 15:48:54 -0400 Subject: [PATCH 1613/1949] Fix example in lift-json's Xml helper. We had some outdated code that used `map` instead of `mapField`. --- .../src/main/scala/net/liftweb/json/Xml.scala | 112 +++++++++--------- 1 file changed, 58 insertions(+), 54 deletions(-) diff --git a/core/json/src/main/scala/net/liftweb/json/Xml.scala b/core/json/src/main/scala/net/liftweb/json/Xml.scala index 209a752f4b..39ac0b0896 100644 --- a/core/json/src/main/scala/net/liftweb/json/Xml.scala +++ b/core/json/src/main/scala/net/liftweb/json/Xml.scala @@ -22,32 +22,33 @@ package json object Xml { import scala.xml._ - /** Convert given XML to JSON. - *

              - * Following rules are used in conversion. - *

                - *
              • XML leaf element is converted to JSON string
              • - *
              • XML parent element is converted to JSON object and its children to JSON fields
              • - *
              • XML elements with same name at same level are converted to JSON array
              • - *
              • XML attributes are converted to JSON fields
              • - *
              - *

              - * Example:

              +  /**
              +   * Converts the given XML to JSON.
              +   *
              +   * The following rules are used in the conversion:
              +   *
              +   *  - an XML leaf element is converted to a JSON string
              +   *  - an XML parent element is converted to a JSON object and its children to JSON fields
              +   *  - XML elements with the same name at the same level are converted to a JSON array
              +   *  - XML attributes are converted to JSON fields
              +   *
              +   * For example:
              +   * {{{
                  * scala> val xml =
              -   *     <users>
              -   *       <user>
              -   *         <id>1</id>
              -   *         <name>Harry</name>
              -   *       </user>
              -   *       <user>
              -   *         <id>2</id>
              -   *         <name>David</name>
              -   *       </user>
              -   *     </users>   
              +   *     
              +   *       
              +   *         1
              +   *         Harry
              +   *       
              +   *       
              +   *         2
              +   *         David
              +   *       
              +   *     
                  *
                  * scala> val json = toJson(xml)
              -   * scala> pretty(render(json))
              -   * 
              +   * scala> prettyRender(json)
              +   *
                  * {
                  *   "users":{
                  *     "user":[{
              @@ -59,22 +60,24 @@ object Xml {
                  *     }]
                  *   }
                  * }
              -   * 
              + * }}} + * + * Now, the above example has two problems. First, the id is converted to a + * `String` while we might want it as an `Int`. This is easy to fix by mapping + * `JString(s)` to `JInt(s.toInt)`. The second problem is more subtle: the + * conversion function decides to use a JSON array because there's more than + * one `user` element in the XML. Therefore a structurally equivalent XML + * document which happens to have just one `user` element will generate a JSON + * document without a JSON array. This is rarely a desired outcome. Both of + * these problems can be fixed by the following `map` invocation: * - * Now, the above example has two problems. First, the id is converted to String while - * we might want it as an Int. This is easy to fix by mapping JString(s) to JInt(s.toInt). - * The second problem is more subtle. The conversion function decides to use JSON array - * because there's more than one user-element in XML. Therefore a structurally equivalent - * XML document which happens to have just one user-element will generate a JSON document - * without JSON array. This is rarely a desired outcome. These both problems can be fixed - * by following map function. - *
              -   * json map {
              +   * {{{
              +   * json mapField {
                  *   case JField("id", JString(s)) => JField("id", JInt(s.toInt))
                  *   case JField("user", x: JObject) => JField("user", JArray(x :: Nil))
              -   *   case x => x 
              +   *   case x => x
                  * }
              -   * 
              + * }}} */ def toJson(xml: NodeSeq): JValue = { def empty_?(node: Node) = node.child.isEmpty @@ -113,10 +116,10 @@ object Xml { case XArray(elems) => JArray(elems.map(toJValue)) } - def mkFields(xs: List[(String, XElem)]) = + def mkFields(xs: List[(String, XElem)]) = xs.flatMap { case (name, value) => (value, toJValue(value)) match { // This special case is needed to flatten nested objects which resulted from - // XML attributes. Flattening keeps transformation more predicatable. + // XML attributes. Flattening keeps transformation more predicatable. // x -> {"a":{"foo":{"foo":"x","id":"1"}}} vs // x -> {"a":{"foo":"x","id":"1"}} case (XLeaf(v, x :: xs), o: JObject) => o.obj @@ -130,17 +133,17 @@ object Xml { val children = directChildren(n) XNode(buildAttrs(n) ::: children.map(nameOf).toList.zip(buildNodes(children))) :: Nil } - case nodes: NodeSeq => + case nodes: NodeSeq => val allLabels = nodes.map(_.label) if (array_?(allLabels)) { - val arr = XArray(nodes.toList.flatMap { n => + val arr = XArray(nodes.toList.flatMap { n => if (leaf_?(n) && n.attributes.length == 0) XValue(n.text) :: Nil else buildNodes(n) }) XLeaf((allLabels(0), arr), Nil) :: Nil } else nodes.toList.flatMap(buildNodes) } - + buildNodes(xml) match { case List(x @ XLeaf(_, _ :: _)) => toJValue(x) case List(x) => JObject(JField(nameOf(xml.head), toJValue(x)) :: Nil) @@ -148,24 +151,25 @@ object Xml { } } - /** Convert given JSON to XML. - *

              - * Following rules are used in conversion. - *

                - *
              • JSON primitives are converted to XML leaf elements
              • - *
              • JSON objects are converted to XML elements
              • - *
              • JSON arrays are recursively converted to XML elements
              • - *
              - *

              - * Use map function to preprocess JSON before conversion to adjust - * the end result. For instance a common conversion is to encode arrays as comma - * separated Strings since XML does not have array type. - *

              +  /**
              +   * Converts the given JSON to XML.
              +   *
              +   * The following rules are used in conversion:
              +   *
              +   *  - JSON primitives are converted to XML leaf elements
              +   *  - JSON objects are converted to XML elements
              +   *  - JSON arrays are recursively converted to XML elements
              +   *
              +   * Use the `map` function to preprocess JSON before conversion to adjust
              +   * the end result. For instance a common conversion is to encode arrays as
              +   * comma separated Strings since XML does not have an array type:
              +   *
              +   * {{{
                  * toXml(json map {
                  *   case JField("nums",JArray(ns)) => JField("nums",JString(ns.map(_.values).mkString(",")))
                  *   case x => x
                  * })
              -   * 
              + * }}} */ def toXml(json: JValue): NodeSeq = { def toXml(name: String, json: JValue): NodeSeq = json match { From 9a3f391834046f33b52bbfcd71583af581766668 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 22 Jul 2017 16:23:17 -0400 Subject: [PATCH 1614/1949] Fix ordering expectation in LAFutureSpec. We were running three futures but checking the final outcome for them to be in order. We remove the ordering constraint since the futures could complete in a different order than they were declared in, even if they return a value immediately. --- .../src/test/scala/net/liftweb/actor/LAFutureSpec.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/actor/src/test/scala/net/liftweb/actor/LAFutureSpec.scala b/core/actor/src/test/scala/net/liftweb/actor/LAFutureSpec.scala index f2afc7b927..f200bd54e9 100644 --- a/core/actor/src/test/scala/net/liftweb/actor/LAFutureSpec.scala +++ b/core/actor/src/test/scala/net/liftweb/actor/LAFutureSpec.scala @@ -108,7 +108,10 @@ class LAFutureSpec extends Specification { val three: LAFuture[Box[Int]] = LAFuture(() => Full(3): Box[Int]) val collectResult = LAFuture.collectAll(one, two, three) - collectResult.get(timeout) shouldEqual Full(List(1, 2, 3)) + collectResult.get(timeout) should beLike { + case Full(Full(results)) => + results should contain(allOf(1, 2, 3)) + } } "collectAll reports a failed LAFuture as a failure for the overall future" in { From d90ccffc8180c08549e83292013db423b95439cd Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sun, 23 Jul 2017 10:22:11 +0530 Subject: [PATCH 1615/1949] Redefine the `collect` method to use an existing `apply` function. Wrap a couple of functions in braces. Replace if-else for checking partial function applicability with an apply-or-else. Add a `flip` method to Box --- .../main/scala/net/liftweb/common/Box.scala | 26 ++++++++++++++----- .../scala/net/liftweb/common/BoxSpec.scala | 15 +++++++++++ 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index 554988e114..e5c5779478 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -167,8 +167,9 @@ sealed trait BoxTrait { * @return A `Full` containing the transformed value if * `pf.isDefinedAt(value)` and `Empty` otherwise. */ - def apply[InType, OutType](pf: PartialFunction[InType, OutType])(value: InType): Box[OutType] = - if (pf.isDefinedAt(value)) Full(pf(value)) else Empty + def apply[InType, OutType](pf: PartialFunction[InType, OutType])(value: InType): Box[OutType] = { + apply(value)(pf) + } /** * Apply the specified `PartialFunction` to the specified `value` and return @@ -180,8 +181,9 @@ sealed trait BoxTrait { * @return A `Full` containing the transformed value if * `pf.isDefinedAt(value)` and `Empty` otherwise. */ - def apply[InType, OutType](value: InType)(pf: PartialFunction[InType, OutType]): Box[OutType] = - if (pf.isDefinedAt(value)) Full(pf(value)) else Empty + def apply[InType, OutType](value: InType)(pf: PartialFunction[InType, OutType]): Box[OutType] = { + pf.andThen(Full.apply).applyOrElse(value, (_: InType) => Empty) + } /** * This implicit transformation allows one to use a `Box` as an `Iterable` of @@ -755,7 +757,7 @@ sealed abstract class Box[+A] extends Product with Serializable{ /** - * If the `Box` is `Full`, apply the transform function `f` on the value `v`; + * If the `Box` is `Fuvall`, apply the transform function `f` on the value `v`; * otherwise, just return the value untransformed. * * The transform function is expected to be a function that will take the @@ -797,7 +799,9 @@ sealed abstract class Box[+A] extends Product with Serializable{ * If the partial function is defined at the current Box's value, apply the * partial function. */ - final def collect[B](pf: PartialFunction[A, B]): Box[B] = filter(pf.isDefinedAt).map(pf) + final def collect[B](pf: PartialFunction[A, B]): Box[B] = flatMap { value => + Box(value)(pf) + } /** * An alias for `collect`. @@ -833,6 +837,16 @@ sealed abstract class Box[+A] extends Product with Serializable{ case f: Failure if pf.isDefinedAt(f) => Full(pf(f)) case _ => Empty } + + /** + * Returns a `Full` box containing the results of applying `flipFn` to this box if it is a `Failure`, + * `ParamFailure` or `Empty`. Returns `Empty` if this box is `Full`. In other words, it "flips" the + * full/empty status of this Box. + */ + def flip[B](flipFn: EmptyBox => B): Box[B] = this match { + case e: EmptyBox => Full(flipFn(e)) + case _ => Empty + } } /** diff --git a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala index bfb24f6b26..0d8ae0dbc0 100644 --- a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala +++ b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala @@ -167,6 +167,9 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { "define a 'collectFailure' method returning Empty" in { Full(1) collectFailure { case _ => Failure("error") } must_== Empty } + "define a 'flip' method returning Empty" in { + Full(1) flip { _ => "No data found" } mustEqual Empty + } "define an 'elements' method returning an iterator containing its value" in { Full(1).elements.next must_== 1 } @@ -323,6 +326,12 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { "define a 'collectFailure' method returning Empty" in { Empty collectFailure { case _ => Failure("error") } must_== Empty } + "define a 'flip' method returning a Full box" in { + Empty flip { + case Empty => "flipped-empty" + case _ => "flipped-failure" + } mustEqual Full("flipped-empty") + } "define an 'elements' method returning an empty iterator" in { Empty.elements.hasNext must beFalse } @@ -399,6 +408,12 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { Failure("Attack Of The Clones") collectFailure { case Failure("The Phantom Menace", Empty, Empty) => "Return Of The Jedi" } must_== Empty } } + "define a 'flip' method returning a Full box" in { + Failure("error", Empty, Empty) flip { + case Empty => "flipped-empty" + case _: Failure => "flipped-failure" + } mustEqual Full("flipped-failure") + } "return itself when asked for its status with the operator ?~" in { Failure("error", Empty, Empty) ?~ "nothing" must_== Failure("error", Empty, Empty) } From 8eb909693108e48829a04d1a50c47b9813e892fa Mon Sep 17 00:00:00 2001 From: Joe Barnes Date: Sun, 23 Jul 2017 12:00:41 -0500 Subject: [PATCH 1616/1949] Roughed out first ideas for LiftRulesSetting --- .../scala/net/liftweb/http/LiftRules.scala | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 0992f6fe30..ee2683411d 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -142,6 +142,8 @@ object LiftRules extends LiftRulesMocker { */ type StatelessTestPF = PartialFunction[List[String], Boolean] + type StackTrace = Array[StackTraceElement] + /** * The test between the path of a request, the HTTP request, and whether that path @@ -1582,6 +1584,12 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { private def logSnippetFailure(sf: SnippetFailure) = logger.info("Snippet Failure: " + sf) + val exceptIfSettingWrittenAfterWrite: SettingWrittenAfterWrite => Unit = e => throw e + val warnIfSettingWrittenAfterWrite: SettingWrittenAfterWrite => Unit = e => logger.warn("LiftRules setting written after a read!", e) + val settingWriteAfterReadFunc = new LiftRulesSetting[SettingWrittenAfterWrite => Unit]("settingWriteAfterReadFunc", exceptIfSettingWrittenAfterWrite) + + val joesciiSetting = new LiftRulesSetting[Boolean]("joesciiSetting", true) + /** * Set to false if you do not want ajax/comet requests that are not * associated with a session to call their respective session @@ -2295,3 +2303,28 @@ trait FormVendor { private object requestForms extends SessionVar[Map[String, List[FormBuilderLocator[_]]]](Map()) } +case class SettingWrittenAfterWrite(settingName: String, origStackTrace: LiftRules.StackTrace) extends Exception + +object LiftRulesSetting { + implicit def extractor[T](s: LiftRulesSetting[T]): T = s.get +} + +class LiftRulesSetting[T](val name: String, val default: T) extends Serializable { + private [this] var v: T = default + private [this] var lastRead: Option[Exception] = None + + def := (value: T): Unit = { + lastRead.foreach { e => + val e2 = SettingWrittenAfterWrite(name, e.getStackTrace) + LiftRules.settingWriteAfterReadFunc.get.apply(e2) + } + + v = value + } + + def get: T = { + lastRead = Some(new Exception) + + v + } +} From 2074654b7e6571215abc5883dbf93781a9bb3b47 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 16 Apr 2016 11:05:40 -0400 Subject: [PATCH 1617/1949] Add a spec demonstrating the issue. --- .../test/scala/net/liftweb/http/CometActorSpec.scala | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/web/webkit/src/test/scala/net/liftweb/http/CometActorSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/CometActorSpec.scala index 6645975bfa..9313e902bc 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/CometActorSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/CometActorSpec.scala @@ -92,5 +92,17 @@ object CometActorSpec extends Specification { redirectUri must beMatching("^[^?]+\\?F[^=]+=_$".r) } } + + "be able to invoke destroySession without causing an NPE" in { + case object BoomSession + val comet = new SpecCometActor { + override def lowPriority = { + case BoomSession => + S.session.foreach(_.destroySession) + } + } + + (comet ! BoomSession) must not(throwAn[Exception]) + } } } From c4763ebeec52787a188a35162d76b31811c554f2 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sun, 23 Jul 2017 13:55:42 -0400 Subject: [PATCH 1618/1949] Validate that destroying a session from a comet works --- .../scala/net/liftweb/http/CometActorSpec.scala | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/web/webkit/src/test/scala/net/liftweb/http/CometActorSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/CometActorSpec.scala index 9313e902bc..3af465b68e 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/CometActorSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/CometActorSpec.scala @@ -94,15 +94,27 @@ object CometActorSpec extends Specification { } "be able to invoke destroySession without causing an NPE" in { + var didRun = false + var didThrow = false + case object BoomSession val comet = new SpecCometActor { override def lowPriority = { case BoomSession => - S.session.foreach(_.destroySession) + try { + didRun = true + S.session.foreach(_.destroySession) + } catch { + case e: Exception => + didThrow = true + } } } - (comet ! BoomSession) must not(throwAn[Exception]) + comet ! BoomSession + + didRun must beTrue + didThrow must beFalse } } } From a71dc4cac41f7e4fb1809df40f2df1a2acadcfe5 Mon Sep 17 00:00:00 2001 From: Joe Barnes Date: Sun, 23 Jul 2017 13:29:10 -0500 Subject: [PATCH 1619/1949] Ironed out implementation of warning developers that a LiftRules setting has been set after it had already been read --- .../scala/net/liftweb/http/LiftRules.scala | 33 ++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index ee2683411d..5f140e0c61 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -1584,9 +1584,9 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { private def logSnippetFailure(sf: SnippetFailure) = logger.info("Snippet Failure: " + sf) - val exceptIfSettingWrittenAfterWrite: SettingWrittenAfterWrite => Unit = e => throw e - val warnIfSettingWrittenAfterWrite: SettingWrittenAfterWrite => Unit = e => logger.warn("LiftRules setting written after a read!", e) - val settingWriteAfterReadFunc = new LiftRulesSetting[SettingWrittenAfterWrite => Unit]("settingWriteAfterReadFunc", exceptIfSettingWrittenAfterWrite) + val throwSettingsException: LiftRulesSettingException => Unit = e => throw e + val warnOnSettingsException: LiftRulesSettingException => Unit = e => logger.warn("LiftRules setting written after a read!", e) + val settingsExceptionFunc = new LiftRulesSetting[LiftRulesSettingException => Unit]("settingsExceptionFunc", throwSettingsException) val joesciiSetting = new LiftRulesSetting[Boolean]("joesciiSetting", true) @@ -2303,27 +2303,44 @@ trait FormVendor { private object requestForms extends SessionVar[Map[String, List[FormBuilderLocator[_]]]](Map()) } -case class SettingWrittenAfterWrite(settingName: String, origStackTrace: LiftRules.StackTrace) extends Exception +case class LiftRulesSettingException(settingName: String, stackTrace: LiftRules.StackTrace, message: String) extends Exception(message) { + setStackTrace(stackTrace) +} object LiftRulesSetting { implicit def extractor[T](s: LiftRulesSetting[T]): T = s.get } -class LiftRulesSetting[T](val name: String, val default: T) extends Serializable { +class LiftRulesSetting[T](val name: String, val default: T) extends Loggable with Serializable { private [this] var v: T = default private [this] var lastRead: Option[Exception] = None + private [this] def writeAfterReadMessage = + s"LiftRules.$name was set AFTER already being read! " + + s"Your Lift application may be booting into an inconsistent state. " + + s"Review the stacktrace below to see where the value was last read. " + + s"Consider re-ordering your settings in boot. " + def := (value: T): Unit = { + // TODO warn if set more than once with different values? + + if(LiftRules.doneBoot) logger.warn(s"LiftRules.$name set after Lift finished booting.") + + // If lastRead is defined, then we need to let the developer know the bad news. + // TODO: Skip if the value is the same? lastRead.foreach { e => - val e2 = SettingWrittenAfterWrite(name, e.getStackTrace) - LiftRules.settingWriteAfterReadFunc.get.apply(e2) + val st = e.getStackTrace.dropWhile(_.getClassName contains "LiftRulesSetting") + val e2 = LiftRulesSettingException(name, st, writeAfterReadMessage) + LiftRules.settingsExceptionFunc.get.apply(e2) } v = value } def get: T = { - lastRead = Some(new Exception) + // Save an exception if we are still booting, which we will use to produce a stack trace of where + // this setting was last read. + if(!LiftRules.doneBoot) lastRead = Some(new Exception) v } From 09a71ce77803d0d83a1cb7f70de676de4eddee99 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sun, 23 Jul 2017 14:42:03 -0400 Subject: [PATCH 1620/1949] Formalize support policy per ML discussion --- CONTRIBUTING.md | 5 ++++ SUPPORT.md | 67 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 SUPPORT.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2f1fb20a88..d7ed209bfd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,6 +2,8 @@ Ticket and Pull Request Guidelines ================================== The policy on issues is: +* Issues should be actionable items of work for the Committers or an outside contributor to + execute on, and should describe an issue in a supported version of Lift. * Non-committers should discuss issues on the Lift mailing list before opening a issue. * All non-committer issues should have a link back to the mailing list @@ -18,6 +20,7 @@ The policy on issues is: We will accept pull requests into the Lift codebase if the pull requests meet all of the following criteria: +* The request is for a currently supported version of Lift. (See [SUPPORT.md][supfile].) * The request handles an issue that has been discussed on the Lift mailing list and whose solution has been requested by the committers (and in general adheres to the spirit of the issue guidelines above). @@ -29,3 +32,5 @@ pull request, the Lift committers are taking responsibility for maintaining that and there are many reasons why this might not be desirable for certain features. We will strive to communicate clearly why pull requests are not accepted if we decide not to accept them. + +[supfile]: https://github.com/lift/framework/blob/master/SUPPORT.md diff --git a/SUPPORT.md b/SUPPORT.md new file mode 100644 index 0000000000..87b633aeae --- /dev/null +++ b/SUPPORT.md @@ -0,0 +1,67 @@ +# The Lift Framework Support Policy + +Ongoing maintenance and development of the Lift Framework is a volunteer effort executed by a +passionate group of Lift supporters known a the Lift Committers. With the assistance of +outside contributors, the Lift Committers work to ensure that with each release of Lift we provide +a selection of new features and bug fixes. The Lift Committers stand behind and provide ongoing +support for the contributions they make to Lift, and a condition of accepting changes from outside +contributors is that the Committers feel comfortable supporting those changes long term. + +Because maintenance of the Framework is a volunteer effort and all of the Committers have busy +lives with precious free time, they must be intentional with the community support they provide for +Lift. To that end, the Committers have decided to embrace the following support policy for releases +of Lift: + +* Lift ships a new release every six months, with three milestones and at least one Release +Candidate building up to each release. Each normal release results in a bump of the middle number +of the version (e.g. 3.1.0 to 3.2.0). +* Brand new features are always a part of new releases. Breaking changes will only ship as a part +of a major release, which results in a bump of the first number of the version (e.g. 2.6.0 to 3.0.0). +* Bug fixes and minor performance improvements will be shipped for releases for 18 months after +their release date. +* Security fixes will be shipped for the current and previous two major releases. + * Due to a change in our release scheme, Lift 2.5 will receive security fixes until the release + of Lift 4.0 and Lift 2.6 will receive security fixes until the release of Lift 5.0. + * Any application using Lift 2.4 or earlier should be upgraded as soon as possible. + * Major releases aren't on a regular schedule, but we will announce and update this policy with + actual dates when major releases become planned. + +As of July 23rd, 2017 the current support status looks like this: + +|Version |Initial Release | Last Release | Current Support Status | +|---------|-----------------|------------------|---------------------------------| +|< 2.5 | | | Not supported | +|2.5 |June 2013 | 2.5.4 (Jan 2016) | Security fixes (until Lift 4.0) | +|2.6 |January 2015 | 2.6.3 (Jan 2016) | Security fixes (until Lift 5.0) | +|3.0 |November 2016 | 3.0.1 (Dec 2016) | Minor fixes (until May 2018) | +|3.1 |July 2017 | 3.1.0 (Jul 2017) | Minor fixes (until Jan 2019) | + +Per this support policy please ensure you're using a currently supported version of Lift before: + +* Opening a [Mailing List][ml] thread asking for help from the committers. +* Filing an issue in GitHub. +* Opening a Pull Request intended to patch an older version of Lift. + +## Common issues + +We're sure that this support policy won't meet every organization's needs. To that end, the +committers wanted to address a few specific problems that may come up as a result of this support +strategy. + +### I need help upgrading Lift + +If you're using an ancient version of Lift, you may find that a lot has changed and upgrading has +become quite challenging. If you're in this position, please reach out to the community on the +[Lift Mailing List][ml]. We're happy to answer quick questions about changes we've made. + +If your needs are more involved, you may want to consider hiring a member of the Lift community +or a Lift Committer to help you upgrade your application. A number of members in the community do +part-time consulting off and on and several more consult full time as their day job. + +### I need to plan for longer-term support of a Lift application + +For some organizations 18 months just isn't a sufficient support window. If your organization is in +this situation, please consider reaching out on the [Mailing List][ml]. You may be able to hire a +Lift community member to help you provide longer-term support for your application. + +[ml]: https://groups.google.com/forum/#!forum/liftweb From 43fa4c0c96a6247e94baf8be5ddf7461c74ef7cb Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sun, 23 Jul 2017 14:53:37 -0400 Subject: [PATCH 1621/1949] Reworking the README for support changes --- CONTRIBUTING.md | 2 +- README.md | 106 +++++++++++++++++++++++++----------------------- 2 files changed, 56 insertions(+), 52 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d7ed209bfd..a55ac86cb6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,7 +24,7 @@ all of the following criteria: * The request handles an issue that has been discussed on the Lift mailing list and whose solution has been requested by the committers (and in general adheres to the spirit of the issue guidelines above). -* The request includes a signature at the bottom of the /contributors.md file. +* The contributor's signature is already in or added to /contributors.md file. Note that only Lift committers can merge a pull request, and accepting or rejecting a pull request is entirely at the discretion of the Lift committers. By merging a diff --git a/README.md b/README.md index 1bdb229cad..169ce49d6e 100644 --- a/README.md +++ b/README.md @@ -13,26 +13,6 @@ Lift applications are: Because Lift applications are written in [Scala](http://www.scala-lang.org), an elegant JVM language, you can still use your favorite Java libraries and deploy to your favorite Servlet Container and app server. Use the code you've already written and deploy to the container you've already configured! -## Issues - -Issues on the Lift GitHub project are intended to describe actionable, -already-discussed items. Committers on the project may open issues for -themselves at any time, but non-committers should visit the [Lift mailing -list](https://groups.google.com/forum/#!forum/liftweb) and start a discussion -for any issue that they wish to open. - -## Pull Requests - -We will accept pull requests into the [Lift codebase](https://github.com/lift) -if the pull requests meet the following criteria: - -* The request handles an issue that has been discussed on the [Lift mailing list](http://groups.google.com/forum/#!forum/liftweb) - and whose solution has been requested (and in general adheres to the spirit of - the issue guidelines outlined in [CONTRIBUTING.md](https://github.com/lift/framework/blob/master/CONTRIBUTING.md)). -* The request includes a signature at the bottom of the `/contributors.md` file. - -For more details, see [CONTRIBUTING.md](https://github.com/lift/framework/blob/master/CONTRIBUTING.md). - ## Getting Started **Compatibility note:** @@ -41,30 +21,32 @@ you'll need to use Lift 2.6 until you can upgrade your Java installation. You can create a new Lift project using your favorite build system by adding Lift as a dependency: -#### sbt +### With sbt We recommend using the latest sbt version, which is currently 0.13.15, but anything 0.13.6+ will work with the instructions below. Create or update your `project/plugins.sbt` file with the `xsbt-web-plugin`: - addSbtPlugin("com.github.siasia" %% "xsbt-web-plugin" % "3.0.2") +```scala +addSbtPlugin("com.github.siasia" %% "xsbt-web-plugin" % "3.0.2") +``` Then, add the plugin and Lift to your `build.sbt` file: - enablePlugins(JettyPlugin) +```scala +enablePlugins(JettyPlugin) - libraryDependencies ++= { - val liftVersion = "3.1.0" - Seq( - "net.liftweb" %% "lift-webkit" % liftVersion % "compile", - "ch.qos.logback" % "logback-classic" % "1.1.3" - ) - } +libraryDependencies ++= { + val liftVersion = "3.1.0" + Seq( + "net.liftweb" %% "lift-webkit" % liftVersion % "compile", + "ch.qos.logback" % "logback-classic" % "1.2.5" + ) +} +``` -You can [learn more on the cookbook](http://cookbook.liftweb.net/#LiftFromScratch). - -#### Maven: +### With Maven You can use one of the several archetypes -- `lift-archetype-blank`, `lift-archetype-basic`, `lift-archetype-jpa-basic` -- to generate a new Lift project. You must set `archetypeRepository` and `remoteRepositories` to `http://scala-tools.org/repo-releases` or `http://scala-tools.org/repo-snapshots`, depending on whether you are using a release or the latest SNAPSHOT. @@ -79,27 +61,52 @@ Or, you can add Lift to your `pom.xml` like so: Where `${scala.version}` is `2.11` or `2.12`. Individual patch releases of the Scala compiler (e.g. 2.12.2) are binary compatible with everything in their release series, so you only need the first two version parts. -You can [learn more on the wiki](http://www.assembla.com/wiki/show/liftweb/Using_Maven). +You can learn more about Maven integration [on the wiki](http://www.assembla.com/wiki/show/liftweb/Using_Maven). -## Project Organization +### Learning Lift -The Lift Framework is divided into several components that are published independently. This organization enables you to use just the elements of Lift necessary for your project and no more. +There are lots of resources out there for learning Lift. Some of our favorites include: -### This Repository +* [The Lift Cookbook](http://cookbook.liftweb.net/) +* [Simply Lift](http://simply.liftweb.net) -This repository, `framework`, contains the following components: +If you've found one that you particularly enjoy, please open a PR to update this README! -#### core +## Issues & Pull Requests -Core elements used by Lift projects. If you wish to reuse some of Lift's helpers and constructs, such as `Box`, this component may be all you need. However, a web application will most likely require one or more of Lift's components. +Per our [contributing guidelines][contribfile], Issues on the Lift GitHub project are intended to +describe actionable, already-discussed items. Committers on the project may open issues for +themselves at any time, but non-committers should visit the [Lift mailing +list](https://groups.google.com/forum/#!forum/liftweb) and start a discussion +for any issue that they wish to open. + +We will accept issues and pull requests into the Lift codebase if the pull requests meet the following criteria: + +* The request is for a [supported version of Lift][supfile] +* The request adheres to our [contributing guidelines][contribfile], including having been discussed + on the Mailing List if its from a non-committer. + +[contribfile]: https://github.com/lift/framework/blob/master/CONTRIBUTING.md +[sigfile]: https://github.com/lift/framework/blob/master/contributors.md + +## Project Organization -#### web +The Lift Framework is divided into several components that are published independently. This organization enables you to use just the elements of Lift necessary for your project and no more. -This component includes all of Lift's core HTTP and web handling. Including `lift-webkit` in your build process should be sufficient for basic applications and will include `lift-core` as a transitive dependency. +### This Repository -#### persistence +This repository, `framework`, contains the following components: -This component includes Mapper and Record, Lift's two ORMs. While you needn't use either and can use the ORM of your choice, Mapper and Record integrate nicely with Lift's idioms. Mapper is an ORM for relational databases, while Record is a broader ORM with support for both SQL databases and NoSQL datastores. +* **core:** Core elements used by Lift projects. If you wish to reuse some of Lift's helpers and +constructs, such as `Box`, this component may be all you need. However, a web application will most +likely require one or more of Lift's components. +* **web:** This component includes all of Lift's core HTTP and web handling. Including `lift-webkit` +in your build process should be sufficient for basic applications and will include `lift-core` as a +transitive dependency. +* **persistence:** This component includes Mapper and Record, Lift's two ORMs. While you needn't use +either and can use the ORM of your choice, Mapper and Record integrate nicely with Lift's idioms. +Mapper is an ORM for relational databases, while Record is a broader ORM with support for both SQL +databases and NoSQL datastores. ### Other Repostories @@ -107,9 +114,10 @@ There are a variety of other repositories available on the Lift GitHub page. Whi #### modules -The [modules](https://github.com/liftmodules) repository contains the many add-on modules that are part of the Lift project. If you don't find a module you need here, consider [creating a module](http://www.assembla.com/spaces/liftweb/wiki/Modules) and sharing it with the community. - -Please note that the modules project does accept pull requests. +The [modules](https://github.com/liftmodules) organization contains the many add-on modules that are +part of the Lift project. If you don't find a module you need here, consider +[creating a module](http://www.assembla.com/spaces/liftweb/wiki/Modules) and sharing it with the +community. #### examples @@ -145,10 +153,6 @@ The Lift wiki is hosted on Assembla and can be found at [http://www.assembla.com The ScalaDocs for each release of Lift, in additional to the actual JARs, are available on the Liftweb.net site. You can access the source code–based documentation for releases via the site's homepage or by navigating directly to the URL for the specific release. For instance, the Lift 3.0 release can be accessed at [http://liftweb.net/api/31/api/](http://liftweb.net/api/31/api/). -### Cookbook - -You can find up-to-date information on the [Lift Cookbook](http://cookbook.liftweb.net/) - ## License Lift is open source software released under the **Apache 2.0 license**. From 5b12875f539ea82b8622d45c7797184b9aae9451 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sun, 23 Jul 2017 15:20:37 -0400 Subject: [PATCH 1622/1949] A few minor revisions per review --- SUPPORT.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/SUPPORT.md b/SUPPORT.md index 87b633aeae..1f6e54d046 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -12,13 +12,14 @@ lives with precious free time, they must be intentional with the community suppo Lift. To that end, the Committers have decided to embrace the following support policy for releases of Lift: -* Lift ships a new release every six months, with three milestones and at least one Release -Candidate building up to each release. Each normal release results in a bump of the middle number -of the version (e.g. 3.1.0 to 3.2.0). -* Brand new features are always a part of new releases. Breaking changes will only ship as a part -of a major release, which results in a bump of the first number of the version (e.g. 2.6.0 to 3.0.0). -* Bug fixes and minor performance improvements will be shipped for releases for 18 months after -their release date. +* Lift ships a new release every six months, with three milestones and at least one release + candidate building up to each release. Each normal release results in a bump of the middle number + of the version (e.g. 3.1.0 to 3.2.0). +* Brand new features are always a part of new releases. API-incompatible changes will only ship as a + part of a major release, which results in a bump of the first number of the version (e.g. 2.6.0 to + 3.0.0). +* Bug fixes and minor performance improvements can, by consensus of the Committers, be shipped for + releases for 18 months after their release date. * Security fixes will be shipped for the current and previous two major releases. * Due to a change in our release scheme, Lift 2.5 will receive security fixes until the release of Lift 4.0 and Lift 2.6 will receive security fixes until the release of Lift 5.0. @@ -46,7 +47,7 @@ Per this support policy please ensure you're using a currently supported version We're sure that this support policy won't meet every organization's needs. To that end, the committers wanted to address a few specific problems that may come up as a result of this support -strategy. +strategy: ### I need help upgrading Lift From 3fc0603c867d812972b02b29e04f091abf0d8164 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sun, 23 Jul 2017 15:21:31 -0400 Subject: [PATCH 1623/1949] We're known "as" not known "a" --- SUPPORT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SUPPORT.md b/SUPPORT.md index 1f6e54d046..fe7a475446 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -1,7 +1,7 @@ # The Lift Framework Support Policy Ongoing maintenance and development of the Lift Framework is a volunteer effort executed by a -passionate group of Lift supporters known a the Lift Committers. With the assistance of +passionate group of Lift supporters known as the Lift Committers. With the assistance of outside contributors, the Lift Committers work to ensure that with each release of Lift we provide a selection of new features and bug fixes. The Lift Committers stand behind and provide ongoing support for the contributions they make to Lift, and a condition of accepting changes from outside From 33c979b5131702731ce9ee00824f49820238d20d Mon Sep 17 00:00:00 2001 From: karma4u101 Date: Tue, 25 Jul 2017 16:01:35 +0200 Subject: [PATCH 1624/1949] Fixing stackoverflow in typechecker while building tests --- liftsh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/liftsh b/liftsh index 8b47803def..f96e94f03f 100755 --- a/liftsh +++ b/liftsh @@ -21,7 +21,7 @@ if test -f ~/.liftsh.config; then fi # Internal options, always specified -INTERNAL_OPTS="-Dfile.encoding=UTF-8 -Xmx1768m -noverify -XX:ReservedCodeCacheSize=296m -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC -XX:MaxPermSize=812m" +INTERNAL_OPTS="-Dfile.encoding=UTF-8 -Xss256m -Xmx2048m -noverify -XX:ReservedCodeCacheSize=296m -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC -XX:MaxPermSize=812m" # Add 64bit specific option exec java -version 2>&1 | grep -q "64-Bit" && INTERNAL_OPTS="${INTERNAL_OPTS} -XX:+UseCompressedOops -XX:ReservedCodeCacheSize=328m" From 0c3e066e1881d544cf9a1f5e08bbd0d01cc1e94c Mon Sep 17 00:00:00 2001 From: Christopher Webster Date: Tue, 25 Jul 2017 17:45:54 -0700 Subject: [PATCH 1625/1949] This change eliminates the use of Range in the appendEscapeString method as this imposes somewhere between a 12-18% performance penalty. There are several causes for this, but the main factor is the additional object creation caused by initializing a range object. This penalty can be avoided by using forEach directly on the input string. --- core/json/src/main/scala/net/liftweb/json/JsonAST.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/json/src/main/scala/net/liftweb/json/JsonAST.scala b/core/json/src/main/scala/net/liftweb/json/JsonAST.scala index b766e122f1..33c03866ee 100644 --- a/core/json/src/main/scala/net/liftweb/json/JsonAST.scala +++ b/core/json/src/main/scala/net/liftweb/json/JsonAST.scala @@ -817,8 +817,7 @@ object JsonAST { } private def appendEscapedString(buf: Appendable, s: String, settings: RenderSettings) { - for (i <- 0 until s.length) { - val c = s.charAt(i) + s.foreach { c => val strReplacement = c match { case '"' => "\\\"" case '\\' => "\\\\" From afd4f7bcce8b7cb6a7dc9098e529c8b3942e09ca Mon Sep 17 00:00:00 2001 From: Joe Barnes Date: Wed, 26 Jul 2017 09:17:48 -0500 Subject: [PATCH 1626/1949] Fleshed out more warnings and such for settings --- .../scala/net/liftweb/http/LiftRules.scala | 59 +++++++++++++++---- 1 file changed, 47 insertions(+), 12 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 5f140e0c61..8813634f3c 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -1585,8 +1585,8 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { private def logSnippetFailure(sf: SnippetFailure) = logger.info("Snippet Failure: " + sf) val throwSettingsException: LiftRulesSettingException => Unit = e => throw e - val warnOnSettingsException: LiftRulesSettingException => Unit = e => logger.warn("LiftRules setting written after a read!", e) - val settingsExceptionFunc = new LiftRulesSetting[LiftRulesSettingException => Unit]("settingsExceptionFunc", throwSettingsException) + val warnOnSettingsException: LiftRulesSettingException => Unit = e => logger.warn("LiftRules setting safety violation!!!", e) + val settingsExceptionFunc = new LiftRulesSetting[LiftRulesSettingException => Unit]("settingsExceptionFunc", warnOnSettingsException) val joesciiSetting = new LiftRulesSetting[Boolean]("joesciiSetting", true) @@ -2303,16 +2303,29 @@ trait FormVendor { private object requestForms extends SessionVar[Map[String, List[FormBuilderLocator[_]]]](Map()) } -case class LiftRulesSettingException(settingName: String, stackTrace: LiftRules.StackTrace, message: String) extends Exception(message) { +abstract class LiftRulesSettingException(settingName: String, stackTrace: LiftRules.StackTrace, message: String) extends Exception(message) { setStackTrace(stackTrace) } +case class LiftRulesSettingWrittenAfterReadException(settingName: String, stackTrace: LiftRules.StackTrace, message: String) + extends LiftRulesSettingException(settingName, stackTrace, message) + +case class LiftRulesSettingWrittenAfterBootException(settingName: String, stackTrace: LiftRules.StackTrace, message: String) + extends LiftRulesSettingException(settingName, stackTrace, message) + +case class LiftRulesSettingWrittenTwiceException(settingName: String, stackTrace: LiftRules.StackTrace, message: String) + extends LiftRulesSettingException(settingName, stackTrace, message) + + object LiftRulesSetting { implicit def extractor[T](s: LiftRulesSetting[T]): T = s.get } -class LiftRulesSetting[T](val name: String, val default: T) extends Loggable with Serializable { +class LiftRulesSetting[T](val name: String, val default: T) extends Serializable { + import LiftRules.StackTrace + private [this] var v: T = default + private [this] var lastSet: Option[Exception] = None private [this] var lastRead: Option[Exception] = None private [this] def writeAfterReadMessage = @@ -2321,27 +2334,49 @@ class LiftRulesSetting[T](val name: String, val default: T) extends Loggable wit s"Review the stacktrace below to see where the value was last read. " + s"Consider re-ordering your settings in boot. " + private [this] def writeAfterBootMessage = + s"LiftRules.$name set after Lift finished booting. " + + s"This could potentially cause your Lift application to run in an inconsistent state. " + + s"Review the stacktrace below to see where settings are being changed after boot time. " + + private [this] def writtenTwiceMessage1(newVal: T) = + s"LiftRules.$name was set to $v then later set to $newVal. " + + s"This could potentially cause your Lift application to run in an inconsistent state. " + + s"Review the stacktrace below to see where LiftRules.$name was first set to $v. " + + private [this] def writtenTwiceMessage2(newVal: T) = + s"Review the stacktrace below to see where LiftRules.$name was later set to $newVal. " + + private [this] def trimmedStackTrace(t: Throwable): StackTrace = + t.getStackTrace.dropWhile(_.getClassName contains "LiftRulesSetting") + + def := (value: T): Unit = { - // TODO warn if set more than once with different values? + lastSet.collect { case e if v != value => + val e1 = LiftRulesSettingWrittenTwiceException(name, trimmedStackTrace(e), writtenTwiceMessage1(value)) + LiftRules.settingsExceptionFunc.get.apply(e1) - if(LiftRules.doneBoot) logger.warn(s"LiftRules.$name set after Lift finished booting.") + val e2 = LiftRulesSettingWrittenTwiceException(name, trimmedStackTrace(e), writtenTwiceMessage2(value)) + LiftRules.settingsExceptionFunc.get.apply(e2) + } + + if(LiftRules.doneBoot) { + val e = LiftRulesSettingWrittenAfterBootException(name, trimmedStackTrace(new Exception), writeAfterBootMessage) + LiftRules.settingsExceptionFunc.get.apply(e) + } - // If lastRead is defined, then we need to let the developer know the bad news. // TODO: Skip if the value is the same? lastRead.foreach { e => - val st = e.getStackTrace.dropWhile(_.getClassName contains "LiftRulesSetting") - val e2 = LiftRulesSettingException(name, st, writeAfterReadMessage) + val e2 = LiftRulesSettingWrittenAfterReadException(name, trimmedStackTrace(e), writeAfterReadMessage) LiftRules.settingsExceptionFunc.get.apply(e2) } + lastSet = Some(new Exception) v = value } def get: T = { - // Save an exception if we are still booting, which we will use to produce a stack trace of where - // this setting was last read. if(!LiftRules.doneBoot) lastRead = Some(new Exception) - v } } From a1311f4eb876cd200cbcbe8597c8f21449e5e42a Mon Sep 17 00:00:00 2001 From: Joe Barnes Date: Wed, 26 Jul 2017 13:21:55 -0500 Subject: [PATCH 1627/1949] Extending LiftRulesSetting from the same heirarchy as SessionVar, etc for a consistent API --- .../src/main/scala/net/liftweb/http/LiftRules.scala | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 8813634f3c..328fe79ec1 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -2317,11 +2317,7 @@ case class LiftRulesSettingWrittenTwiceException(settingName: String, stackTrace extends LiftRulesSettingException(settingName, stackTrace, message) -object LiftRulesSetting { - implicit def extractor[T](s: LiftRulesSetting[T]): T = s.get -} - -class LiftRulesSetting[T](val name: String, val default: T) extends Serializable { +class LiftRulesSetting[T](val name: String, val default: T) extends LiftValue[T] with HasCalcDefaultValue[T] { import LiftRules.StackTrace private [this] var v: T = default @@ -2351,7 +2347,7 @@ class LiftRulesSetting[T](val name: String, val default: T) extends Serializable t.getStackTrace.dropWhile(_.getClassName contains "LiftRulesSetting") - def := (value: T): Unit = { + override def set(value: T): T = { lastSet.collect { case e if v != value => val e1 = LiftRulesSettingWrittenTwiceException(name, trimmedStackTrace(e), writtenTwiceMessage1(value)) LiftRules.settingsExceptionFunc.get.apply(e1) @@ -2373,10 +2369,13 @@ class LiftRulesSetting[T](val name: String, val default: T) extends Serializable lastSet = Some(new Exception) v = value + v } - def get: T = { + override def get: T = { if(!LiftRules.doneBoot) lastRead = Some(new Exception) v } + + override protected def calcDefaultValue: T = default } From b07b3ed76f04587be69b85a996ad52a391ce3741 Mon Sep 17 00:00:00 2001 From: Joe Barnes Date: Wed, 26 Jul 2017 13:23:11 -0500 Subject: [PATCH 1628/1949] Changing the default behavior for LiftRulesSetting violations to throw an Exception since it will only be applied to new settings (for now) --- web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 328fe79ec1..af9e68d498 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -1586,7 +1586,7 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { val throwSettingsException: LiftRulesSettingException => Unit = e => throw e val warnOnSettingsException: LiftRulesSettingException => Unit = e => logger.warn("LiftRules setting safety violation!!!", e) - val settingsExceptionFunc = new LiftRulesSetting[LiftRulesSettingException => Unit]("settingsExceptionFunc", warnOnSettingsException) + val settingsExceptionFunc = new LiftRulesSetting[LiftRulesSettingException => Unit]("settingsExceptionFunc", throwSettingsException) val joesciiSetting = new LiftRulesSetting[Boolean]("joesciiSetting", true) From fdb2b27eb75cd4427d85894f48be143ffcb4616b Mon Sep 17 00:00:00 2001 From: Joe Barnes Date: Wed, 26 Jul 2017 13:24:20 -0500 Subject: [PATCH 1629/1949] Removing my test setting --- web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala | 2 -- 1 file changed, 2 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index af9e68d498..607ce85748 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -1588,8 +1588,6 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { val warnOnSettingsException: LiftRulesSettingException => Unit = e => logger.warn("LiftRules setting safety violation!!!", e) val settingsExceptionFunc = new LiftRulesSetting[LiftRulesSettingException => Unit]("settingsExceptionFunc", throwSettingsException) - val joesciiSetting = new LiftRulesSetting[Boolean]("joesciiSetting", true) - /** * Set to false if you do not want ajax/comet requests that are not * associated with a session to call their respective session From 4e3a038cbd13c8128a96924c781fa50e9fa4ce2f Mon Sep 17 00:00:00 2001 From: Joe Barnes Date: Wed, 26 Jul 2017 13:52:37 -0500 Subject: [PATCH 1630/1949] Updating the stacktrace trim function now that we extend LiftValue --- web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 607ce85748..a939560765 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -2341,8 +2341,9 @@ class LiftRulesSetting[T](val name: String, val default: T) extends LiftValue[T] private [this] def writtenTwiceMessage2(newVal: T) = s"Review the stacktrace below to see where LiftRules.$name was later set to $newVal. " + private [this] val toIgnore = Set("LiftRulesSetting", "LiftValue") private [this] def trimmedStackTrace(t: Throwable): StackTrace = - t.getStackTrace.dropWhile(_.getClassName contains "LiftRulesSetting") + t.getStackTrace.dropWhile(e => toIgnore.find(e.getClassName contains _).isDefined) override def set(value: T): T = { From 50c08dfa80a363db8e98a853372ea6ada9d1139b Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Thu, 3 Aug 2017 22:48:43 -0400 Subject: [PATCH 1631/1949] Address some outstanding issues with README changes --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 169ce49d6e..df38ca877c 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,7 @@ This repository, `framework`, contains the following components: * **core:** Core elements used by Lift projects. If you wish to reuse some of Lift's helpers and constructs, such as `Box`, this component may be all you need. However, a web application will most -likely require one or more of Lift's components. +likely require one or more of Lift's other components. * **web:** This component includes all of Lift's core HTTP and web handling. Including `lift-webkit` in your build process should be sufficient for basic applications and will include `lift-core` as a transitive dependency. @@ -114,8 +114,9 @@ There are a variety of other repositories available on the Lift GitHub page. Whi #### modules -The [modules](https://github.com/liftmodules) organization contains the many add-on modules that are -part of the Lift project. If you don't find a module you need here, consider +The [modules](https://github.com/liftmodules) organization contains some of the many add-on modules +that are part of the Lift project. If you don't find a module you need here, consider +looking for it on the [Lift modules directory](https://liftweb.net/lift_modules) or [creating a module](http://www.assembla.com/spaces/liftweb/wiki/Modules) and sharing it with the community. From 52e6f17dcf207f460ae83c729a06ec35a6d0ef71 Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sun, 6 Aug 2017 09:57:44 +0530 Subject: [PATCH 1632/1949] Implement `flip` to take a partial-function. Remove `collectFailure` as `flip` covers the case it was written to handle --- .../main/scala/net/liftweb/common/Box.scala | 33 +++++++--------- .../scala/net/liftweb/common/BoxSpec.scala | 38 ++++++++----------- 2 files changed, 29 insertions(+), 42 deletions(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index e5c5779478..8160f5ee25 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -814,37 +814,30 @@ sealed abstract class Box[+A] extends Product with Serializable{ } /** - * If this box is a `Failure` and if `pf` is defined for this `Failure` instance, returns - * a `Full` box containing the result of applying `pf` to this `Failure`, otherwise, returns - * `Empty`. + * Returns a `Full` box containing the results of applying `flipFn` to this box if it is a `Failure`, + * `ParamFailure` or `Empty`, and `flipFn` is defined for it. Returns `Empty` if this box is `Full`. + * In other words, it "flips" the full/empty status of this Box. * * @example {{{ * // Returns Full("alternative") because the partial function covers the case. - * Failure("error") collectFailure { case Failure("error", Empty, Empty) => "alternative" } + * Failure("error") flip { case Failure("error", Empty, Empty) => "alternative" } * * // Returns Empty because the partial function doesn't cover the case. - * Failure("another-error") collectFailure { case Failure("error", Empty, Empty) => "alternative" } + * Failure("another-error") flip { case Failure("error", Empty, Empty) => "alternative" } + * + * // Returns Full("alternative") for an Empty box since `partialFn` is defined for Empty + * Empty flip { case Empty => "alternative" } * - * // Returns Empty for an Empty box - * Empty collectFailure { case _ => Failure("error") } + * // Returns Empty because the partial function is not defined for Empty + * Empty flip { case Failure("error", Empty, Empty) => "alternative" } * * // Returns Empty for a Full box - * Full(1) collectFailure { case _ => Failure("error") } + * Full(1) flip { case _ => Failure("error") } * * }}} */ - final def collectFailure[B](pf: PartialFunction[Failure, B]): Box[B] = this match { - case f: Failure if pf.isDefinedAt(f) => Full(pf(f)) - case _ => Empty - } - - /** - * Returns a `Full` box containing the results of applying `flipFn` to this box if it is a `Failure`, - * `ParamFailure` or `Empty`. Returns `Empty` if this box is `Full`. In other words, it "flips" the - * full/empty status of this Box. - */ - def flip[B](flipFn: EmptyBox => B): Box[B] = this match { - case e: EmptyBox => Full(flipFn(e)) + def flip[B](flipFn: PartialFunction[EmptyBox, B]): Box[B] = this match { + case e: EmptyBox if flipFn.isDefinedAt(e) => Full(flipFn(e)) case _ => Empty } } diff --git a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala index 0d8ae0dbc0..bf797feba4 100644 --- a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala +++ b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala @@ -164,11 +164,8 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { Full("Hermione") collect { case "Albus" => "Dumbledore"} must beEmpty } } - "define a 'collectFailure' method returning Empty" in { - Full(1) collectFailure { case _ => Failure("error") } must_== Empty - } "define a 'flip' method returning Empty" in { - Full(1) flip { _ => "No data found" } mustEqual Empty + Full(1) flip { case _ => "alternative" } must_== Empty } "define an 'elements' method returning an iterator containing its value" in { Full(1).elements.next must_== 1 @@ -323,14 +320,17 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { "define a 'collect' method returning Empty" in { Empty collect { case _ => "Some Value" } must beEmpty } - "define a 'collectFailure' method returning Empty" in { - Empty collectFailure { case _ => Failure("error") } must_== Empty - } - "define a 'flip' method returning a Full box" in { - Empty flip { - case Empty => "flipped-empty" - case _ => "flipped-failure" - } mustEqual Full("flipped-empty") + "define a 'flip' method that takes a PartialFunction to transform this Empty box into something else" in { + "If the partial-function is defined for Empty, returns a full box containing the result of applying the partial function to it" in { + Empty flip { case Empty => "Return Of The Jedi" } must_== Full("Return Of The Jedi") + Empty flip { + case Failure("The Phantom Menace", Empty, Empty) => "Attack" + case Empty => "Return Of The Jedi" + } must_== Full("Return Of The Jedi") + } + "If the partial-function is not defined for Empty, returns Empty" in { + Empty flip { case Failure("The Phantom Menace", Empty, Empty) => "Return Of The Jedi" } must_== Empty + } } "define an 'elements' method returning an empty iterator" in { Empty.elements.hasNext must beFalse @@ -399,21 +399,15 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { "define a 'collect' method returning itself" in { Failure("error", Empty, Empty) collect { case _ => "Some Value" } must_== Failure("error", Empty, Empty) } - "define a 'collectFailure' method that takes a PartialFunction to transform this Failure into something else" in { + "define a 'flip' method that takes a PartialFunction to transform this Failure into something else" in { "If the partial-function is defined for this Failure, returns a full box containing the result of applying the partial function to it" in { - Failure("The Phantom Menace") collectFailure { case Failure("The Phantom Menace", Empty, Empty) => "Return Of The Jedi" } must_== Full("Return Of The Jedi") - Failure("The Phantom Menace") collectFailure { case Failure("The Phantom Menace", Empty, Empty) => Failure("Attack") } must_== Full(Failure("Attack")) + Failure("The Phantom Menace") flip { case Failure("The Phantom Menace", Empty, Empty) => "Return Of The Jedi" } must_== Full("Return Of The Jedi") + Failure("The Phantom Menace") flip { case Failure("The Phantom Menace", Empty, Empty) => Failure("Attack") } must_== Full(Failure("Attack")) } "If the partial-function is not defined for this Failure, returns Empty" in { - Failure("Attack Of The Clones") collectFailure { case Failure("The Phantom Menace", Empty, Empty) => "Return Of The Jedi" } must_== Empty + Failure("Attack Of The Clones") flip { case Failure("The Phantom Menace", Empty, Empty) => "Return Of The Jedi" } must_== Empty } } - "define a 'flip' method returning a Full box" in { - Failure("error", Empty, Empty) flip { - case Empty => "flipped-empty" - case _: Failure => "flipped-failure" - } mustEqual Full("flipped-failure") - } "return itself when asked for its status with the operator ?~" in { Failure("error", Empty, Empty) ?~ "nothing" must_== Failure("error", Empty, Empty) } From 833abfab481f80d3e003f675a3b71137f3cdcc83 Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sun, 6 Aug 2017 10:06:09 +0530 Subject: [PATCH 1633/1949] Remvoed a typo --- core/common/src/main/scala/net/liftweb/common/Box.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index 8160f5ee25..cc0d03b833 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -757,7 +757,7 @@ sealed abstract class Box[+A] extends Product with Serializable{ /** - * If the `Box` is `Fuvall`, apply the transform function `f` on the value `v`; + * If the `Box` is `Full`, apply the transform function `f` on the value `v`; * otherwise, just return the value untransformed. * * The transform function is expected to be a function that will take the From bc2bf2b59232137d1da3c76017e00e356f30f863 Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sun, 6 Aug 2017 10:24:06 +0530 Subject: [PATCH 1634/1949] Point the links to latest (lift 3.1) API docs. Grammatical corrections. --- docs/dependency-injection-liftweb-scala.adoc | 29 ++++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/docs/dependency-injection-liftweb-scala.adoc b/docs/dependency-injection-liftweb-scala.adoc index ea6f8282a6..338e42e156 100644 --- a/docs/dependency-injection-liftweb-scala.adoc +++ b/docs/dependency-injection-liftweb-scala.adoc @@ -9,13 +9,12 @@ Dependency injection is an important topic in the JVM world. With Java, we usually go with Guice or Spring whenever we need it. However, Scala provides some unique advantages that allow many of the -features needed for dependency injection without requiring an entire framework. - -Scala allows the use of http://jonasboner.com/real-world-scala-dependency-injection-di/[cake pattern], which -facilitates composing complex classes out of Scala traits; however, it doesn't provide complete dependency -injection functionality. In particular, it is less helpful in terms of allowing you to make dynamic -choices about which combination of dependencies to vend in a given situation. Lift provides some additional -features that complete the dependency injection puzzle. +features needed for dependency injection without requiring an entire framework. For example, Scala allows +the use of http://jonasboner.com/real-world-scala-dependency-injection-di/[cake pattern], which +facilitates composing complex classes out of Scala traits; however, this by itself is not enough to +provide complete dependency injection functionality. In particular, it is less helpful in terms of +allowing you to make dynamic choices about which combination of dependencies to vend in a given situation. +Lift provides some additional features that complete the dependency injection puzzle. == Why Lift's Dependency Injection @@ -25,20 +24,20 @@ there is a better, more direct way that is less magical. Lift's philosophy is to keep things as simple as possible (and no simpler), and allow full control to the developers over how things work. To paraphrase David Pollak, -"The abstraction turtles have to end somewhere" and Lift's Dependency Injection features end those turtles quite +"The abstraction turtles have to end somewhere", and Lift's dependency injection features end those turtles quite early. == Getting started -Lift's dependency injection is based on two main traits: https://liftweb.net/api/26/api/index.html#net.liftweb.util.Injector[`Injector`] -and https://liftweb.net/api/26/api/index.html#net.liftweb.util.Maker[`Maker`]; however, most of the time, +Lift's dependency injection is based on two main traits: https://liftweb.net/api/31/api/net/liftweb/util/Injector.html[`Injector`] +and https://liftweb.net/api/31/api/net/liftweb/util/Maker.html[`Maker`]; however, most of the time, three higher level elements can be used: -. The https://liftweb.net/api/26/api/index.html#net.liftweb.http.Factory[`Factory`] trait -. The https://github.com/lift/framework/blob/5033c8798d4444f81996199c10ea330770e47fbc/web/webkit/src/main/scala/net/liftweb/http/Factory.scala#L37[`FactoryMaker`], an abstract class inside +. The https://liftweb.net/api/31/api/net/liftweb/http/Factory.html[`Factory`] trait +. The https://liftweb.net/api/31/api/net/liftweb/http/Factory$FactoryMaker.html[`FactoryMaker`], an abstract class inside inside the `Factory` trait. -. https://liftweb.net/api/26/api/index.html#net.liftweb.util.SimpleInjector$Inject[`Inject`], an abstract class within the - `SimpleInjector` trait. +. https://liftweb.net/api/31/api/net/liftweb/util/SimpleInjector$Inject.html[`Inject`], an abstract class within the + https://liftweb.net/api/31/api/net/liftweb/util/SimpleInjector.html[`SimpleInjector`] trait. `FactoryMaker` and `Inject` serve the same purpose, with the former having more features. An important difference between them is related to performance, @@ -328,7 +327,7 @@ class SomeSpec extends ... with DependencyOverrides { == Differences between FactoryMaker and Inject You can also declare your dependencies using -the https://liftweb.net/api/26/api/index.html#net.liftweb.util.SimpleInjector$Inject[`Inject`] class, exactly like the +the https://liftweb.net/api/31/api/net/liftweb/util/SimpleInjector$Inject.html[`Inject`] class, exactly like the `FactoryMaker`. For ex. [source,scala] From 9b264be35282a8594eff42694448148b8de3ff84 Mon Sep 17 00:00:00 2001 From: Joe Barnes Date: Mon, 7 Aug 2017 14:45:26 -0500 Subject: [PATCH 1635/1949] Adding a ContainerSerializer that works for anything extending Serializable --- web/webkit/src/main/scala/net/liftweb/http/Vars.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/web/webkit/src/main/scala/net/liftweb/http/Vars.scala b/web/webkit/src/main/scala/net/liftweb/http/Vars.scala index c22dc8d923..c511438df0 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/Vars.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/Vars.scala @@ -319,6 +319,7 @@ object ContainerSerializer { implicit def listSerializer[T](implicit tc: ContainerSerializer[T]): ContainerSerializer[List[T]] = buildSerializer + implicit def anyRefSerializer[T <: Serializable]: ContainerSerializer[T] = buildSerializer } /** From 501d1d56652bf40a38a0238e9e5cadb9cd38381f Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Thu, 10 Aug 2017 16:12:39 -0400 Subject: [PATCH 1636/1949] Make the servlet session identifier configurable from LiftRules --- .../src/main/scala/net/liftweb/http/LiftRules.scala | 10 +++++++++- .../http/provider/servlet/HTTPServletSession.scala | 3 +-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 0992f6fe30..cc68c9b18f 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -300,6 +300,15 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { */ @volatile var authentication: HttpAuthentication = NoAuthentication + /** + * A session identifier for [[net.liftweb.http.provider.servlet.HTTPServletSession]]. + * + * Under most circumstances, you won't need to change this value. However, in some cases + * containers will be configured with backing datastores that don't play nice with the default + * value. In those cases you can change this string to get those working. + */ + @volatile var servletSessionIdentifier: String = "$lift_magic_session_thingy$" + /** * A function that takes the HTTPSession and the contextPath as parameters * and returns a LiftSession reference. This can be used in cases subclassing @@ -2294,4 +2303,3 @@ trait FormVendor { private object sessionForms extends SessionVar[Map[String, List[FormBuilderLocator[_]]]](Map()) private object requestForms extends SessionVar[Map[String, List[FormBuilderLocator[_]]]](Map()) } - diff --git a/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPServletSession.scala b/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPServletSession.scala index 2981442d0e..0feb3650ac 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPServletSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPServletSession.scala @@ -24,7 +24,7 @@ import net.liftweb.common._ import net.liftweb.util._ class HTTPServletSession(session: HttpSession) extends HTTPSession { - private val LiftMagicID = "$lift_magic_session_thingy$" + private[this] lazy val LiftMagicID = LiftRules.servletSessionIdentifier def sessionId: String = session.getId @@ -72,4 +72,3 @@ case class SessionToServletBridge(uniqueId: String) extends HttpSessionBindingLi } } - From 5108bc2cd6f4c8e91516b737c538bbe62eb48c08 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Thu, 10 Aug 2017 16:13:09 -0400 Subject: [PATCH 1637/1949] Clean up the naming of the identifier --- .../liftweb/http/provider/servlet/HTTPServletSession.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPServletSession.scala b/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPServletSession.scala index 0feb3650ac..f98f344790 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPServletSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPServletSession.scala @@ -24,13 +24,13 @@ import net.liftweb.common._ import net.liftweb.util._ class HTTPServletSession(session: HttpSession) extends HTTPSession { - private[this] lazy val LiftMagicID = LiftRules.servletSessionIdentifier + private[this] lazy val servletSessionIdentifier = LiftRules.servletSessionIdentifier def sessionId: String = session.getId - def link(liftSession: LiftSession) = session.setAttribute(LiftMagicID, SessionToServletBridge(liftSession.underlyingId)) + def link(liftSession: LiftSession) = session.setAttribute(servletSessionIdentifier, SessionToServletBridge(liftSession.underlyingId)) - def unlink(liftSession: LiftSession) = session.removeAttribute(LiftMagicID) + def unlink(liftSession: LiftSession) = session.removeAttribute(servletSessionIdentifier) def maxInactiveInterval: Long = session.getMaxInactiveInterval From 5cf6f915c4e8c43d190cfe5a515f640c26c588b7 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 12 Aug 2017 12:07:16 -0400 Subject: [PATCH 1638/1949] Updates to readme for new giter8 templates --- README.md | 56 ++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 45 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index df38ca877c..d1cdd0cc58 100644 --- a/README.md +++ b/README.md @@ -19,24 +19,50 @@ Because Lift applications are written in [Scala](http://www.scala-lang.org), an As of Lift 3.0, you'll need to be running Java 8 to use Lift. For those using Java 6 or Java 7, you'll need to use Lift 2.6 until you can upgrade your Java installation. -You can create a new Lift project using your favorite build system by adding Lift as a dependency: +You can create a new Lift project using your favorite build system by adding Lift as a dependency. +Below we walk through setting up Lift in sbt and Maven. -### With sbt +### With sbt (new project) -We recommend using the latest sbt version, which is currently 0.13.15, but anything 0.13.6+ will work -with the instructions below. +If you're using a recent version of sbt (e.g. 0.13.16), you can create a new Lift application using +our Giter8. To create a new, basic Lift application that has some example code, simply execute: -Create or update your `project/plugins.sbt` file with the `xsbt-web-plugin`: +``` +sbt new lift/basic-app.g8 +``` + +Or, if you're more on the advanced side of the room, you can also create a new, entirely blank +application: -```scala -addSbtPlugin("com.github.siasia" %% "xsbt-web-plugin" % "3.0.2") ``` +sbt new lift/blank-app.g8 +``` + +Follow the prompts to create your Lift application. + +### With sbt (Existing project) + +If you're using Lift in an existing sbt project you'll need to: -Then, add the plugin and Lift to your `build.sbt` file: +1. Add the xsbt-web-plugin if you don't already have it or some other way to start a servlet app. +2. Add the lift dependencies. + +To add the xsbt-web-plugin download the most recent version of our [web-plugin.sbt][wpsbt] file +to your `project/` folder. + +Then, enable the plugin for the container you want to use and in your `build.sbt` file. Below, we +activate the JettyPlugin: ```scala enablePlugins(JettyPlugin) +``` + +More information on using the plugin can be found on the [xsbt-web-plugin project][wpproj]. +After you've done this, you'll want to add Lift to your `libraryDependencies` in addition to +Logback if you don't already have another SLF4J logging library in place. For example: + +```scala libraryDependencies ++= { val liftVersion = "3.1.0" Seq( @@ -46,9 +72,16 @@ libraryDependencies ++= { } ``` +[wpsbt]: https://github.com/lift/basic-app.g8/blob/master/src/main/g8/project/web-plugin.sbt +[wpproj]: https://github.com/earldouglas/xsbt-web-plugin/ + ### With Maven -You can use one of the several archetypes -- `lift-archetype-blank`, `lift-archetype-basic`, `lift-archetype-jpa-basic` -- to generate a new Lift project. You must set `archetypeRepository` and `remoteRepositories` to `http://scala-tools.org/repo-releases` or `http://scala-tools.org/repo-snapshots`, depending on whether you are using a release or the latest SNAPSHOT. +You can use one of the several archetypes -- `lift-archetype-blank`, `lift-archetype-basic`, +`lift-archetype-jpa-basic` -- to generate a new Lift project. You must set `archetypeRepository` +and `remoteRepositories` to `http://scala-tools.org/repo-releases` or +`http://scala-tools.org/repo-snapshots`, depending on whether you are using a release or the latest +SNAPSHOT. Or, you can add Lift to your `pom.xml` like so: @@ -58,8 +91,9 @@ Or, you can add Lift to your `pom.xml` like so: 3.1.0 -Where `${scala.version}` is `2.11` or `2.12`. Individual patch releases of the Scala compiler (e.g. 2.12.2) -are binary compatible with everything in their release series, so you only need the first two version parts. +Where `${scala.version}` is `2.11` or `2.12`. Individual patch releases of the Scala compiler +(e.g. 2.12.2) are binary compatible with everything in their release series, so you only need the +first two version parts. You can learn more about Maven integration [on the wiki](http://www.assembla.com/wiki/show/liftweb/Using_Maven). From 9f1867bc0b8667f671bfa4172953bb531b1690c0 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 12 Aug 2017 12:18:20 -0400 Subject: [PATCH 1639/1949] Remove apparently outdated archetype information --- README.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/README.md b/README.md index d1cdd0cc58..9eabc90248 100644 --- a/README.md +++ b/README.md @@ -77,13 +77,7 @@ libraryDependencies ++= { ### With Maven -You can use one of the several archetypes -- `lift-archetype-blank`, `lift-archetype-basic`, -`lift-archetype-jpa-basic` -- to generate a new Lift project. You must set `archetypeRepository` -and `remoteRepositories` to `http://scala-tools.org/repo-releases` or -`http://scala-tools.org/repo-snapshots`, depending on whether you are using a release or the latest -SNAPSHOT. - -Or, you can add Lift to your `pom.xml` like so: +Add Lift to your `pom.xml` like so: net.liftweb From 0077e956f629f62634a484f4b33bff062831b41d Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sun, 13 Aug 2017 14:55:48 -0400 Subject: [PATCH 1640/1949] Use regular val instead of lazy val --- .../net/liftweb/http/provider/servlet/HTTPServletSession.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPServletSession.scala b/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPServletSession.scala index f98f344790..c4eea821f5 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPServletSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPServletSession.scala @@ -24,7 +24,7 @@ import net.liftweb.common._ import net.liftweb.util._ class HTTPServletSession(session: HttpSession) extends HTTPSession { - private[this] lazy val servletSessionIdentifier = LiftRules.servletSessionIdentifier + private[this] val servletSessionIdentifier = LiftRules.servletSessionIdentifier def sessionId: String = session.getId From da0182a2e31d86d0c994070cd0e02dca56a26aa2 Mon Sep 17 00:00:00 2001 From: Joe Barnes Date: Wed, 16 Aug 2017 10:34:44 -0500 Subject: [PATCH 1641/1949] Minor tweaks to LiftRulesGuardedSetting per PR comments --- .../scala/net/liftweb/http/LiftRules.scala | 83 +--------------- .../http/LiftRulesGuardedSetting.scala | 94 +++++++++++++++++++ 2 files changed, 97 insertions(+), 80 deletions(-) create mode 100644 web/webkit/src/main/scala/net/liftweb/http/LiftRulesGuardedSetting.scala diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index a939560765..ee29e87b76 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -142,7 +142,6 @@ object LiftRules extends LiftRulesMocker { */ type StatelessTestPF = PartialFunction[List[String], Boolean] - type StackTrace = Array[StackTraceElement] /** @@ -1584,9 +1583,9 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { private def logSnippetFailure(sf: SnippetFailure) = logger.info("Snippet Failure: " + sf) - val throwSettingsException: LiftRulesSettingException => Unit = e => throw e - val warnOnSettingsException: LiftRulesSettingException => Unit = e => logger.warn("LiftRules setting safety violation!!!", e) - val settingsExceptionFunc = new LiftRulesSetting[LiftRulesSettingException => Unit]("settingsExceptionFunc", throwSettingsException) + val settingsExceptionFunc = new LiftRulesGuardedSetting[LiftRulesSettingException => Unit]("settingsExceptionFunc", + e => logger.warn("LiftRules setting safety violation!!!", e) + ) /** * Set to false if you do not want ajax/comet requests that are not @@ -2301,80 +2300,4 @@ trait FormVendor { private object requestForms extends SessionVar[Map[String, List[FormBuilderLocator[_]]]](Map()) } -abstract class LiftRulesSettingException(settingName: String, stackTrace: LiftRules.StackTrace, message: String) extends Exception(message) { - setStackTrace(stackTrace) -} - -case class LiftRulesSettingWrittenAfterReadException(settingName: String, stackTrace: LiftRules.StackTrace, message: String) - extends LiftRulesSettingException(settingName, stackTrace, message) - -case class LiftRulesSettingWrittenAfterBootException(settingName: String, stackTrace: LiftRules.StackTrace, message: String) - extends LiftRulesSettingException(settingName, stackTrace, message) - -case class LiftRulesSettingWrittenTwiceException(settingName: String, stackTrace: LiftRules.StackTrace, message: String) - extends LiftRulesSettingException(settingName, stackTrace, message) - - -class LiftRulesSetting[T](val name: String, val default: T) extends LiftValue[T] with HasCalcDefaultValue[T] { - import LiftRules.StackTrace - - private [this] var v: T = default - private [this] var lastSet: Option[Exception] = None - private [this] var lastRead: Option[Exception] = None - - private [this] def writeAfterReadMessage = - s"LiftRules.$name was set AFTER already being read! " + - s"Your Lift application may be booting into an inconsistent state. " + - s"Review the stacktrace below to see where the value was last read. " + - s"Consider re-ordering your settings in boot. " - - private [this] def writeAfterBootMessage = - s"LiftRules.$name set after Lift finished booting. " + - s"This could potentially cause your Lift application to run in an inconsistent state. " + - s"Review the stacktrace below to see where settings are being changed after boot time. " - - private [this] def writtenTwiceMessage1(newVal: T) = - s"LiftRules.$name was set to $v then later set to $newVal. " + - s"This could potentially cause your Lift application to run in an inconsistent state. " + - s"Review the stacktrace below to see where LiftRules.$name was first set to $v. " - - private [this] def writtenTwiceMessage2(newVal: T) = - s"Review the stacktrace below to see where LiftRules.$name was later set to $newVal. " - - private [this] val toIgnore = Set("LiftRulesSetting", "LiftValue") - private [this] def trimmedStackTrace(t: Throwable): StackTrace = - t.getStackTrace.dropWhile(e => toIgnore.find(e.getClassName contains _).isDefined) - - override def set(value: T): T = { - lastSet.collect { case e if v != value => - val e1 = LiftRulesSettingWrittenTwiceException(name, trimmedStackTrace(e), writtenTwiceMessage1(value)) - LiftRules.settingsExceptionFunc.get.apply(e1) - - val e2 = LiftRulesSettingWrittenTwiceException(name, trimmedStackTrace(e), writtenTwiceMessage2(value)) - LiftRules.settingsExceptionFunc.get.apply(e2) - } - - if(LiftRules.doneBoot) { - val e = LiftRulesSettingWrittenAfterBootException(name, trimmedStackTrace(new Exception), writeAfterBootMessage) - LiftRules.settingsExceptionFunc.get.apply(e) - } - - // TODO: Skip if the value is the same? - lastRead.foreach { e => - val e2 = LiftRulesSettingWrittenAfterReadException(name, trimmedStackTrace(e), writeAfterReadMessage) - LiftRules.settingsExceptionFunc.get.apply(e2) - } - - lastSet = Some(new Exception) - v = value - v - } - - override def get: T = { - if(!LiftRules.doneBoot) lastRead = Some(new Exception) - v - } - - override protected def calcDefaultValue: T = default -} diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRulesGuardedSetting.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRulesGuardedSetting.scala new file mode 100644 index 0000000000..2713685e73 --- /dev/null +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRulesGuardedSetting.scala @@ -0,0 +1,94 @@ +package net.liftweb.http + +import net.liftweb.util.{HasCalcDefaultValue, LiftValue} + +object LiftRulesGuardedSetting { + type StackTrace = Array[StackTraceElement] +} + +import LiftRulesGuardedSetting._ + +/** + * TODO + * @param name + * @param default + * @tparam T + */ +class LiftRulesGuardedSetting[T](val name: String, val default: T) extends LiftValue[T] with HasCalcDefaultValue[T] { + private[this] var v: T = default + private[this] var lastSet: Option[StackTrace] = None + private[this] var lastRead: Option[StackTrace] = None + + private[this] def writeAfterReadMessage = + s"LiftRules.$name was set AFTER already being read! " + + s"Review the stacktrace below to see where the value was last read. " + + private[this] def writeAfterBootMessage = + s"LiftRules.$name set after Lift finished booting. " + + s"Review the stacktrace below to see where settings are being changed after boot time. " + + private[this] def writtenTwiceMessage1(newVal: T) = + s"LiftRules.$name was set to $v then later set to $newVal. " + + s"This could potentially cause your Lift application to run in an inconsistent state. " + + s"Review the stacktrace below to see where LiftRules.$name was first set to $v. " + + private[this] def writtenTwiceMessage2(newVal: T) = + s"Review the stacktrace below to see where LiftRules.$name was later set to $newVal. " + + private[this] def trimmedStackTrace(t: Throwable): StackTrace = { + val toIgnore = Set("LiftRulesGuardedSetting", "LiftValue") + t.getStackTrace.dropWhile(e => toIgnore.find(e.getClassName contains _).isDefined) + } + + private[this] def currentStackTrace: StackTrace = trimmedStackTrace(new Exception) + + override def set(value: T): T = { + lastSet.foreach { case stackTrace if v != value => + val e1 = LiftRulesSettingWrittenTwiceException(name, stackTrace, writtenTwiceMessage1(value)) + LiftRules.settingsExceptionFunc.get.apply(e1) + + val e2 = LiftRulesSettingWrittenTwiceException(name, stackTrace, writtenTwiceMessage2(value)) + LiftRules.settingsExceptionFunc.get.apply(e2) + } + + if(LiftRules.doneBoot) { + val e = LiftRulesSettingWrittenAfterBootException(name, currentStackTrace, writeAfterBootMessage) + LiftRules.settingsExceptionFunc.get.apply(e) + } + + // TODO: Skip if the value is the same? + lastRead.foreach { stackTrace => + val e2 = LiftRulesSettingWrittenAfterReadException(name, stackTrace, writeAfterReadMessage) + LiftRules.settingsExceptionFunc.get.apply(e2) + } + + lastSet = Some(currentStackTrace) + v = value + v + } + + override def get: T = { + if(!LiftRules.doneBoot) lastRead = Some(currentStackTrace) + v + } + + override protected def calcDefaultValue: T = default +} + + + +abstract class LiftRulesSettingException(settingName: String, stackTrace: StackTrace, message: String) extends Exception(message) { + setStackTrace(stackTrace) +} + +case class LiftRulesSettingWrittenAfterReadException(settingName: String, stackTrace: StackTrace, message: String) + extends LiftRulesSettingException(settingName, stackTrace, message) + +case class LiftRulesSettingWrittenAfterBootException(settingName: String, stackTrace: StackTrace, message: String) + extends LiftRulesSettingException(settingName, stackTrace, message) + +case class LiftRulesSettingWrittenTwiceException(settingName: String, stackTrace: StackTrace, message: String) + extends LiftRulesSettingException(settingName, stackTrace, message) + + + From a2d41d0139d48eb0443bf96a9255af5249b70f13 Mon Sep 17 00:00:00 2001 From: Joe Barnes Date: Wed, 16 Aug 2017 11:08:02 -0500 Subject: [PATCH 1642/1949] Removing the heavy use of Exceptions in LiftRulesGuardedSettings --- .../scala/net/liftweb/http/LiftRules.scala | 4 +- .../http/LiftRulesGuardedSetting.scala | 82 +++++++++++++------ 2 files changed, 59 insertions(+), 27 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index ee29e87b76..38560bfbc7 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -1583,8 +1583,8 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { private def logSnippetFailure(sf: SnippetFailure) = logger.info("Snippet Failure: " + sf) - val settingsExceptionFunc = new LiftRulesGuardedSetting[LiftRulesSettingException => Unit]("settingsExceptionFunc", - e => logger.warn("LiftRules setting safety violation!!!", e) + val guardedSettingViolationFunc = new LiftRulesGuardedSetting[LiftRulesGuardedSetting.SettingViolation => Unit]("guardedSettingViolationFunc", + violation => logger.warn("LiftRules guarded setting violation!!!", violation.toException) ) /** diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRulesGuardedSetting.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRulesGuardedSetting.scala index 2713685e73..f3c150b107 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRulesGuardedSetting.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRulesGuardedSetting.scala @@ -4,12 +4,59 @@ import net.liftweb.util.{HasCalcDefaultValue, LiftValue} object LiftRulesGuardedSetting { type StackTrace = Array[StackTraceElement] + + /** + * Base class for all possible violations which LiftRulesGuardedSetting warns you about + * @param settingName + * @param stackTrace + * @param message + */ + abstract class SettingViolation(settingName: String, stackTrace: StackTrace, message: String) extends Serializable { + /** + * Converts this violation into an Exception which can be handed to a logger for clean message printing + * @return + */ + def toException: Exception = { + val e = new Exception(message) + e.setStackTrace(stackTrace) + e + } + } + + /** + * Indicates that a LiftRulesGuardedSetting was written after it had already been read. + * @param settingName + * @param stackTrace + * @param message + */ + case class SettingWrittenAfterRead(settingName: String, stackTrace: StackTrace, message: String) + extends SettingViolation(settingName, stackTrace, message) + + /** + * Indicates that a LiftRulesGuardedSetting was written after Lift finished booting. + * @param settingName + * @param stackTrace + * @param message + */ + case class SettingWrittenAfterBoot(settingName: String, stackTrace: StackTrace, message: String) + extends SettingViolation(settingName, stackTrace, message) + + /** + * Indicates that a LiftRulesGuardedSetting was set to two different values. + * @param settingName + * @param stackTrace + * @param message + */ + case class SettingWrittenTwice(settingName: String, stackTrace: StackTrace, message: String) + extends SettingViolation(settingName, stackTrace, message) } import LiftRulesGuardedSetting._ /** - * TODO + * This class encapsulates a mutable LiftRules setting which guards its value against changes which can produce + * unexpected results in a Lift application. + * * @param name * @param default * @tparam T @@ -20,7 +67,7 @@ class LiftRulesGuardedSetting[T](val name: String, val default: T) extends LiftV private[this] var lastRead: Option[StackTrace] = None private[this] def writeAfterReadMessage = - s"LiftRules.$name was set AFTER already being read! " + + s"LiftRules.$name was set after already being read! " + s"Review the stacktrace below to see where the value was last read. " private[this] def writeAfterBootMessage = @@ -44,22 +91,22 @@ class LiftRulesGuardedSetting[T](val name: String, val default: T) extends LiftV override def set(value: T): T = { lastSet.foreach { case stackTrace if v != value => - val e1 = LiftRulesSettingWrittenTwiceException(name, stackTrace, writtenTwiceMessage1(value)) - LiftRules.settingsExceptionFunc.get.apply(e1) + val e1 = SettingWrittenTwice(name, stackTrace, writtenTwiceMessage1(value)) + LiftRules.guardedSettingViolationFunc.get.apply(e1) - val e2 = LiftRulesSettingWrittenTwiceException(name, stackTrace, writtenTwiceMessage2(value)) - LiftRules.settingsExceptionFunc.get.apply(e2) + val e2 = SettingWrittenTwice(name, currentStackTrace, writtenTwiceMessage2(value)) + LiftRules.guardedSettingViolationFunc.get.apply(e2) } if(LiftRules.doneBoot) { - val e = LiftRulesSettingWrittenAfterBootException(name, currentStackTrace, writeAfterBootMessage) - LiftRules.settingsExceptionFunc.get.apply(e) + val e = SettingWrittenAfterBoot(name, currentStackTrace, writeAfterBootMessage) + LiftRules.guardedSettingViolationFunc.get.apply(e) } // TODO: Skip if the value is the same? lastRead.foreach { stackTrace => - val e2 = LiftRulesSettingWrittenAfterReadException(name, stackTrace, writeAfterReadMessage) - LiftRules.settingsExceptionFunc.get.apply(e2) + val e2 = SettingWrittenAfterRead(name, stackTrace, writeAfterReadMessage) + LiftRules.guardedSettingViolationFunc.get.apply(e2) } lastSet = Some(currentStackTrace) @@ -77,18 +124,3 @@ class LiftRulesGuardedSetting[T](val name: String, val default: T) extends LiftV -abstract class LiftRulesSettingException(settingName: String, stackTrace: StackTrace, message: String) extends Exception(message) { - setStackTrace(stackTrace) -} - -case class LiftRulesSettingWrittenAfterReadException(settingName: String, stackTrace: StackTrace, message: String) - extends LiftRulesSettingException(settingName, stackTrace, message) - -case class LiftRulesSettingWrittenAfterBootException(settingName: String, stackTrace: StackTrace, message: String) - extends LiftRulesSettingException(settingName, stackTrace, message) - -case class LiftRulesSettingWrittenTwiceException(settingName: String, stackTrace: StackTrace, message: String) - extends LiftRulesSettingException(settingName, stackTrace, message) - - - From d3d54b887426b9fcacd289510382d01d63b2b047 Mon Sep 17 00:00:00 2001 From: Joe Barnes Date: Thu, 17 Aug 2017 08:35:24 -0500 Subject: [PATCH 1643/1949] Cleaning up scaladocs for LiftRulesGuardedSetting --- .../http/LiftRulesGuardedSetting.scala | 24 ++++++------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRulesGuardedSetting.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRulesGuardedSetting.scala index f3c150b107..ee163ce013 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRulesGuardedSetting.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRulesGuardedSetting.scala @@ -6,15 +6,14 @@ object LiftRulesGuardedSetting { type StackTrace = Array[StackTraceElement] /** - * Base class for all possible violations which LiftRulesGuardedSetting warns you about - * @param settingName - * @param stackTrace - * @param message + * Base class for all possible violations which LiftRulesGuardedSetting warns you about. + * @param settingName the name of the LiftRules setting which was violated. + * @param stackTrace the stacktrace from where the violation occurred + * @param message an English message for the developer detailing the violation */ abstract class SettingViolation(settingName: String, stackTrace: StackTrace, message: String) extends Serializable { /** * Converts this violation into an Exception which can be handed to a logger for clean message printing - * @return */ def toException: Exception = { val e = new Exception(message) @@ -25,27 +24,18 @@ object LiftRulesGuardedSetting { /** * Indicates that a LiftRulesGuardedSetting was written after it had already been read. - * @param settingName - * @param stackTrace - * @param message */ case class SettingWrittenAfterRead(settingName: String, stackTrace: StackTrace, message: String) extends SettingViolation(settingName, stackTrace, message) /** * Indicates that a LiftRulesGuardedSetting was written after Lift finished booting. - * @param settingName - * @param stackTrace - * @param message */ case class SettingWrittenAfterBoot(settingName: String, stackTrace: StackTrace, message: String) extends SettingViolation(settingName, stackTrace, message) /** * Indicates that a LiftRulesGuardedSetting was set to two different values. - * @param settingName - * @param stackTrace - * @param message */ case class SettingWrittenTwice(settingName: String, stackTrace: StackTrace, message: String) extends SettingViolation(settingName, stackTrace, message) @@ -57,9 +47,9 @@ import LiftRulesGuardedSetting._ * This class encapsulates a mutable LiftRules setting which guards its value against changes which can produce * unexpected results in a Lift application. * - * @param name - * @param default - * @tparam T + * @param name the name of the LiftRules setting (an unfortunate duplication of the name given on LiftRules itself). + * @param default the default value of this setting + * @tparam T the type of the setting */ class LiftRulesGuardedSetting[T](val name: String, val default: T) extends LiftValue[T] with HasCalcDefaultValue[T] { private[this] var v: T = default From b64c5fec120450accbf65180b93f5e42b41a5d39 Mon Sep 17 00:00:00 2001 From: Joe Barnes Date: Thu, 17 Aug 2017 09:09:32 -0500 Subject: [PATCH 1644/1949] Removing the LiftRulesGuardedSetting check for a setting being set multiple times --- .../http/LiftRulesGuardedSetting.scala | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRulesGuardedSetting.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRulesGuardedSetting.scala index ee163ce013..388d09be7e 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRulesGuardedSetting.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRulesGuardedSetting.scala @@ -33,12 +33,6 @@ object LiftRulesGuardedSetting { */ case class SettingWrittenAfterBoot(settingName: String, stackTrace: StackTrace, message: String) extends SettingViolation(settingName, stackTrace, message) - - /** - * Indicates that a LiftRulesGuardedSetting was set to two different values. - */ - case class SettingWrittenTwice(settingName: String, stackTrace: StackTrace, message: String) - extends SettingViolation(settingName, stackTrace, message) } import LiftRulesGuardedSetting._ @@ -64,14 +58,6 @@ class LiftRulesGuardedSetting[T](val name: String, val default: T) extends LiftV s"LiftRules.$name set after Lift finished booting. " + s"Review the stacktrace below to see where settings are being changed after boot time. " - private[this] def writtenTwiceMessage1(newVal: T) = - s"LiftRules.$name was set to $v then later set to $newVal. " + - s"This could potentially cause your Lift application to run in an inconsistent state. " + - s"Review the stacktrace below to see where LiftRules.$name was first set to $v. " - - private[this] def writtenTwiceMessage2(newVal: T) = - s"Review the stacktrace below to see where LiftRules.$name was later set to $newVal. " - private[this] def trimmedStackTrace(t: Throwable): StackTrace = { val toIgnore = Set("LiftRulesGuardedSetting", "LiftValue") t.getStackTrace.dropWhile(e => toIgnore.find(e.getClassName contains _).isDefined) @@ -80,14 +66,6 @@ class LiftRulesGuardedSetting[T](val name: String, val default: T) extends LiftV private[this] def currentStackTrace: StackTrace = trimmedStackTrace(new Exception) override def set(value: T): T = { - lastSet.foreach { case stackTrace if v != value => - val e1 = SettingWrittenTwice(name, stackTrace, writtenTwiceMessage1(value)) - LiftRules.guardedSettingViolationFunc.get.apply(e1) - - val e2 = SettingWrittenTwice(name, currentStackTrace, writtenTwiceMessage2(value)) - LiftRules.guardedSettingViolationFunc.get.apply(e2) - } - if(LiftRules.doneBoot) { val e = SettingWrittenAfterBoot(name, currentStackTrace, writeAfterBootMessage) LiftRules.guardedSettingViolationFunc.get.apply(e) From edb75905d83b5cfdfb513509c7357dd8e18d3043 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Thu, 17 Aug 2017 19:32:03 -0400 Subject: [PATCH 1645/1949] Remove json-scalaz --- core/json-scalaz/README.md | 84 ------------- .../scala/net/liftweb/json/scalaz/Base.scala | 116 ------------------ .../net/liftweb/json/scalaz/JsonScalaz.scala | 86 ------------- .../net/liftweb/json/scalaz/Lifting.scala | 58 --------- .../net/liftweb/json/scalaz/Tuples.scala | 73 ----------- .../net/lifweb/json/scalaz/Example.scala | 66 ---------- .../net/lifweb/json/scalaz/LottoExample.scala | 38 ------ .../net/lifweb/json/scalaz/TupleExample.scala | 15 --- .../json/scalaz/ValidationExample.scala | 71 ----------- 9 files changed, 607 deletions(-) delete mode 100644 core/json-scalaz/README.md delete mode 100644 core/json-scalaz/src/main/scala/net/liftweb/json/scalaz/Base.scala delete mode 100644 core/json-scalaz/src/main/scala/net/liftweb/json/scalaz/JsonScalaz.scala delete mode 100644 core/json-scalaz/src/main/scala/net/liftweb/json/scalaz/Lifting.scala delete mode 100644 core/json-scalaz/src/main/scala/net/liftweb/json/scalaz/Tuples.scala delete mode 100644 core/json-scalaz/src/test/scala/net/lifweb/json/scalaz/Example.scala delete mode 100644 core/json-scalaz/src/test/scala/net/lifweb/json/scalaz/LottoExample.scala delete mode 100644 core/json-scalaz/src/test/scala/net/lifweb/json/scalaz/TupleExample.scala delete mode 100644 core/json-scalaz/src/test/scala/net/lifweb/json/scalaz/ValidationExample.scala diff --git a/core/json-scalaz/README.md b/core/json-scalaz/README.md deleted file mode 100644 index 6014fb1d2b..0000000000 --- a/core/json-scalaz/README.md +++ /dev/null @@ -1,84 +0,0 @@ -Scalaz support for Lift JSON -============================ - -This project adds a type class to parse JSON: - - trait JSON[A] { - def read(json: JValue): Result[A] - def write(value: A): JValue - } - - type Result[A] = ValidationNEL[Error, A] - -Function 'read' returns an Applicative Functor, enabling parsing in an applicative style. - -Simple example --------------- - - scala> import scalaz._ - scala> import Scalaz._ - scala> import net.liftweb.json.scalaz.JsonScalaz._ - scala> import net.liftweb.json._ - - scala> case class Address(street: String, zipCode: String) - scala> case class Person(name: String, age: Int, address: Address) - - scala> val json = parse(""" {"street": "Manhattan 2", "zip": "00223" } """) - scala> (field[String]("street")(json) |@| field[String]("zip")(json)) { Address } - res0: Success(Address(Manhattan 2,00223)) - - scala> (field[String]("streets")(json) |@| field[String]("zip")(json)) { Address } - res1: Failure("no such field 'streets'") - -Notice the required explicit types when reading fields from JSON. The library comes with helpers which -can lift functions with pure values into "parsing context". This works well with Scala's type inferencer: - - scala> Address.applyJSON(field("street"), field("zip"))(json) - res2: Success(Address(Manhattan 2,00223)) - -Function 'applyJSON' above lifts function - - (String, String) => Address - -to - - (JValue => Result[String], JValue => Result[String]) => (JValue => Result[Address]) - -Example which adds a new type class instance --------------------------------------------- - - scala> implicit def addrJSONR: JSONR[Address] = Address.applyJSON(field("street"), field("zip")) - - scala> val p = JsonParser.parse(""" {"name":"joe","age":34,"address":{"street": "Manhattan 2", "zip": "00223" }} """) - scala> Person.applyJSON(field("name"), field("age"), field("address"))(p) - res0: Success(Person(joe,34,Address(Manhattan 2,00223))) - -Validation ----------- - -Applicative style parsing works nicely with validation and data conversion. It is easy to compose -transformations with various combinators Scalaz provides. An often used combinator is called a Kleisli -composition >=>. - - def min(x: Int): Int => Result[Int] = (y: Int) => - if (y < x) Fail("min", y + " < " + x) else y.success - - def max(x: Int): Int => Result[Int] = (y: Int) => - if (y > x) Fail("max", y + " > " + x) else y.success - - // Creates a function JValue => Result[Person] - Person.applyJSON(field("name"), validate[Int]("age") >=> min(18) >=> max(60)) - -Installation ------------- - -Add dependency to your SBT project description: - - val lift_json_scalaz = "net.liftweb" %% "lift-json-scalaz" % "XXX" - -Links ------ - -* [More examples](https://github.com/lift/framework/tree/master/core/json-scalaz/src/test/scala/net/liftweb/json/scalaz) -* [Scalaz](http://code.google.com/p/scalaz/) -* [Kleisli composition](http://www.haskell.org/hoogle/?hoogle=%28a+-%3E+m+b%29+-%3E+%28b+-%3E+m+c%29+-%3E+%28a+-%3E+m+c%29) diff --git a/core/json-scalaz/src/main/scala/net/liftweb/json/scalaz/Base.scala b/core/json-scalaz/src/main/scala/net/liftweb/json/scalaz/Base.scala deleted file mode 100644 index a8019d325b..0000000000 --- a/core/json-scalaz/src/main/scala/net/liftweb/json/scalaz/Base.scala +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright 2009-2010 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.json.scalaz - -import scalaz._ -import Scalaz._ -import net.liftweb.json._ -import scala.collection.breakOut - -trait Base { this: Types => - implicit def boolJSON: JSON[Boolean] = new JSON[Boolean] { - def read(json: JValue) = json match { - case JBool(b) => success(b) - case x => UnexpectedJSONError(x, classOf[JBool]).fail.liftFailNel - } - - def write(value: Boolean) = JBool(value) - } - - implicit def intJSON: JSON[Int] = new JSON[Int] { - def read(json: JValue) = json match { - case JInt(x) => success(x.intValue) - case x => UnexpectedJSONError(x, classOf[JInt]).fail.liftFailNel - } - - def write(value: Int) = JInt(BigInt(value)) - } - - implicit def longJSON: JSON[Long] = new JSON[Long] { - def read(json: JValue) = json match { - case JInt(x) => success(x.longValue) - case x => UnexpectedJSONError(x, classOf[JInt]).fail.liftFailNel - } - - def write(value: Long) = JInt(BigInt(value)) - } - - implicit def doubleJSON: JSON[Double] = new JSON[Double] { - def read(json: JValue) = json match { - case JDouble(x) => success(x) - case x => UnexpectedJSONError(x, classOf[JDouble]).fail.liftFailNel - } - - def write(value: Double) = JDouble(value) - } - - implicit def stringJSON: JSON[String] = new JSON[String] { - def read(json: JValue) = json match { - case JString(x) => success(x) - case x => UnexpectedJSONError(x, classOf[JString]).fail.liftFailNel - } - - def write(value: String) = JString(value) - } - - implicit def bigintJSON: JSON[BigInt] = new JSON[BigInt] { - def read(json: JValue) = json match { - case JInt(x) => success(x) - case x => UnexpectedJSONError(x, classOf[JInt]).fail.liftFailNel - } - - def write(value: BigInt) = JInt(value) - } - - implicit def jvalueJSON: JSON[JValue] = new JSON[JValue] { - def read(json: JValue) = success(json) - def write(value: JValue) = value - } - - implicit def listJSONR[A: JSONR]: JSONR[List[A]] = new JSONR[List[A]] { - def read(json: JValue) = json match { - case JArray(xs) => - xs.map(fromJSON[A]).sequence[PartialApply1Of2[ValidationNEL, Error]#Apply, A] - case x => UnexpectedJSONError(x, classOf[JArray]).fail.liftFailNel - } - } - implicit def listJSONW[A: JSONW]: JSONW[List[A]] = new JSONW[List[A]] { - def write(values: List[A]) = JArray(values.map(x => toJSON(x))) - } - - implicit def optionJSONR[A: JSONR]: JSONR[Option[A]] = new JSONR[Option[A]] { - def read(json: JValue) = json match { - case JNothing | JNull => success(None) - case x => fromJSON[A](x).map(some) - } - } - implicit def optionJSONW[A: JSONW]: JSONW[Option[A]] = new JSONW[Option[A]] { - def write(value: Option[A]) = value.map(x => toJSON(x)).getOrElse(JNothing) - } - - implicit def mapJSONR[A: JSONR]: JSONR[Map[String, A]] = new JSONR[Map[String, A]] { - def read(json: JValue) = json match { - case JObject(fs) => - val r = fs.map(f => fromJSON[A](f.value).map(v => (f.name, v))).sequence[PartialApply1Of2[ValidationNEL, Error]#Apply, (String, A)] - r.map(_.toMap) - case x => UnexpectedJSONError(x, classOf[JObject]).fail.liftFailNel - } - } - implicit def mapJSONW[A: JSONW]: JSONW[Map[String, A]] = new JSONW[Map[String, A]] { - def write(values: Map[String, A]) = JObject(values.map { case (k, v) => JField(k, toJSON(v)) }(breakOut): _*) - } -} diff --git a/core/json-scalaz/src/main/scala/net/liftweb/json/scalaz/JsonScalaz.scala b/core/json-scalaz/src/main/scala/net/liftweb/json/scalaz/JsonScalaz.scala deleted file mode 100644 index 51ec87a86b..0000000000 --- a/core/json-scalaz/src/main/scala/net/liftweb/json/scalaz/JsonScalaz.scala +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2009-2010 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.json.scalaz - -// FIXME Needed to due to https://issues.scala-lang.org/browse/SI-6541, -// which causes existential types to be inferred for the generated -// unapply of a case class with a wildcard parameterized type. -// Ostensibly should be fixed in 2.12, which means we're a ways away -// from being able to remove this, though. -import scala.language.existentials - -import scalaz._ -import Scalaz._ -import net.liftweb.json._ - -trait Types { - type Result[A] = ValidationNEL[Error, A] - - sealed trait Error - case class UnexpectedJSONError(was: JValue, expected: Class[_ <: JValue]) extends Error - case class NoSuchFieldError(name: String, json: JValue) extends Error - case class UncategorizedError(key: String, desc: String, args: List[Any]) extends Error - - case object Fail { - def apply[A](key: String, desc: String, args: List[Any]): Result[A] = - UncategorizedError(key, desc, args).fail.liftFailNel - - def apply[A](key: String, desc: String): Result[A] = - UncategorizedError(key, desc, Nil).fail.liftFailNel - } - - implicit def JValueShow[A <: JValue]: Show[A] = new Show[A] { - def show(json: A) = compact(render(json)).toList - } - - implicit def JValueZero: Zero[JValue] = zero(JNothing) - implicit def JValueSemigroup: Semigroup[JValue] = semigroup(_ ++ _) - implicit def JValueEqual: Equal[JValue] = equalA - - trait JSONR[A] { - def read(json: JValue): Result[A] - } - - trait JSONW[A] { - def write(value: A): JValue - } - - trait JSON[A] extends JSONR[A] with JSONW[A] - - implicit def Result2JSONR[A](f: JValue => Result[A]): JSONR[A] = new JSONR[A] { - def read(json: JValue) = f(json) - } - - def fromJSON[A: JSONR](json: JValue): Result[A] = implicitly[JSONR[A]].read(json) - def toJSON[A: JSONW](value: A): JValue = implicitly[JSONW[A]].write(value) - - def field[A: JSONR](name: String)(json: JValue): Result[A] = json match { - case JObject(fs) => - fs.find(_.name == name) - .map(f => implicitly[JSONR[A]].read(f.value)) - .orElse(implicitly[JSONR[A]].read(JNothing).fold(_ => none, x => some(Success(x)))) - .getOrElse(NoSuchFieldError(name, json).fail.liftFailNel) - case x => UnexpectedJSONError(x, classOf[JObject]).fail.liftFailNel - } - - def validate[A: JSONR](name: String): Kleisli[Result, JValue, A] = kleisli(field[A](name)) - - def makeObj(fields: Traversable[(String, JValue)]): JObject = - JObject(fields.toList.map { case (n, v) => JField(n, v) }) -} - -object JsonScalaz extends Types with Lifting with Base with Tuples diff --git a/core/json-scalaz/src/main/scala/net/liftweb/json/scalaz/Lifting.scala b/core/json-scalaz/src/main/scala/net/liftweb/json/scalaz/Lifting.scala deleted file mode 100644 index 0c224a4719..0000000000 --- a/core/json-scalaz/src/main/scala/net/liftweb/json/scalaz/Lifting.scala +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2009-2010 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.json.scalaz - -import scalaz._ -import Scalaz._ -import net.liftweb.json._ - -trait Lifting { this: Types => - implicit def Func2ToJSON[A: JSONR, B: JSONR, R](z: (A, B) => R) = new { - def applyJSON(a: JValue => Result[A], b: JValue => Result[B]): JValue => Result[R] = - (json: JValue) => (a(json) |@| b(json))(z) - } - - implicit def Func3ToJSON[A: JSONR, B: JSONR, C: JSONR, R](z: (A, B, C) => R) = new { - def applyJSON(a: JValue => Result[A], b: JValue => Result[B], c: JValue => Result[C]): JValue => Result[R] = - (json: JValue) => (a(json) |@| b(json) |@| c(json))(z) - } - - implicit def Func4ToJSON[A: JSONR, B: JSONR, C: JSONR, D: JSONR, R](z: (A, B, C, D) => R) = new { - def applyJSON(a: JValue => Result[A], b: JValue => Result[B], c: JValue => Result[C], d: JValue => Result[D]): JValue => Result[R] = - (json: JValue) => (a(json) |@| b(json) |@| c(json) |@| d(json))(z) - } - - implicit def Func5ToJSON[A: JSONR, B: JSONR, C: JSONR, D: JSONR, E: JSONR, R](z: (A, B, C, D, E) => R) = new { - def applyJSON(a: JValue => Result[A], b: JValue => Result[B], c: JValue => Result[C], d: JValue => Result[D], e: JValue => Result[E]): JValue => Result[R] = - (json: JValue) => (a(json) |@| b(json) |@| c(json) |@| d(json) |@| e(json))(z) - } - - implicit def Func6ToJSON[A: JSONR, B: JSONR, C: JSONR, D: JSONR, E: JSONR, F: JSONR, R](z: (A, B, C, D, E, F) => R) = new { - def applyJSON(a: JValue => Result[A], b: JValue => Result[B], c: JValue => Result[C], d: JValue => Result[D], e: JValue => Result[E], f: JValue => Result[F]): JValue => Result[R] = - (json: JValue) => (a(json) |@| b(json) |@| c(json) |@| d(json) |@| e(json) |@| f(json))(z) - } - - implicit def Func7ToJSON[A: JSONR, B: JSONR, C: JSONR, D: JSONR, E: JSONR, F: JSONR, G: JSONR, R](z: (A, B, C, D, E, F, G) => R) = new { - def applyJSON(a: JValue => Result[A], b: JValue => Result[B], c: JValue => Result[C], d: JValue => Result[D], e: JValue => Result[E], f: JValue => Result[F], g: JValue => Result[G]): JValue => Result[R] = - (json: JValue) => (a(json) |@| b(json) |@| c(json) |@| d(json) |@| e(json) |@| f(json) |@| g(json))(z) - } - - implicit def Func8ToJSON[A: JSONR, B: JSONR, C: JSONR, D: JSONR, E: JSONR, F: JSONR, G: JSONR, H: JSONR, R](z: (A, B, C, D, E, F, G, H) => R) = new { - def applyJSON(a: JValue => Result[A], b: JValue => Result[B], c: JValue => Result[C], d: JValue => Result[D], e: JValue => Result[E], f: JValue => Result[F], g: JValue => Result[G], h: JValue => Result[H]): JValue => Result[R] = - (json: JValue) => (a(json) |@| b(json) |@| c(json) |@| d(json) |@| e(json) |@| f(json) |@| g(json) |@| h(json))(z) - } -} diff --git a/core/json-scalaz/src/main/scala/net/liftweb/json/scalaz/Tuples.scala b/core/json-scalaz/src/main/scala/net/liftweb/json/scalaz/Tuples.scala deleted file mode 100644 index 9dca9d3c20..0000000000 --- a/core/json-scalaz/src/main/scala/net/liftweb/json/scalaz/Tuples.scala +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2009-2010 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.json.scalaz - -import scalaz._ -import Scalaz._ -import net.liftweb.json._ - -trait Tuples { this: Types => - implicit def Tuple2JSON[A: JSON, B: JSON]: JSON[(A, B)] = new JSON[(A, B)] { - def read(json: JValue) = json match { - case JArray(a :: b :: _) => - (fromJSON[A](a) |@| fromJSON[B](b)) { (a, b) => (a, b) } - case x => UnexpectedJSONError(x, classOf[JArray]).fail.liftFailNel - } - - def write(value: (A, B)) = JArray(toJSON(value._1) :: toJSON(value._2) :: Nil) - } - - implicit def Tuple3JSON[A: JSON, B: JSON, C: JSON]: JSON[(A, B, C)] = new JSON[(A, B, C)] { - def read(json: JValue) = json match { - case JArray(a :: b :: c :: _) => - (fromJSON[A](a) |@| fromJSON[B](b) |@| fromJSON[C](c)) { (a, b, c) => (a, b, c) } - case x => UnexpectedJSONError(x, classOf[JArray]).fail.liftFailNel - } - - def write(value: (A, B, C)) = JArray(toJSON(value._1) :: toJSON(value._2) :: toJSON(value._3) :: Nil) - } - - implicit def Tuple4JSON[A: JSON, B: JSON, C: JSON, D: JSON]: JSON[(A, B, C, D)] = new JSON[(A, B, C, D)] { - def read(json: JValue) = json match { - case JArray(a :: b :: c :: d :: _) => - (fromJSON[A](a) |@| fromJSON[B](b) |@| fromJSON[C](c) |@| fromJSON[D](d)) { (a, b, c, d) => (a, b, c, d) } - case x => UnexpectedJSONError(x, classOf[JArray]).fail.liftFailNel - } - - def write(value: (A, B, C, D)) = JArray(toJSON(value._1) :: toJSON(value._2) :: toJSON(value._3) :: toJSON(value._4) :: Nil) - } - - implicit def Tuple5JSON[A: JSON, B: JSON, C: JSON, D: JSON, E: JSON]: JSON[(A, B, C, D, E)] = new JSON[(A, B, C, D, E)] { - def read(json: JValue) = json match { - case JArray(a :: b :: c :: d :: e :: _) => - (fromJSON[A](a) |@| fromJSON[B](b) |@| fromJSON[C](c) |@| fromJSON[D](d) |@| fromJSON[E](e)) { (a, b, c, d, e) => (a, b, c, d, e) } - case x => UnexpectedJSONError(x, classOf[JArray]).fail.liftFailNel - } - - def write(value: (A, B, C, D, E)) = JArray(toJSON(value._1) :: toJSON(value._2) :: toJSON(value._3) :: toJSON(value._4) :: toJSON(value._5) :: Nil) - } - - implicit def Tuple6JSON[A: JSON, B: JSON, C: JSON, D: JSON, E: JSON, F: JSON]: JSON[(A, B, C, D, E, F)] = new JSON[(A, B, C, D, E, F)] { - def read(json: JValue) = json match { - case JArray(a :: b :: c :: d :: e :: f :: _) => - (fromJSON[A](a) |@| fromJSON[B](b) |@| fromJSON[C](c) |@| fromJSON[D](d) |@| fromJSON[E](e) |@| fromJSON[F](f)) { (a, b, c, d, e, f) => (a, b, c, d, e, f) } - case x => UnexpectedJSONError(x, classOf[JArray]).fail.liftFailNel - } - - def write(value: (A, B, C, D, E, F)) = JArray(toJSON(value._1) :: toJSON(value._2) :: toJSON(value._3) :: toJSON(value._4) :: toJSON(value._5) :: toJSON(value._6) :: Nil) - } -} diff --git a/core/json-scalaz/src/test/scala/net/lifweb/json/scalaz/Example.scala b/core/json-scalaz/src/test/scala/net/lifweb/json/scalaz/Example.scala deleted file mode 100644 index 41a8ca70e7..0000000000 --- a/core/json-scalaz/src/test/scala/net/lifweb/json/scalaz/Example.scala +++ /dev/null @@ -1,66 +0,0 @@ -package net.liftweb.json.scalaz - -import scala.language.reflectiveCalls - -import scalaz._ -import Scalaz._ -import JsonScalaz._ -import net.liftweb.json._ - -import org.specs2.mutable.Specification - -object Example extends Specification { - - case class Address(street: String, zipCode: String) - case class Person(name: String, age: Int, address: Address) - - "Parse address in an Applicative style" in { - val json = parse(""" {"street": "Manhattan 2", "zip": "00223" } """) - val a1 = field[String]("zip")(json) <*> (field[String]("street")(json) map Address.curried) - val a2 = (field[String]("street")(json) |@| field[String]("zip")(json)) { Address } - val a3 = Address.applyJSON(field("street"), field("zip"))(json) - a1 mustEqual Success(Address("Manhattan 2", "00223")) - a2 mustEqual a1 - a3 mustEqual a1 - } - - "Failed address parsing" in { - val json = parse(""" {"street": "Manhattan 2", "zip": "00223" } """) - val a = (field[String]("streets")(json) |@| field[String]("zip")(json)) { Address } - a.fail.toOption.get.list mustEqual List(NoSuchFieldError("streets", json)) - } - - "Parse Person with Address" in { - implicit def addrJSON: JSONR[Address] = new JSONR[Address] { - def read(json: JValue) = Address.applyJSON(field("street"), field("zip"))(json) - } - - val p = parse(""" {"name":"joe","age":34,"address":{"street": "Manhattan 2", "zip": "00223" }} """) - val person = Person.applyJSON(field("name"), field("age"), field("address"))(p) - person mustEqual Success(Person("joe", 34, Address("Manhattan 2", "00223"))) - } - - "Format Person with Address" in { - implicit def addrJSON: JSONW[Address] = new JSONW[Address] { - def write(a: Address) = - makeObj(("street" -> toJSON(a.street)) :: ("zip" -> toJSON(a.zipCode)) :: Nil) - } - - val p = Person("joe", 34, Address("Manhattan 2", "00223")) - val json = makeObj(("name" -> toJSON(p.name)) :: - ("age" -> toJSON(p.age)) :: - ("address" -> toJSON(p.address)) :: Nil) - json.shows mustEqual - """{"name":"joe","age":34,"address":{"street":"Manhattan 2","zip":"00223"}}""" - } - - "Parse Map" in { - val json = parse(""" {"street": "Manhattan 2", "zip": "00223" } """) - fromJSON[Map[String, String]](json) mustEqual Success(Map("street" -> "Manhattan 2", "zip" -> "00223")) - } - - "Format Map" in { - toJSON(Map("street" -> "Manhattan 2", "zip" -> "00223")).shows mustEqual - """{"street":"Manhattan 2","zip":"00223"}""" - } -} diff --git a/core/json-scalaz/src/test/scala/net/lifweb/json/scalaz/LottoExample.scala b/core/json-scalaz/src/test/scala/net/lifweb/json/scalaz/LottoExample.scala deleted file mode 100644 index 44fe7220b0..0000000000 --- a/core/json-scalaz/src/test/scala/net/lifweb/json/scalaz/LottoExample.scala +++ /dev/null @@ -1,38 +0,0 @@ -package net.liftweb.json.scalaz - -import scalaz._ -import Scalaz._ -import JsonScalaz._ -import net.liftweb.json._ - -import org.specs2.mutable.Specification - -object LottoExample extends Specification { - case class Winner(winnerId: Long, numbers: List[Int]) - case class Lotto(id: Long, winningNumbers: List[Int], winners: List[Winner], drawDate: Option[String]) - - val json = parse("""{"id":5,"winning-numbers":[2,45,34,23,7,5],"winners":[{"winner-id":23,"numbers":[2,45,34,23,3,5]},{"winner-id":54,"numbers":[52,3,12,11,18,22]}]}""") - - // Lotto line must have exactly 6 numbers - def len(x: Int) = (xs: List[Int]) => - if (xs.length != x) Fail("len", xs.length + " != " + x) else xs.success - - // FIXME enable when 2.8 no longer supported, 2.9 needs: import Validation.Monad._ -/* - // Note 'apply _' is not needed on Scala 2.8.1 >= - implicit def winnerJSON: JSONR[Winner] = - Winner.applyJSON(field("winner-id"), validate[List[Int]]("numbers") >=> len(6) apply _) - - implicit def lottoJSON: JSONR[Lotto] = - Lotto.applyJSON(field("id") - , validate[List[Int]]("winning-numbers") >=> len(6) apply _ - , field("winners") - , field("draw-date")) - - val winners = List(Winner(23, List(2, 45, 34, 23, 3, 5)), Winner(54, List(52, 3, 12, 11, 18, 22))) - val lotto = Lotto(5, List(2, 45, 34, 23, 7, 5), winners, None) - - fromJSON[Lotto](json) mustEqual Success(lotto) - */ -} - diff --git a/core/json-scalaz/src/test/scala/net/lifweb/json/scalaz/TupleExample.scala b/core/json-scalaz/src/test/scala/net/lifweb/json/scalaz/TupleExample.scala deleted file mode 100644 index c31f21ede0..0000000000 --- a/core/json-scalaz/src/test/scala/net/lifweb/json/scalaz/TupleExample.scala +++ /dev/null @@ -1,15 +0,0 @@ -package net.liftweb.json.scalaz - -import scalaz._ -import Scalaz._ -import JsonScalaz._ -import net.liftweb.json._ - -import org.specs2.mutable.Specification - -object TupleExample extends Specification { - "Parse tuple from List" in { - val json = JsonParser.parse(""" [1,2,3] """) - fromJSON[Tuple3[Int, Int, Int]](json) mustEqual Success(1, 2, 3) - } -} diff --git a/core/json-scalaz/src/test/scala/net/lifweb/json/scalaz/ValidationExample.scala b/core/json-scalaz/src/test/scala/net/lifweb/json/scalaz/ValidationExample.scala deleted file mode 100644 index 54d8afd485..0000000000 --- a/core/json-scalaz/src/test/scala/net/lifweb/json/scalaz/ValidationExample.scala +++ /dev/null @@ -1,71 +0,0 @@ -package net.liftweb.json.scalaz - -import scala.language.reflectiveCalls - -import scalaz._ -import Scalaz._ -import JsonScalaz._ -import net.liftweb.json._ - -import org.specs2.mutable.Specification - -object ValidationExample extends Specification { - - case class Person(name: String, age: Int) - - "Validation" should { - def min(x: Int): Int => Result[Int] = (y: Int) => - if (y < x) Fail("min", y + " < " + x) else y.success - - def max(x: Int): Int => Result[Int] = (y: Int) => - if (y > x) Fail("max", y + " > " + x) else y.success - - val json = JsonParser.parse(""" {"name":"joe","age":17} """) - - // Note 'apply _' is not needed on Scala 2.8.1 >= - "fail when age is less than min age" in { - // Age must be between 18 an 60 -// FIXME enable when 2.8 no longer supported, 2.9 needs: import Validation.Monad._ -// val person = Person.applyJSON(field("name"), validate[Int]("age") >=> min(18) >=> max(60) apply _) -// person(json).fail.toOption.get.list mustEqual List(UncategorizedError("min", "17 < 18", Nil)) - } - - "pass when age within limits" in { - // Age must be between 16 an 60 - import Validation.Monad._ - val person = Person.applyJSON(field("name"), validate[Int]("age") >=> min(16) >=> max(60) apply _) - person(json) mustEqual Success(Person("joe", 17)) - } - } - - case class Range(start: Int, end: Int) - - // This example shows: - // * a validation where result depends on more than one value - // * parse a List with invalid values -// FIXME enable when 2.8 no longer supported, 2.9 needs: import Validation.Monad._ -/* - "Range filtering" should { - val json = JsonParser.parse(""" [{"s":10,"e":17},{"s":12,"e":13},{"s":11,"e":8}] """) - - def ascending: (Int, Int) => Result[(Int, Int)] = (x1: Int, x2: Int) => - if (x1 > x2) Fail("asc", x1 + " > " + x2) else (x1, x2).success - - // Valid range is a range having start <= end - implicit def rangeJSON: JSONR[Range] = new JSONR[Range] { - def read(json: JValue) = - ((field[Int]("s")(json) |@| field[Int]("e")(json)) apply ascending).join map Range.tupled - } - - "fail if lists contains invalid ranges" in { - val r = fromJSON[List[Range]](json) - r.fail.toOption.get.list mustEqual List(UncategorizedError("asc", "11 > 8", Nil)) - } - - "optionally return only valid ranges" in { - val ranges = json.children.map(fromJSON[Range]).filter(_.isSuccess).sequence[PartialApply1Of2[ValidationNEL, Error]#Apply, Range] - ranges mustEqual Success(List(Range(10, 17), Range(12, 13))) - } - } - */ -} From 10424972ce4f6d964fcb8a7e35e36bb0bd72349c Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Thu, 17 Aug 2017 19:41:09 -0400 Subject: [PATCH 1646/1949] Try upgrading to the trusty environment --- .travis.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 524d6c16bd..7cfed3621c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,9 @@ language: scala +dist: trusty + +sudo: false + scala: - 2.11.11 - 2.12.2 @@ -28,7 +32,6 @@ before_script: - "cd web/webkit" - "npm install" - "cd -" - - sudo chmod +x /usr/local/bin/sbt notifications: webhooks: From f2cea13ba662431f997f0b3cd9225f9893b9e7b2 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Thu, 17 Aug 2017 20:02:50 -0400 Subject: [PATCH 1647/1949] Try building with 2.12.3? --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 7cfed3621c..4653524786 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ sudo: false scala: - 2.11.11 - - 2.12.2 + - 2.12.3 cache: directories: From f7c6e09c1cf19211acdebc4c10176bf06ceec7c4 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 19 Aug 2017 08:40:18 -0400 Subject: [PATCH 1648/1949] Modernize the root build.sbt file --- build.sbt | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/build.sbt b/build.sbt index 2b41d45198..e83955414c 100644 --- a/build.sbt +++ b/build.sbt @@ -1,37 +1,33 @@ import Dependencies._ organization in ThisBuild := "net.liftweb" - version in ThisBuild := "3.2.0-SNAPSHOT" - homepage in ThisBuild := Some(url("http://www.liftweb.net")) - licenses in ThisBuild += ("Apache License, Version 2.0", url("http://www.apache.org/licenses/LICENSE-2.0.txt")) - startYear in ThisBuild := Some(2006) - organizationName in ThisBuild := "WorldWide Conferencing, LLC" - scalaVersion in ThisBuild := "2.12.2" - crossScalaVersions in ThisBuild := Seq("2.12.2", "2.11.11") -libraryDependencies in ThisBuild <++= scalaVersion {sv => Seq(specs2, specs2Matchers, specs2Mock, scalacheck, scalatest) } +libraryDependencies in ThisBuild ++= Seq(specs2, specs2Matchers, specs2Mock, scalacheck, scalatest) // Settings for Sonatype compliance -pomIncludeRepository in ThisBuild := { _ => false } - -publishTo in ThisBuild <<= isSnapshot(if (_) Some(Opts.resolver.sonatypeSnapshots) else Some(Opts.resolver.sonatypeStaging)) - -scmInfo in ThisBuild := Some(ScmInfo(url("https://github.com/lift/framework"), "scm:git:https://github.com/lift/framework.git")) - -pomExtra in ThisBuild := Developers.toXml - -credentials in ThisBuild <+= state map { s => Credentials(BuildPaths.getGlobalSettingsDirectory(s, BuildPaths.getGlobalBase(s)) / ".credentials") } - -initialize <<= (name, version, scalaVersion) apply printLogo - -resolvers in ThisBuild ++= Seq( +pomIncludeRepository in ThisBuild := { _ => false } +publishTo in ThisBuild := { + if (isSnapshot.value) { + Some(Opts.resolver.sonatypeSnapshots) + } else { + Some(Opts.resolver.sonatypeStaging) + } +} +scmInfo in ThisBuild := Some(ScmInfo(url("https://github.com/lift/framework"), "scm:git:https://github.com/lift/framework.git")) +pomExtra in ThisBuild := Developers.toXml + +credentials in ThisBuild += Credentials(BuildPaths.getGlobalSettingsDirectory(state.value, BuildPaths.getGlobalBase(state.value)) / ".credentials") + +initialize <<= (name, version, scalaVersion).apply(printLogo) + +resolvers in ThisBuild ++= Seq( "snapshots" at "https://oss.sonatype.org/content/repositories/snapshots", "releases" at "https://oss.sonatype.org/content/repositories/releases" ) From 0670b7643dfcdd210c755ff176109e4abc5de435 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 19 Aug 2017 08:41:17 -0400 Subject: [PATCH 1649/1949] Move MatchingModule to its own file --- project/Build.scala | 11 ----------- project/MatchingModule.scala | 30 ++++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 11 deletions(-) create mode 100644 project/MatchingModule.scala diff --git a/project/Build.scala b/project/Build.scala index 62b3359a43..35c3ff9fde 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -22,17 +22,6 @@ import Dependencies._ import com.typesafe.sbt.web.SbtWeb import com.typesafe.sbt.web.SbtWeb.autoImport._ -/** - * Pattern-matches an attributed file, extracting its module organization, - * name, and revision if available in its attributes. - */ -object MatchingModule { - def unapply(file: Attributed[File]): Option[(String,String,String)] = { - file.get(moduleID.key).map { moduleInfo => - (moduleInfo.organization, moduleInfo.name, moduleInfo.revision) - } - } -} object BuildDef extends Build { diff --git a/project/MatchingModule.scala b/project/MatchingModule.scala new file mode 100644 index 0000000000..dda4cf4d29 --- /dev/null +++ b/project/MatchingModule.scala @@ -0,0 +1,30 @@ +/* + * Copyright 2012-2015 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 sbt._ +import Keys._ + +/** + * Pattern-matches an attributed file, extracting its module organization, + * name, and revision if available in its attributes. + */ +object MatchingModule { + def unapply(file: Attributed[File]): Option[(String,String,String)] = { + file.get(moduleID.key).map { moduleInfo => + (moduleInfo.organization, moduleInfo.name, moduleInfo.revision) + } + } +} From 3f44f9634b0228c7f02331f80931b818199c00c4 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 19 Aug 2017 08:44:47 -0400 Subject: [PATCH 1650/1949] Abstract out the LiftSbtHelpers --- project/Build.scala | 51 +------------------------ project/LiftSbtHelpers.scala | 72 ++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 50 deletions(-) create mode 100644 project/LiftSbtHelpers.scala diff --git a/project/Build.scala b/project/Build.scala index 35c3ff9fde..aad09f84bc 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -18,28 +18,12 @@ import sbt._ import Keys._ import net.liftweb.sbt.LiftBuildPlugin._ import Dependencies._ +import LiftSbtHelpers._ import com.typesafe.sbt.web.SbtWeb import com.typesafe.sbt.web.SbtWeb.autoImport._ - object BuildDef extends Build { - - /** - * A helper that returns the revision and JAR file for a given dependency. - * Useful when trying to attach API doc URI information. - */ - def findManagedDependency(classpath: Seq[Attributed[File]], - organization: String, - name: String): Option[(String,File)] = { - classpath.collectFirst { - case entry @ MatchingModule(moduleOrganization, moduleName, revision) - if moduleOrganization == organization && - moduleName.startsWith(name) => - (revision, entry.data) - } - } - lazy val liftProjects = core ++ web ++ persistence lazy val framework = @@ -224,37 +208,4 @@ object BuildDef extends Build { .settings(parallelExecution in Test := false) - def coreProject = liftProject("core") _ - def webProject = liftProject("web") _ - def persistenceProject = liftProject("persistence") _ - - /** Project definition helper that simplifies creation of `ProjectReference`. - * - * It is a convenience method to create a Lift `ProjectReference` module by having the boilerplate for most common - * activities tucked in. - * - * @param base the base path location of project module. - * @param prefix the prefix of project module. - * @param module the name of the project module. Typically, a project id is of the form lift-`module`. - */ - def liftProject(base: String, prefix: String = "lift-")(module: String): Project = - liftProject(id = if (module.startsWith(prefix)) module else prefix + module, - base = file(base) / module.stripPrefix(prefix)) - - def liftProject(id: String, base: File): Project = { - Project(id, base) - .settings(liftBuildSettings: _*) - .settings(scalacOptions ++= List("-feature", "-language:implicitConversions")) - .settings( - autoAPIMappings := true, - apiMappings ++= { - val cp: Seq[Attributed[File]] = (fullClasspath in Compile).value - - findManagedDependency(cp, "org.scala-lang.modules", "scala-xml").map { - case (revision, file) => - (file -> url("http://www.scala-lang.org/api/" + version)) - }.toMap - } - ) - } } diff --git a/project/LiftSbtHelpers.scala b/project/LiftSbtHelpers.scala new file mode 100644 index 0000000000..dfb30d310d --- /dev/null +++ b/project/LiftSbtHelpers.scala @@ -0,0 +1,72 @@ +/* + * Copyright 2012-2015 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 sbt._ +import Keys._ + +import net.liftweb.sbt.LiftBuildPlugin._ + +object LiftSbtHelpers { + def coreProject = liftProject("core") _ + def webProject = liftProject("web") _ + def persistenceProject = liftProject("persistence") _ + + /** Project definition helper that simplifies creation of `ProjectReference`. + * + * It is a convenience method to create a Lift `ProjectReference` module by having the boilerplate for most common + * activities tucked in. + * + * @param base the base path location of project module. + * @param prefix the prefix of project module. + * @param module the name of the project module. Typically, a project id is of the form lift-`module`. + */ + def liftProject(base: String, prefix: String = "lift-")(module: String): Project = + liftProject(id = if (module.startsWith(prefix)) module else prefix + module, + base = file(base) / module.stripPrefix(prefix)) + + def liftProject(id: String, base: File): Project = { + Project(id, base) + .settings(liftBuildSettings: _*) + .settings(scalacOptions ++= List("-feature", "-language:implicitConversions")) + .settings( + autoAPIMappings := true, + apiMappings ++= { + val cp: Seq[Attributed[File]] = (fullClasspath in Compile).value + + findManagedDependency(cp, "org.scala-lang.modules", "scala-xml").map { + case (revision, file) => + (file -> url("http://www.scala-lang.org/api/" + version)) + }.toMap + } + ) + } + + + /** + * A helper that returns the revision and JAR file for a given dependency. + * Useful when trying to attach API doc URI information. + */ + def findManagedDependency(classpath: Seq[Attributed[File]], + organization: String, + name: String): Option[(String,File)] = { + classpath.collectFirst { + case entry @ MatchingModule(moduleOrganization, moduleName, revision) + if moduleOrganization == organization && + moduleName.startsWith(name) => + (revision, entry.data) + } + } +} From c3675b625673ae531bbb49fdcc086b2f0bc4e316 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 19 Aug 2017 08:56:39 -0400 Subject: [PATCH 1651/1949] Replace Build.scala with build.sbt --- build.sbt | 221 ++++++++++++++++++++++++++++++++++++++++++++ project/Build.scala | 211 ------------------------------------------ 2 files changed, 221 insertions(+), 211 deletions(-) delete mode 100644 project/Build.scala diff --git a/build.sbt b/build.sbt index e83955414c..fd2f46ad1c 100644 --- a/build.sbt +++ b/build.sbt @@ -1,4 +1,5 @@ import Dependencies._ +import LiftSbtHelpers._ organization in ThisBuild := "net.liftweb" version in ThisBuild := "3.2.0-SNAPSHOT" @@ -31,3 +32,223 @@ resolvers in ThisBuild ++= Seq( "snapshots" at "https://oss.sonatype.org/content/repositories/snapshots", "releases" at "https://oss.sonatype.org/content/repositories/releases" ) + +lazy val liftProjects = core ++ web ++ persistence + +lazy val framework = + liftProject("lift-framework", file(".")) + .aggregate(liftProjects: _*) + .settings(scalacOptions in (Compile, doc) ++= Seq(scalaVersion.value).flatMap { + case v if v.startsWith("2.12") => + Seq("-no-java-comments") + case _ => + Seq() + }) //workaround for scala/scala-dev#249 + .settings(aggregatedSetting(sources in(Compile, doc)), + aggregatedSetting(dependencyClasspath in(Compile, doc)), + publishArtifact := false) + +// Core Projects +// ------------- +lazy val core: Seq[ProjectReference] = + Seq(common, actor, markdown, json, json_scalaz7, json_ext, util) + +lazy val common = + coreProject("common") + .settings( + description := "Common Libraties and Utilities", + libraryDependencies ++= Seq(slf4j_api, logback, slf4j_log4j12, scala_xml, scala_parser) + ) + +lazy val actor = + coreProject("actor") + .dependsOn(common) + .settings( + scalacOptions in (Compile, doc) ++= Seq(scalaVersion.value).flatMap { + case v if v.startsWith("2.12") => + Seq("-no-java-comments") + case _ => + Seq() + }, //workaround for scala/scala-dev#249 + description := "Simple Actor", + parallelExecution in Test := false + ) + +lazy val markdown = + coreProject("markdown") + .settings( + description := "Markdown Parser", + parallelExecution in Test := false, + libraryDependencies ++= Seq(scalatest, junit, scala_xml, scala_parser) + ) + +lazy val json = + coreProject("json") + .settings( + description := "JSON Library", + parallelExecution in Test := false, + libraryDependencies ++= Seq(scalap(scalaVersion.value), paranamer) + ) + +lazy val documentationHelpers = + coreProject("documentation-helpers") + .settings(description := "Documentation Helpers") + .dependsOn(util) + +lazy val json_scalaz7 = + coreProject("json-scalaz7") + .dependsOn(json) + .settings( + description := "JSON Library based on Scalaz 7", + libraryDependencies ++= Seq(scalaz7) + ) + +lazy val json_ext = + coreProject("json-ext") + .dependsOn(common, json) + .settings( + description := "Extentions to JSON Library", + libraryDependencies ++= Seq(commons_codec, joda_time, joda_convert) + ) + +lazy val util = + coreProject("util") + .dependsOn(actor, json, markdown) + .settings( + description := "Utilities Library", + parallelExecution in Test := false, + libraryDependencies ++= Seq( + scala_compiler(scalaVersion.value), + joda_time, + joda_convert, + commons_codec, + javamail, + log4j, + htmlparser, + xerces + ) + ) + +// Web Projects +// ------------ +lazy val web: Seq[ProjectReference] = + Seq(testkit, webkit) + +lazy val testkit = + webProject("testkit") + .dependsOn(util) + .settings( + description := "Testkit for Webkit Library", + libraryDependencies ++= Seq(commons_httpclient, servlet_api) + ) + +lazy val webkit = + webProject("webkit") + .dependsOn(util, testkit % "provided") + .settings( + description := "Webkit Library", + parallelExecution in Test := false, + libraryDependencies ++= Seq( + commons_fileupload, + rhino, + servlet_api, + specs2.copy(configurations = Some("provided")), + specs2Matchers.copy(configurations = Some("provided")), + jetty6, + jwebunit, + mockito_all, + jquery, + jasmineCore, + jasmineAjax + ), + initialize in Test <<= (sourceDirectory in Test) { src => + System.setProperty("net.liftweb.webapptest.src.test.webapp", (src / "webapp").absString) + }, + unmanagedSourceDirectories in Compile <+= (sourceDirectory in Compile, scalaBinaryVersion) { + (sourceDirectory, binaryVersion) => + sourceDirectory / ("scala_" + binaryVersion) + }, + unmanagedSourceDirectories in Test <+= (sourceDirectory in Test, scalaBinaryVersion) { + (sourceDirectory, binaryVersion) => + sourceDirectory / ("scala_" + binaryVersion) + }, + (compile in Compile) <<= (compile in Compile) dependsOn (WebKeys.assets), + /** + * This is to ensure that the tests in net.liftweb.webapptest run last + * so that other tests (MenuSpec in particular) run before the SiteMap + * is set. + */ + testGrouping in Test <<= (definedTests in Test).map { tests => + import Tests._ + + val (webapptests, others) = tests.partition { test => + test.name.startsWith("net.liftweb.webapptest") + } + + Seq( + new Group("others", others, InProcess), + new Group("webapptests", webapptests, InProcess) + ) + }, + + scalacOptions in (Compile, doc) ++= { + if (scalaVersion.value.startsWith("2.12")) { + Seq("-no-java-comments") + } else { + Seq() + } //workaround for scala/scala-dev#249 + } + ) + .settings(yuiCompressor.Plugin.yuiSettings: _*) + .enablePlugins(SbtWeb) + +// Persistence Projects +// -------------------- +lazy val persistence: Seq[ProjectReference] = + Seq(db, proto, mapper, record, squeryl_record, mongodb, mongodb_record) + +lazy val db = + persistenceProject("db") + .dependsOn(util, webkit) + .settings(libraryDependencies += mockito_all) + +lazy val proto = + persistenceProject("proto") + .dependsOn(webkit) + +lazy val mapper = + persistenceProject("mapper") + .dependsOn(db, proto) + .settings( + description := "Mapper Library", + parallelExecution in Test := false, + libraryDependencies ++= Seq(h2, derby), + initialize in Test <<= (crossTarget in Test) { ct => + System.setProperty("derby.stream.error.file", (ct / "derby.log").absolutePath) + } + ) + +lazy val record = + persistenceProject("record") + .dependsOn(proto) + +lazy val squeryl_record = + persistenceProject("squeryl-record") + .dependsOn(record, db) + .settings(libraryDependencies ++= Seq(h2, squeryl)) + +lazy val mongodb = + persistenceProject("mongodb") + .dependsOn(json_ext, util) + .settings( + parallelExecution in Test := false, + libraryDependencies ++= Seq(mongo_java_driver, mongo_java_driver_async), + initialize in Test <<= (resourceDirectory in Test) { rd => + System.setProperty("java.util.logging.config.file", (rd / "logging.properties").absolutePath) + } + ) + +lazy val mongodb_record = + persistenceProject("mongodb-record") + .dependsOn(record, mongodb) + .settings(parallelExecution in Test := false) diff --git a/project/Build.scala b/project/Build.scala deleted file mode 100644 index aad09f84bc..0000000000 --- a/project/Build.scala +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Copyright 2012-2015 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 sbt._ -import Keys._ -import net.liftweb.sbt.LiftBuildPlugin._ -import Dependencies._ -import LiftSbtHelpers._ - -import com.typesafe.sbt.web.SbtWeb -import com.typesafe.sbt.web.SbtWeb.autoImport._ - -object BuildDef extends Build { - lazy val liftProjects = core ++ web ++ persistence - - lazy val framework = - liftProject("lift-framework", file(".")) - .aggregate(liftProjects: _*) - .settings(scalacOptions in (Compile, doc) ++= Seq(scalaVersion.value).flatMap { - case v if v.startsWith("2.12") => - Seq("-no-java-comments") - case _ => - Seq() - }) //workaround for scala/scala-dev#249 - .settings(aggregatedSetting(sources in(Compile, doc)), - aggregatedSetting(dependencyClasspath in(Compile, doc)), - publishArtifact := false) - - // Core Projects - // ------------- - lazy val core: Seq[ProjectReference] = - Seq(common, actor, markdown, json, json_scalaz7, json_ext, util) - - lazy val common = - coreProject("common") - .settings(description := "Common Libraties and Utilities", - libraryDependencies ++= Seq(slf4j_api, logback, slf4j_log4j12, scala_xml, scala_parser) - ) - - lazy val actor = - coreProject("actor") - .dependsOn(common) - .settings(scalacOptions in (Compile, doc) ++= Seq(scalaVersion.value).flatMap { - case v if v.startsWith("2.12") => - Seq("-no-java-comments") - case _ => - Seq() - }) //workaround for scala/scala-dev#249 - .settings(description := "Simple Actor", - parallelExecution in Test := false) - - lazy val markdown = - coreProject("markdown") - .settings(description := "Markdown Parser", - parallelExecution in Test := false, - libraryDependencies <++= scalaVersion { sv => Seq(scalatest, junit, scala_xml, scala_parser) } - ) - - lazy val json = - coreProject("json") - .settings(description := "JSON Library", - parallelExecution in Test := false, - libraryDependencies <++= scalaVersion { sv => Seq(scalap(sv), paranamer) }) - - lazy val documentationHelpers = - coreProject("documentation-helpers") - .settings(description := "Documentation Helpers") - .dependsOn(util) - - lazy val json_scalaz7 = - coreProject("json-scalaz7") - .dependsOn(json) - .settings(description := "JSON Library based on Scalaz 7", - libraryDependencies ++= Seq(scalaz7)) - - lazy val json_ext = - coreProject("json-ext") - .dependsOn(common, json) - .settings(description := "Extentions to JSON Library", - libraryDependencies ++= Seq(commons_codec, joda_time, joda_convert)) - - lazy val util = - coreProject("util") - .dependsOn(actor, json, markdown) - .settings(description := "Utilities Library", - parallelExecution in Test := false, - libraryDependencies <++= scalaVersion {sv => Seq(scala_compiler(sv), joda_time, - joda_convert, commons_codec, javamail, log4j, htmlparser, xerces)} - ) - - // Web Projects - // ------------ - lazy val web: Seq[ProjectReference] = - Seq(testkit, webkit) - - lazy val testkit = - webProject("testkit") - .dependsOn(util) - .settings(description := "Testkit for Webkit Library", - libraryDependencies ++= Seq(commons_httpclient, servlet_api)) - lazy val webkit = - webProject("webkit") - .dependsOn(util, testkit % "provided") - .settings(scalacOptions in (Compile, doc) ++= { - if (scalaVersion.value.startsWith("2.12")) { - Seq("-no-java-comments") - } else { - Seq() - } - }) //workaround for scala/scala-dev#249 - .settings(libraryDependencies ++= Seq(mockito_all, jquery, jasmineCore, jasmineAjax)) - .settings(yuiCompressor.Plugin.yuiSettings: _*) - .settings(description := "Webkit Library", - parallelExecution in Test := false, - libraryDependencies <++= scalaVersion { sv => - Seq(commons_fileupload, rhino, servlet_api, specs2.copy(configurations = Some("provided")), specs2Matchers.copy(configurations = Some("provided")), jetty6, - jwebunit) - }, - initialize in Test <<= (sourceDirectory in Test) { src => - System.setProperty("net.liftweb.webapptest.src.test.webapp", (src / "webapp").absString) - }, - unmanagedSourceDirectories in Compile <+= (sourceDirectory in Compile, scalaBinaryVersion) { - (sourceDirectory, binaryVersion) => - sourceDirectory / ("scala_" + binaryVersion) - }, - unmanagedSourceDirectories in Test <+= (sourceDirectory in Test, scalaBinaryVersion) { - (sourceDirectory, binaryVersion) => - sourceDirectory / ("scala_" + binaryVersion) - }, - (compile in Compile) <<= (compile in Compile) dependsOn (WebKeys.assets), - /** - * This is to ensure that the tests in net.liftweb.webapptest run last - * so that other tests (MenuSpec in particular) run before the SiteMap - * is set. - */ - testGrouping in Test <<= (definedTests in Test).map { tests => - import Tests._ - - val (webapptests, others) = tests.partition { test => - test.name.startsWith("net.liftweb.webapptest") - } - - Seq( - new Group("others", others, InProcess), - new Group("webapptests", webapptests, InProcess) - ) - }) - .enablePlugins(SbtWeb) - - // Persistence Projects - // -------------------- - lazy val persistence: Seq[ProjectReference] = - Seq(db, proto, mapper, record, squeryl_record, mongodb, mongodb_record) - - lazy val db = - persistenceProject("db") - .dependsOn(util, webkit) - .settings(libraryDependencies += mockito_all) - - lazy val proto = - persistenceProject("proto") - .dependsOn(webkit) - - lazy val mapper = - persistenceProject("mapper") - .dependsOn(db, proto) - .settings(description := "Mapper Library", - parallelExecution in Test := false, - libraryDependencies ++= Seq(h2, derby), - initialize in Test <<= (crossTarget in Test) { ct => - System.setProperty("derby.stream.error.file", (ct / "derby.log").absolutePath) - }) - - lazy val record = - persistenceProject("record") - .dependsOn(proto) - - lazy val squeryl_record = - persistenceProject("squeryl-record") - .dependsOn(record, db) - .settings(libraryDependencies ++= Seq(h2, squeryl)) - - lazy val mongodb = - persistenceProject("mongodb") - .dependsOn(json_ext, util) - .settings(parallelExecution in Test := false, - libraryDependencies ++= Seq(mongo_java_driver, mongo_java_driver_async), - initialize in Test <<= (resourceDirectory in Test) { rd => - System.setProperty("java.util.logging.config.file", (rd / "logging.properties").absolutePath) - }) - - lazy val mongodb_record = - persistenceProject("mongodb-record") - .dependsOn(record, mongodb) - .settings(parallelExecution in Test := false) - - -} From fa78d5200ff9569ef2f63470fead8a6511f89e1e Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Thu, 24 Aug 2017 22:52:17 -0400 Subject: [PATCH 1652/1949] Provide context path to comet session reload function --- web/webkit/src/main/resources/toserve/lift.js | 12 ++++++++---- .../src/main/scala/net/liftweb/http/LiftRules.scala | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/web/webkit/src/main/resources/toserve/lift.js b/web/webkit/src/main/resources/toserve/lift.js index 1b1e5f1c0a..86dc28ab69 100644 --- a/web/webkit/src/main/resources/toserve/lift.js +++ b/web/webkit/src/main/resources/toserve/lift.js @@ -76,8 +76,12 @@ }, cometGetTimeout: 140000, cometFailureRetryTimeout: 10000, - cometOnSessionLost: function() { - window.location.href = "/"; + cometOnSessionLost: function(contextPath) { + if (contextPath === null || contextPath === undefined) { + window.location.href = "/"; + } else { + window.location.href = contextPath; + } }, cometServer: null, cometOnError: function(e) { @@ -711,7 +715,7 @@ if (typeof elementOrId === 'string') { element = document.getElementById(elementOrId); } - + // This is a Lift addition to allow return false to properly do // cross-browser preventDefault/stopPropagation/etc work. function normalizeEventReturn(event) { @@ -722,7 +726,7 @@ event.stopPropagation(); } } - + return result; } diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index cc68c9b18f..89d92f5bcb 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -775,7 +775,7 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { * lift.cometOnSessionLost reloads the current page by default. */ val noCometSessionCmd = new FactoryMaker[JsCmd]( - () => JsCmds.Run("lift.cometOnSessionLost()") + () => JsCmds.Run(s"lift.cometOnSessionLost('${S.contextPath}')") ) {} /** From 0411f0270dc4b3fe1f48431d70df43be4c8243c1 Mon Sep 17 00:00:00 2001 From: Riccardo Sirigu Date: Sun, 27 Aug 2017 22:27:17 +0200 Subject: [PATCH 1653/1949] Not crawlable email address --- contributors.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contributors.md b/contributors.md index 221c5359ea..62b1e315d1 100644 --- a/contributors.md +++ b/contributors.md @@ -262,4 +262,4 @@ mroocoo at gmail dot com Riccardo Sirigu ### Email: ### -me@riccardosirigu.com \ No newline at end of file +me at riccardosirigu dot com From af1601f2cce4eb3a1b06d50d18626581fa582cba Mon Sep 17 00:00:00 2001 From: Josef Vlach Date: Mon, 28 Aug 2017 21:04:26 +0100 Subject: [PATCH 1654/1949] Pass contextPath to settings.cometOnSessionLost --- web/webkit/src/main/resources/toserve/lift.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/webkit/src/main/resources/toserve/lift.js b/web/webkit/src/main/resources/toserve/lift.js index 86dc28ab69..1da92f990e 100644 --- a/web/webkit/src/main/resources/toserve/lift.js +++ b/web/webkit/src/main/resources/toserve/lift.js @@ -606,8 +606,8 @@ }, calcAjaxUrl: calcAjaxUrl, registerComets: registerComets, - cometOnSessionLost: function() { - settings.cometOnSessionLost(); + cometOnSessionLost: function(contextPath) { + settings.cometOnSessionLost(contextPath); }, cometOnError: function(e) { settings.cometOnError(e); From f2866d92884c61a52b39b08183ca53f1f2f9f781 Mon Sep 17 00:00:00 2001 From: Noel Kennedy Date: Mon, 26 Nov 2012 16:31:35 +0000 Subject: [PATCH 1655/1949] Added an optional feature which times the performance of snippet evaluation --- contributors.md | 6 +++ .../scala/net/liftweb/http/LiftSession.scala | 5 ++- .../scala/net/liftweb/http/SnippetTimer.scala | 39 +++++++++++++++++++ 3 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 web/webkit/src/main/scala/net/liftweb/http/SnippetTimer.scala diff --git a/contributors.md b/contributors.md index 62b1e315d1..b87118128f 100644 --- a/contributors.md +++ b/contributors.md @@ -263,3 +263,9 @@ Riccardo Sirigu ### Email: ### me at riccardosirigu dot com + +### Name: ### +Noel Kennedy + +### Email: ### +nkennedy at rvc dot ac dot uk diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index 88d87c350b..b02f688556 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -1642,10 +1642,11 @@ class LiftSession(private[http] val _contextPath: String, val underlyingId: Stri val ret: NodeSeq = try { - snippetName.map{snippet => val (cls, method) = splitColonPair(snippet) - S.doSnippet(snippet)( + val snippetTimer = SnippetTimer.evaluate(snippet) + + snippetTimer apply S.doSnippet(snippet)( runWhitelist(snippet, cls, method, kids){(S.locateMappedSnippet(snippet).map(_(kids)) or locSnippet(snippet)).openOr( S.locateSnippet(snippet).map(_(kids)) openOr { diff --git a/web/webkit/src/main/scala/net/liftweb/http/SnippetTimer.scala b/web/webkit/src/main/scala/net/liftweb/http/SnippetTimer.scala new file mode 100644 index 0000000000..d37137350a --- /dev/null +++ b/web/webkit/src/main/scala/net/liftweb/http/SnippetTimer.scala @@ -0,0 +1,39 @@ +package net.liftweb.http + +import xml.NodeSeq +import net.liftweb.util.TimeHelpers._ +import net.liftweb.util.Props + +/** + * Times the performance of evaluating of individual snippets + */ +object SnippetTimer { + + /** + * The configured snippet timing function. Defaults to not timing snippets. + */ + lazy val evaluate: String => (=> NodeSeq) => NodeSeq = { + if(Props.getBool("run.timesnippets",defVal = false)) + timeSnippet _ + else + noTiming _ + } + + /** + * Times the evaluation of a snippet + * @param snippetName String The name of the snippet + * @param f The snippet function + * @return NodeSeq The result of evaluating f + */ + def timeSnippet(snippetName:String)(f: => NodeSeq) = { + logTime("Snippet %s (and children) evaluation" format snippetName, f) + } + + /** + * A function which doesn't time a snippet but just evaluates it + * @param snippetName String Name of the snippet which is ignored + * @param f The snippet function + * @return NodeSeq The result of evaluating f + */ + def noTiming(snippetName:String)(f: => NodeSeq) = f +} From b8c8211758c0e1aae567ca3c5c9165a1b216d7a8 Mon Sep 17 00:00:00 2001 From: Noel Kennedy Date: Sat, 8 Dec 2012 11:45:23 +0000 Subject: [PATCH 1656/1949] Snippet timer's message was incorrect. This version doesn't include timings for a snippet's children. --- web/webkit/src/main/scala/net/liftweb/http/SnippetTimer.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/SnippetTimer.scala b/web/webkit/src/main/scala/net/liftweb/http/SnippetTimer.scala index d37137350a..4e3f7107a2 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/SnippetTimer.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/SnippetTimer.scala @@ -26,7 +26,7 @@ object SnippetTimer { * @return NodeSeq The result of evaluating f */ def timeSnippet(snippetName:String)(f: => NodeSeq) = { - logTime("Snippet %s (and children) evaluation" format snippetName, f) + logTime("Snippet %s evaluation" format snippetName, f) } /** From 3e6aa73981e286f224c889602ee9b8b380ba095f Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Tue, 29 Aug 2017 17:43:29 -0400 Subject: [PATCH 1657/1949] Forgive me, but this was screwing up all my syntax highlighting --- web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index b71769d2b9..56dad603e5 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -647,10 +647,10 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { * * The HTML Lift emitted would then look like: * - * {{{ + *
                  * Do something!
              -   * }}}
              +   * 
              * * This makes it possible to replace the old CSS with with similar * matching for the `data-lift-removed-attributes` attribute: From fed8d2b13d4407d31fb3582fb1f3306216607c17 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Tue, 29 Aug 2017 17:44:08 -0400 Subject: [PATCH 1658/1949] Convert SnippetTimer to an interface with a few default impls --- .../scala/net/liftweb/http/SnippetTimer.scala | 44 +++++++------------ 1 file changed, 17 insertions(+), 27 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/SnippetTimer.scala b/web/webkit/src/main/scala/net/liftweb/http/SnippetTimer.scala index 4e3f7107a2..2cef19769d 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/SnippetTimer.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/SnippetTimer.scala @@ -5,35 +5,25 @@ import net.liftweb.util.TimeHelpers._ import net.liftweb.util.Props /** - * Times the performance of evaluating of individual snippets + * A snippet timer is a general interface for timing snippets. A few default implementations are + * provided and can be selected by setting LiftRules.snippetTimer as you need. */ -object SnippetTimer { +trait SnippetTimer { + def timeSnippet(snippetName: String)(snippetFunc: => NodeSeq): NodeSeq +} - /** - * The configured snippet timing function. Defaults to not timing snippets. - */ - lazy val evaluate: String => (=> NodeSeq) => NodeSeq = { - if(Props.getBool("run.timesnippets",defVal = false)) - timeSnippet _ - else - noTiming _ - } +/** + * A SnippetTimer that does not do anything. + */ +object NoOpSnippetTimer extends SnippetTimer { + override def timeSnippet(snippetName: String)(snippetFunc: => NodeSeq): NodeSeq = snippetFunc +} - /** - * Times the evaluation of a snippet - * @param snippetName String The name of the snippet - * @param f The snippet function - * @return NodeSeq The result of evaluating f - */ - def timeSnippet(snippetName:String)(f: => NodeSeq) = { - logTime("Snippet %s evaluation" format snippetName, f) +/** + * A SnippetTimer that logs its timings to the console. + */ +object LoggingSnippetTimer extends SnippetTimer { + override def timeSnippet(snippetName: String)(snippetFunc: => NodeSeq): NodeSeq = { + logTime(s"Snippet $snippetName evaluation", snippetFunc) } - - /** - * A function which doesn't time a snippet but just evaluates it - * @param snippetName String Name of the snippet which is ignored - * @param f The snippet function - * @return NodeSeq The result of evaluating f - */ - def noTiming(snippetName:String)(f: => NodeSeq) = f } From 713e913d6f237b865f7b8700109959062688d8e0 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Tue, 29 Aug 2017 17:44:25 -0400 Subject: [PATCH 1659/1949] Wire SnippetTimer in as a LiftRule and re-indent half of LiftSession --- .../scala/net/liftweb/http/LiftRules.scala | 3 + .../scala/net/liftweb/http/LiftSession.scala | 253 +++++++++--------- 2 files changed, 131 insertions(+), 125 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 56dad603e5..792ada00f7 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -852,6 +852,9 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { @volatile var snippetNamesToSearch: FactoryMaker[String => List[String]] = new FactoryMaker(() => (name: String) => name :: Nil) {} + + val snippetTimer = new FactoryMaker[SnippetTimer](() => NoOpSnippetTimer){} + /** * Implementation for snippetNamesToSearch that looks first in a package named by taking the current template path. * For example, suppose the following is configured in Boot: diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index b02f688556..22bb3ef26e 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -1644,143 +1644,146 @@ class LiftSession(private[http] val _contextPath: String, val underlyingId: Stri try { snippetName.map{snippet => val (cls, method) = splitColonPair(snippet) - val snippetTimer = SnippetTimer.evaluate(snippet) + val snippetTimer = LiftRules.snippetTimer.vend + + snippetTimer.timeSnippet(snippet) { + S.doSnippet(snippet)( + runWhitelist(snippet, cls, method, kids){(S.locateMappedSnippet(snippet).map(_(kids)) or + locSnippet(snippet)).openOr( + S.locateSnippet(snippet).map(_(kids)) openOr { + + (locateAndCacheSnippet(cls)) match { + // deal with a stateless request when a snippet has + // different behavior in stateless mode + case Full(inst: StatelessBehavior) if !stateful_? => + if (inst.statelessDispatch.isDefinedAt(method)) + inst.statelessDispatch(method)(kids) else NodeSeq.Empty + + case Full(inst: StatefulSnippet) if !stateful_? => + reportSnippetError(page, snippetName, + LiftRules.SnippetFailures.StateInStateless, + NodeSeq.Empty, + wholeTag) + + case Full(inst: StatefulSnippet) => + if (inst.dispatch.isDefinedAt(method)) { + val res = inst.dispatch(method)(kids) + + inst.mergeIntoForm(isForm, res, SHtml.hidden(() => inst.registerThisSnippet)) + /* (if (isForm && !res.isEmpty) SHtml.hidden(() => inst.registerThisSnippet) else NodeSeq.Empty) ++ + res*/ + } else reportSnippetError(page, snippetName, + LiftRules.SnippetFailures.StatefulDispatchNotMatched, + NodeSeq.Empty, + wholeTag) + + case Full(inst: DispatchSnippet) => + if (inst.dispatch.isDefinedAt(method)) inst.dispatch(method)(kids) + else reportSnippetError(page, snippetName, + LiftRules.SnippetFailures.StatefulDispatchNotMatched, + NodeSeq.Empty, + wholeTag) + + case Full(inst) => { + def gotIt: Box[NodeSeq] = + for { + meth <- tryo(inst.getClass.getMethod(method)) + if classOf[CssBindFunc].isAssignableFrom(meth.getReturnType) + } yield meth.invoke(inst).asInstanceOf[CssBindFunc].apply(kids) + + import java.lang.reflect.{Type, ParameterizedType} + + def isFunc1(tpe: Type): Boolean = tpe match { + case null => false + case c: Class[_] => classOf[Function1[_, _]] isAssignableFrom c + case _ => false + } - snippetTimer apply S.doSnippet(snippet)( - runWhitelist(snippet, cls, method, kids){(S.locateMappedSnippet(snippet).map(_(kids)) or - locSnippet(snippet)).openOr( - S.locateSnippet(snippet).map(_(kids)) openOr { + def isNodeSeq(tpe: Type): Boolean = tpe match { + case null => false + case c: Class[_] => classOf[NodeSeq] isAssignableFrom c + case _ => false + } - (locateAndCacheSnippet(cls)) match { - // deal with a stateless request when a snippet has - // different behavior in stateless mode - case Full(inst: StatelessBehavior) if !stateful_? => - if (inst.statelessDispatch.isDefinedAt(method)) - inst.statelessDispatch(method)(kids) else NodeSeq.Empty + def testGeneric(tpe: Type): Boolean = tpe match { + case null => false + case pt: ParameterizedType => + if (isFunc1(pt.getRawType) && + pt.getActualTypeArguments.length == 2 && + isNodeSeq(pt.getActualTypeArguments()(0)) && + isNodeSeq(pt.getActualTypeArguments()(1))) + true + else testGeneric(pt.getRawType) + + case clz: Class[_] => + if (clz == classOf[Object]) false + else clz.getGenericInterfaces.find(testGeneric) match { + case Some(_) => true + case _ => testGeneric(clz.getSuperclass) + } + + case _ => false + } - case Full(inst: StatefulSnippet) if !stateful_? => - reportSnippetError(page, snippetName, - LiftRules.SnippetFailures.StateInStateless, - NodeSeq.Empty, - wholeTag) + def isFuncNodeSeq(meth: Method): Boolean = { + (classOf[Function1[_, _]] isAssignableFrom meth.getReturnType) && + testGeneric(meth.getGenericReturnType) + } - case Full(inst: StatefulSnippet) => - if (inst.dispatch.isDefinedAt(method)) { - val res = inst.dispatch(method)(kids) - inst.mergeIntoForm(isForm, res, SHtml.hidden(() => inst.registerThisSnippet)) - /* (if (isForm && !res.isEmpty) SHtml.hidden(() => inst.registerThisSnippet) else NodeSeq.Empty) ++ - res*/ - } else reportSnippetError(page, snippetName, - LiftRules.SnippetFailures.StatefulDispatchNotMatched, + def nodeSeqFunc: Box[NodeSeq] = + for { + meth <- tryo(inst.getClass.getMethod(method)) + if isFuncNodeSeq(meth) + } yield meth.invoke(inst).asInstanceOf[Function1[NodeSeq, + NodeSeq]].apply(kids) + + + (gotIt or nodeSeqFunc) openOr { + + val ar: Array[AnyRef] = List(Group(kids)).toArray + ((Helpers.invokeMethod(inst.getClass, inst, method, ar, Array(classOf[NodeSeq]))) or + Helpers.invokeMethod(inst.getClass, inst, method)) match { + case CheckNodeSeq(md) => md + case it => + val intersection = if (Props.devMode) { + val methodNames = inst.getClass.getMethods().map(_.getName).toList.distinct + val methodAlts = List(method, Helpers.camelify(method), + Helpers.camelifyMethod(method)) + methodNames intersect methodAlts + } else Nil + + reportSnippetError(page, snippetName, + LiftRules.SnippetFailures.MethodNotFound, + if (intersection.isEmpty) NodeSeq.Empty + else +
              There are possible matching methods ( + {intersection} + ), + but none has the required signature: +
              def
              +                                    {method}
              +                                    (in: NodeSeq): NodeSeq
              +
              , + wholeTag) + } + } + } + case Failure(_, Full(exception), _) => logger.warn("Snippet instantiation error", exception) + reportSnippetError(page, snippetName, + LiftRules.SnippetFailures.InstantiationException, NodeSeq.Empty, wholeTag) - case Full(inst: DispatchSnippet) => - if (inst.dispatch.isDefinedAt(method)) inst.dispatch(method)(kids) - else reportSnippetError(page, snippetName, - LiftRules.SnippetFailures.StatefulDispatchNotMatched, + case _ => reportSnippetError(page, snippetName, + LiftRules.SnippetFailures.ClassNotFound, NodeSeq.Empty, wholeTag) - case Full(inst) => { - def gotIt: Box[NodeSeq] = - for { - meth <- tryo(inst.getClass.getMethod(method)) - if classOf[CssBindFunc].isAssignableFrom(meth.getReturnType) - } yield meth.invoke(inst).asInstanceOf[CssBindFunc].apply(kids) - - import java.lang.reflect.{Type, ParameterizedType} - - def isFunc1(tpe: Type): Boolean = tpe match { - case null => false - case c: Class[_] => classOf[Function1[_, _]] isAssignableFrom c - case _ => false - } - - def isNodeSeq(tpe: Type): Boolean = tpe match { - case null => false - case c: Class[_] => classOf[NodeSeq] isAssignableFrom c - case _ => false - } - - def testGeneric(tpe: Type): Boolean = tpe match { - case null => false - case pt: ParameterizedType => - if (isFunc1(pt.getRawType) && - pt.getActualTypeArguments.length == 2 && - isNodeSeq(pt.getActualTypeArguments()(0)) && - isNodeSeq(pt.getActualTypeArguments()(1))) - true - else testGeneric(pt.getRawType) - - case clz: Class[_] => - if (clz == classOf[Object]) false - else clz.getGenericInterfaces.find(testGeneric) match { - case Some(_) => true - case _ => testGeneric(clz.getSuperclass) - } - - case _ => false - } - - def isFuncNodeSeq(meth: Method): Boolean = { - (classOf[Function1[_, _]] isAssignableFrom meth.getReturnType) && - testGeneric(meth.getGenericReturnType) - } - - - def nodeSeqFunc: Box[NodeSeq] = - for { - meth <- tryo(inst.getClass.getMethod(method)) - if isFuncNodeSeq(meth) - } yield meth.invoke(inst).asInstanceOf[Function1[NodeSeq, - NodeSeq]].apply(kids) - - - (gotIt or nodeSeqFunc) openOr { - - val ar: Array[AnyRef] = List(Group(kids)).toArray - ((Helpers.invokeMethod(inst.getClass, inst, method, ar, Array(classOf[NodeSeq]))) or - Helpers.invokeMethod(inst.getClass, inst, method)) match { - case CheckNodeSeq(md) => md - case it => - val intersection = if (Props.devMode) { - val methodNames = inst.getClass.getMethods().map(_.getName).toList.distinct - val methodAlts = List(method, Helpers.camelify(method), - Helpers.camelifyMethod(method)) - methodNames intersect methodAlts - } else Nil - - reportSnippetError(page, snippetName, - LiftRules.SnippetFailures.MethodNotFound, - if (intersection.isEmpty) NodeSeq.Empty - else -
              There are possible matching methods ( - {intersection} - ), - but none has the required signature: -
              def
              -                                  {method}
              -                                  (in: NodeSeq): NodeSeq
              -
              , - wholeTag) - } - } } - case Failure(_, Full(exception), _) => logger.warn("Snippet instantiation error", exception) - reportSnippetError(page, snippetName, - LiftRules.SnippetFailures.InstantiationException, - NodeSeq.Empty, - wholeTag) - - case _ => reportSnippetError(page, snippetName, - LiftRules.SnippetFailures.ClassNotFound, - NodeSeq.Empty, - wholeTag) - - } - })})}.openOr { + })}) + } + } openOr { reportSnippetError(page, snippetName, LiftRules.SnippetFailures.NoNameSpecified, NodeSeq.Empty, From be3ed14e7850574402c41b681ca008a7077e17b4 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Tue, 29 Aug 2017 17:46:25 -0400 Subject: [PATCH 1660/1949] Add LiftRules docs for SnippetTimer. --- .../src/main/scala/net/liftweb/http/LiftRules.scala | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 792ada00f7..a67e5e58d9 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -853,6 +853,15 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { new FactoryMaker(() => (name: String) => name :: Nil) {} + /** + * Snippet timers are used to time and record the execution time of snippets. We provide + * two default implementations for you: + * - NoOpSnippetTimer that does nothing + * - LoggingSnippetTimer that logs snippet times. + * + * Since this is a FactoryMaker you can programmatically override it for an individual request + * or session as you see fit. You can also implement your own timer! + */ val snippetTimer = new FactoryMaker[SnippetTimer](() => NoOpSnippetTimer){} /** From be7466191e20b6ea485b50299294047ec1e6755f Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Tue, 29 Aug 2017 18:11:55 -0400 Subject: [PATCH 1661/1949] Add an onShutdown to buildRoundtrip --- .../src/main/scala/net/liftweb/http/LiftSession.scala | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index 88d87c350b..c8d25143cd 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -2668,10 +2668,10 @@ class LiftSession(private[http] val _contextPath: String, val underlyingId: Stri * marshalled to the server and the code is executed on the server. * The result can be an item (JValue) or a Stream of Items. * - * If the - * The // HERE + * @param info The RoundTripInfo to build on. + * @param onShutdown A callback that is invoked when the underlying comet is shut down. */ - def buildRoundtrip(info: Seq[RoundTripInfo]): JsExp = { + def buildRoundtrip(info: Seq[RoundTripInfo], onShutdown: ()=>Unit = ()=>()): JsExp = { testStatefulFeature{ @@ -2720,6 +2720,8 @@ class LiftSession(private[http] val _contextPath: String, val underlyingId: Stri case _ => } + + override def localShutdown(): Unit = onShutdown() } nasyncComponents.put(CometId(ca.theType openOr "Roundtrip Comet Actor", ca.name), ca) From 4ac766af2d5534d5d3c8b50b81b775d3c79b4a3b Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Tue, 29 Aug 2017 18:30:50 -0400 Subject: [PATCH 1662/1949] Support limiting MetaProtoExtendedSession cookies to context path --- .../net/liftweb/mapper/ProtoExtendedSession.scala | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/persistence/mapper/src/main/scala/net/liftweb/mapper/ProtoExtendedSession.scala b/persistence/mapper/src/main/scala/net/liftweb/mapper/ProtoExtendedSession.scala index 73468299e7..e1fb46c3ea 100644 --- a/persistence/mapper/src/main/scala/net/liftweb/mapper/ProtoExtendedSession.scala +++ b/persistence/mapper/src/main/scala/net/liftweb/mapper/ProtoExtendedSession.scala @@ -59,8 +59,12 @@ trait UserIdAsString { def userIdAsString: String } +trait ProtoSessionCookiePath { + def sessionCookiePath: String = "/" +} + trait MetaProtoExtendedSession[T <: ProtoExtendedSession[T]] extends -KeyedMetaMapper[Long, T] { +KeyedMetaMapper[Long, T] with ProtoSessionCookiePath { self: T => def CookieName = "ext_id" @@ -92,7 +96,7 @@ KeyedMetaMapper[Long, T] { val inst = create.userId(uid.userIdAsString).saveMe val cookie = HTTPCookie(CookieName, inst.cookieId.get). setMaxAge(((inst.expiration.get - millis) / 1000L).toInt). - setPath("/") + setPath(sessionCookiePath) S.addCookie(cookie) } @@ -120,10 +124,13 @@ KeyedMetaMapper[Long, T] { case Full(es) => logUserIdIn(es.userId.get) case _ => } - + case _ => } } } } +trait ContextPathExtendedCookie extends ProtoSessionCookiePath { + override def sessionCookiePath = S.contextPath +} From 7d728136db5cc0ad26bdc364aa5d626428d97dad Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Tue, 29 Aug 2017 23:06:20 -0400 Subject: [PATCH 1663/1949] Revert "Forgive me, but this was screwing up all my syntax highlighting" This reverts commit 3e6aa73981e286f224c889602ee9b8b380ba095f. --- web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index a67e5e58d9..7112e8923a 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -647,10 +647,10 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { * * The HTML Lift emitted would then look like: * - *
              +   * {{{
                  * Do something!
              -   * 
              + * }}} * * This makes it possible to replace the old CSS with with similar * matching for the `data-lift-removed-attributes` attribute: From aaee41a9f27482a11cc62d8fa5f8588734394709 Mon Sep 17 00:00:00 2001 From: Josef Vlach Date: Wed, 30 Aug 2017 22:10:06 +0100 Subject: [PATCH 1664/1949] Update of contributors.md --- contributors.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/contributors.md b/contributors.md index 221c5359ea..3a557545ac 100644 --- a/contributors.md +++ b/contributors.md @@ -262,4 +262,10 @@ mroocoo at gmail dot com Riccardo Sirigu ### Email: ### -me@riccardosirigu.com \ No newline at end of file +me@riccardosirigu.com + +### Name: ### +Josef Vlach + +### Email: ### +vlach.josef at gmail dot com From ffe5359cc87fcb5065c324dfe4677aca5d8b6840 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Wed, 30 Aug 2017 20:33:27 -0400 Subject: [PATCH 1665/1949] Switch to using a LiftRulesGuardedSetting for snippetTimer This is to avoid the cost of invoking FactoryMaker in the situation that the timer is disabled. --- web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala | 2 +- web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 7112e8923a..79ac130eda 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -862,7 +862,7 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { * Since this is a FactoryMaker you can programmatically override it for an individual request * or session as you see fit. You can also implement your own timer! */ - val snippetTimer = new FactoryMaker[SnippetTimer](() => NoOpSnippetTimer){} + val snippetTimer = new LiftRulesGuardedSetting[Option[FactoryMaker[SnippetTimer]]]("snippetTimer", None) /** * Implementation for snippetNamesToSearch that looks first in a package named by taking the current template path. diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index 22bb3ef26e..24cd096072 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -1644,7 +1644,7 @@ class LiftSession(private[http] val _contextPath: String, val underlyingId: Stri try { snippetName.map{snippet => val (cls, method) = splitColonPair(snippet) - val snippetTimer = LiftRules.snippetTimer.vend + val snippetTimer = LiftRules.snippetTimer.get.map(_.vend).getOrElse(NoOpSnippetTimer) snippetTimer.timeSnippet(snippet) { S.doSnippet(snippet)( From 12f5fd3384b765628d6ecd1b2326ab92026c1de5 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Wed, 30 Aug 2017 20:42:46 -0400 Subject: [PATCH 1666/1949] Move cache defaulting to LiftRules --- .../src/main/scala/net/liftweb/http/LiftRules.scala | 8 +++++++- .../scala/net/liftweb/http/provider/HTTPProvider.scala | 5 ----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index b71769d2b9..4bc2d03b28 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -1917,7 +1917,13 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { private[http] def withMimeHeaders[T](map: Map[String, List[String]])(f: => T): T = _mimeHeaders.doWith(Full(map))(f) - @volatile var templateCache: Box[TemplateCache[(Locale, List[String]), NodeSeq]] = Empty + @volatile var templateCache: Box[TemplateCache[(Locale, List[String]), NodeSeq]] = { + if (Props.productionMode) { + Full(InMemoryCache(500)) + } else { + Empty + } + } val dateTimeConverter: FactoryMaker[DateTimeConverter] = new FactoryMaker[DateTimeConverter]( () => DefaultDateTimeConverter ) {} diff --git a/web/webkit/src/main/scala/net/liftweb/http/provider/HTTPProvider.scala b/web/webkit/src/main/scala/net/liftweb/http/provider/HTTPProvider.scala index 9d2beb7de1..d9a7a8240c 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/provider/HTTPProvider.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/provider/HTTPProvider.scala @@ -134,11 +134,6 @@ trait HTTPProvider { private def postBoot { try { ResourceBundle getBundle (LiftRules.liftCoreResourceName) - - if (Props.productionMode && LiftRules.templateCache.isEmpty) { - // Since we're in productin mode and user did not explicitely set any template caching, we're setting it - LiftRules.templateCache = Full(InMemoryCache(500)) - } } catch { case _: Exception => logger.error("LiftWeb core resource bundle for locale " + Locale.getDefault() + ", was not found ! ") } finally { From 06aeb5c313ebd8d96ceea2e2e02185ff3833c6be Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Wed, 30 Aug 2017 21:24:47 -0400 Subject: [PATCH 1667/1949] Improve LAPinger documentation for clarity --- core/actor/src/main/scala/net/liftweb/actor/LAPinger.scala | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/core/actor/src/main/scala/net/liftweb/actor/LAPinger.scala b/core/actor/src/main/scala/net/liftweb/actor/LAPinger.scala index 0547538690..c7d745f18e 100644 --- a/core/actor/src/main/scala/net/liftweb/actor/LAPinger.scala +++ b/core/actor/src/main/scala/net/liftweb/actor/LAPinger.scala @@ -34,8 +34,8 @@ object ThreadPoolRules { } /** - * The ActorPing object schedules an actor to be ping-ed with a given message at specific intervals. - * The schedule methods return a ScheduledFuture object which can be cancelled if necessary + * The LAPinger object schedules a LiftActor to be pinged "delay" miliseconds in the future. + * The schedule method return a ScheduledFuture object which can be cancelled if necessary. */ object LAPinger { @@ -60,6 +60,9 @@ object LAPinger { /** * Schedules the sending of a message to occur after the specified delay. * + * @param to The LiftActor to send the message to. + * @param msg The message to send. + * @param delay The number of milliseconds to delay before sending msg * @return a ScheduledFuture which sends the msg to * the to Actor after the specified TimeSpan delay. */ From 6369a0b7ace031cad53fccbcd87b370d2fc24d7a Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Wed, 30 Aug 2017 21:39:55 -0400 Subject: [PATCH 1668/1949] Consider LiftRules.cometCreation while building comets This fixes a bug that appeared in Lift 3 where we stopped considering cometCreation while creating comets from creation info. --- .../src/main/scala/net/liftweb/http/LiftSession.scala | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index 88d87c350b..6943543a74 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -2487,8 +2487,9 @@ class LiftSession(private[http] val _contextPath: String, val underlyingId: Stri /** * As with `findOrBuildComet[T]`, but specify the type as a `String`. If the * comet doesn't already exist, the comet type is first looked up via - * `LiftRules.cometCreationFactory`, and then as a class name in the comet - * packages designated by `LiftRules.buildPackage("comet")`. + * `LiftRules.cometCreationFactory`, then `LiftRules.cometCreation`, and + * finally as a class name in the comet packages designated by + * `LiftRules.buildPackage("comet")`. */ private[http] def findOrCreateComet( cometType: String, @@ -2568,12 +2569,14 @@ class LiftSession(private[http] val _contextPath: String, val underlyingId: Stri } // Given a comet creation info, build a comet based on the comet type, first - // attempting to use LiftRules.cometCreationFactory and then building it by + // attempting to use `LiftRules.cometCreationFactory` and then attempting to + // find a match in `LiftRules.cometCreation`. Failing those, this will build it by // class name. Return a descriptive Failure if it's all gone sideways. // // Runs some base setup tasks before returning the comet. private def buildCometByCreationInfo(creationInfo: CometCreationInfo): Box[LiftCometActor] = { - LiftRules.cometCreationFactory.vend.apply(creationInfo) or { + LiftRules.cometCreationFactory.vend.apply(creationInfo) or + LiftRules.cometCreation.toList.find(_.isDefinedAt(creationInfo)).map(_.apply(creationInfo)) or { val cometType = findType[LiftCometActor]( creationInfo.cometType, From 981e071e5c1f690572127fd3cac432db8a3f2241 Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sun, 3 Sep 2017 11:01:56 +0530 Subject: [PATCH 1669/1949] Rename the constructor parameter named 'rec' to 'owner' to make things more consistent. Apply @deprecatedName annotation to make sure things still work --- .../record/field/BsonRecordField.scala | 8 ++-- .../mongodb/record/field/CaseClassField.scala | 14 +++--- .../record/field/JsonObjectField.scala | 12 ++--- .../mongodb/record/field/MongoRefField.scala | 47 ++++++++++--------- .../mongodb/record/field/ObjectIdField.scala | 10 ++-- .../mongodb/record/field/PatternField.scala | 8 ++-- .../mongodb/record/field/UUIDField.scala | 10 ++-- 7 files changed, 54 insertions(+), 55 deletions(-) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala index b54c09bb3c..ce3aa46878 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala @@ -35,9 +35,9 @@ abstract class BsonRecordTypedField[OwnerType <: BsonRecord[OwnerType], SubRecor (override val owner: OwnerType, valueMeta: BsonMetaRecord[SubRecordType])(implicit subRecordType: Manifest[SubRecordType]) extends Field[SubRecordType, OwnerType] { - def this(rec: OwnerType, valueMeta: BsonMetaRecord[SubRecordType], value: Box[SubRecordType]) + def this(owner: OwnerType, valueMeta: BsonMetaRecord[SubRecordType], value: Box[SubRecordType]) (implicit subRecordType: Manifest[SubRecordType]) = { - this(rec, valueMeta) + this(owner, valueMeta) setBox(value) } @@ -70,8 +70,8 @@ class BsonRecordField[OwnerType <: BsonRecord[OwnerType], SubRecordType <: BsonR (@deprecatedName('rec) owner: OwnerType, valueMeta: BsonMetaRecord[SubRecordType])(implicit subRecordType: Manifest[SubRecordType]) extends BsonRecordTypedField(owner, valueMeta) with MandatoryTypedField[SubRecordType] { - def this(rec: OwnerType, valueMeta: BsonMetaRecord[SubRecordType], value: SubRecordType)(implicit subRecordType: Manifest[SubRecordType]) = { - this(rec, value.meta) + def this(@deprecatedName('rec) owner: OwnerType, valueMeta: BsonMetaRecord[SubRecordType], value: SubRecordType)(implicit subRecordType: Manifest[SubRecordType]) = { + this(owner, value.meta) set(value) } diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/CaseClassField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/CaseClassField.scala index 7386e3eb54..b4172a12d1 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/CaseClassField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/CaseClassField.scala @@ -81,8 +81,8 @@ abstract class CaseClassTypedField[OwnerType <: Record[OwnerType], CaseType](ove } } -class CaseClassField[OwnerType <: Record[OwnerType], CaseType](rec: OwnerType)(implicit mf: Manifest[CaseType]) - extends CaseClassTypedField[OwnerType, CaseType](rec) with MandatoryTypedField[CaseType] { +class CaseClassField[OwnerType <: Record[OwnerType], CaseType](owner: OwnerType)(implicit mf: Manifest[CaseType]) + extends CaseClassTypedField[OwnerType, CaseType](owner) with MandatoryTypedField[CaseType] { def this(owner: OwnerType, value: CaseType)(implicit mf: Manifest[CaseType]) = { @@ -94,11 +94,11 @@ class CaseClassField[OwnerType <: Record[OwnerType], CaseType](rec: OwnerType)(i } @deprecated("Use the more consistently named 'CaseClassField' instead. This class will be removed in Lift 4.", "3.2") -class MongoCaseClassField[OwnerType <: Record[OwnerType], CaseType](rec: OwnerType)(implicit mf: Manifest[CaseType]) - extends CaseClassField[OwnerType, CaseType](rec) +class MongoCaseClassField[OwnerType <: Record[OwnerType], CaseType](@deprecatedName('rec) owner: OwnerType)(implicit mf: Manifest[CaseType]) + extends CaseClassField[OwnerType, CaseType](owner) -class OptionalCaseClassField[OwnerType <: Record[OwnerType], CaseType](rec: OwnerType)(implicit mf: Manifest[CaseType]) - extends CaseClassTypedField[OwnerType, CaseType](rec) with OptionalTypedField[CaseType] { +class OptionalCaseClassField[OwnerType <: Record[OwnerType], CaseType](owner: OwnerType)(implicit mf: Manifest[CaseType]) + extends CaseClassTypedField[OwnerType, CaseType](owner) with OptionalTypedField[CaseType] { def this(owner: OwnerType, value: Box[CaseType])(implicit mf: Manifest[CaseType]) = { this(owner) @@ -174,5 +174,5 @@ class CaseClassListField[OwnerType <: Record[OwnerType], CaseType](override val } @deprecated("Please use the more consistently named 'CaseClassListField' instead. This class will be removed in Lift 4.", "3.2") -class MongoCaseClassListField[OwnerType <: Record[OwnerType], CaseType](owner: OwnerType)(implicit mf: Manifest[CaseType]) +class MongoCaseClassListField[OwnerType <: Record[OwnerType], CaseType](@deprecatedName('rec) owner: OwnerType)(implicit mf: Manifest[CaseType]) extends CaseClassListField[OwnerType, CaseType](owner) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JsonObjectField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JsonObjectField.scala index 55ba5eefde..8947e1fff8 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JsonObjectField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JsonObjectField.scala @@ -26,11 +26,9 @@ import com.mongodb.{BasicDBList, DBObject} import scala.collection.JavaConverters._ abstract class JsonObjectField[OwnerType <: BsonRecord[OwnerType], JObjectType <: JsonObject[JObjectType]] - (rec: OwnerType, valueMeta: JsonObjectMeta[JObjectType]) + (@deprecatedName('rec) override val owner: OwnerType, valueMeta: JsonObjectMeta[JObjectType]) extends Field[JObjectType, OwnerType] with MandatoryTypedField[JObjectType] with MongoFieldFlavor[JObjectType] { - def owner = rec - implicit val formats = owner.meta.formats /** @@ -69,7 +67,7 @@ abstract class JsonObjectField[OwnerType <: BsonRecord[OwnerType], JObjectType < def setFromString(in: String): Box[JObjectType] = tryo(JsonParser.parse(in)) match { case Full(jv: JValue) => setFromJValue(jv) case f: Failure => setBox(f) - case other => setBox(Failure("Error parsing String into a JValue: "+in)) + case _ => setBox(Failure(s"Error parsing String into a JValue: $in")) } /* @@ -87,8 +85,8 @@ abstract class JsonObjectField[OwnerType <: BsonRecord[OwnerType], JObjectType < * List of JsonObject case classes */ class JsonObjectListField[OwnerType <: BsonRecord[OwnerType], JObjectType <: JsonObject[JObjectType]] -(rec: OwnerType, valueMeta: JsonObjectMeta[JObjectType])(implicit mf: Manifest[JObjectType]) - extends MongoListField[OwnerType, JObjectType](rec: OwnerType) { +(owner: OwnerType, valueMeta: JsonObjectMeta[JObjectType])(implicit mf: Manifest[JObjectType]) + extends MongoListField[OwnerType, JObjectType](owner: OwnerType) { override def asDBObject: DBObject = { val dbl = new BasicDBList @@ -114,4 +112,4 @@ class JsonObjectListField[OwnerType <: BsonRecord[OwnerType], JObjectType <: Jso @deprecated("Use the more consistently named 'JsonObjectListField' instead. This class will be removed in Lift 4.", "3.2") class MongoJsonObjectListField[OwnerType <: BsonRecord[OwnerType], JObjectType <: JsonObject[JObjectType]] -(rec: OwnerType, valueMeta: JsonObjectMeta[JObjectType])(implicit mf: Manifest[JObjectType]) extends JsonObjectListField(rec, valueMeta) +(@deprecatedName('rec) owner: OwnerType, valueMeta: JsonObjectMeta[JObjectType])(implicit mf: Manifest[JObjectType]) extends JsonObjectListField(owner, valueMeta) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoRefField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoRefField.scala index abf9f5d395..fdb47432a2 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoRefField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoRefField.scala @@ -86,59 +86,60 @@ trait MongoRefField[RefType <: MongoRecord[RefType], MyType] extends TypedField[ private[this] def elem = SHtml.selectObj[Box[MyType]]( buildDisplayList, Full(valueBox), - setBox(_) + setBox ) % ("tabindex" -> tabIndex.toString) override def toForm = - if (options.length > 0) + if (options.nonEmpty) { uniqueFieldId match { case Full(id) => Full(elem % ("id" -> id)) case _ => Full(elem) } - else + } else { Empty + } } class ObjectIdRefField[OwnerType <: BsonRecord[OwnerType], RefType <: MongoRecord[RefType]]( - rec: OwnerType, val refMeta: MongoMetaRecord[RefType] -) extends ObjectIdField[OwnerType](rec) with MongoRefField[RefType, ObjectId] + @deprecatedName('rec) owner: OwnerType, val refMeta: MongoMetaRecord[RefType] +) extends ObjectIdField[OwnerType](owner) with MongoRefField[RefType, ObjectId] class OptionalObjectIdRefField[OwnerType <: BsonRecord[OwnerType], RefType <: MongoRecord[RefType]]( - rec: OwnerType, val refMeta: MongoMetaRecord[RefType] -) extends OptionalObjectIdField[OwnerType](rec) with MongoRefField[RefType, ObjectId] + owner: OwnerType, val refMeta: MongoMetaRecord[RefType] +) extends OptionalObjectIdField[OwnerType](owner) with MongoRefField[RefType, ObjectId] class UUIDRefField[OwnerType <: BsonRecord[OwnerType], RefType <: MongoRecord[RefType]]( - rec: OwnerType, val refMeta: MongoMetaRecord[RefType] -) extends UUIDField[OwnerType](rec) with MongoRefField[RefType, UUID] + @deprecatedName('rec) owner: OwnerType, val refMeta: MongoMetaRecord[RefType] +) extends UUIDField[OwnerType](owner) with MongoRefField[RefType, UUID] class OptionalUUIDRefField[OwnerType <: BsonRecord[OwnerType], RefType <: MongoRecord[RefType]]( - rec: OwnerType, val refMeta: MongoMetaRecord[RefType] -) extends OptionalUUIDField[OwnerType](rec) with MongoRefField[RefType, UUID] + owner: OwnerType, val refMeta: MongoMetaRecord[RefType] +) extends OptionalUUIDField[OwnerType](owner) with MongoRefField[RefType, UUID] class StringRefField[OwnerType <: BsonRecord[OwnerType], RefType <: MongoRecord[RefType]]( - rec: OwnerType, val refMeta: MongoMetaRecord[RefType], maxLen: Int -) extends StringField[OwnerType](rec, maxLen) with MongoRefField[RefType, String] + @deprecatedName('rec) owner: OwnerType, val refMeta: MongoMetaRecord[RefType], maxLen: Int +) extends StringField[OwnerType](owner, maxLen) with MongoRefField[RefType, String] class OptionalStringRefField[OwnerType <: BsonRecord[OwnerType], RefType <: MongoRecord[RefType]]( - rec: OwnerType, val refMeta: MongoMetaRecord[RefType], maxLen: Int -) extends OptionalStringField[OwnerType](rec, maxLen) with MongoRefField[RefType, String] + owner: OwnerType, val refMeta: MongoMetaRecord[RefType], maxLen: Int +) extends OptionalStringField[OwnerType](owner, maxLen) with MongoRefField[RefType, String] class IntRefField[OwnerType <: BsonRecord[OwnerType], RefType <: MongoRecord[RefType]]( - rec: OwnerType, val refMeta: MongoMetaRecord[RefType] -) extends IntField[OwnerType](rec) with MongoRefField[RefType, Int] + @deprecatedName('rec) owner: OwnerType, val refMeta: MongoMetaRecord[RefType] +) extends IntField[OwnerType](owner) with MongoRefField[RefType, Int] class OptionalIntRefField[OwnerType <: BsonRecord[OwnerType], RefType <: MongoRecord[RefType]]( - rec: OwnerType, val refMeta: MongoMetaRecord[RefType] -) extends OptionalIntField[OwnerType](rec) with MongoRefField[RefType, Int] + owner: OwnerType, val refMeta: MongoMetaRecord[RefType] +) extends OptionalIntField[OwnerType](owner) with MongoRefField[RefType, Int] class LongRefField[OwnerType <: BsonRecord[OwnerType], RefType <: MongoRecord[RefType]]( - rec: OwnerType, val refMeta: MongoMetaRecord[RefType] -) extends LongField[OwnerType](rec) with MongoRefField[RefType, Long] + @deprecatedName('rec) owner: OwnerType, val refMeta: MongoMetaRecord[RefType] +) extends LongField[OwnerType](owner) with MongoRefField[RefType, Long] class OptionalLongRefField[OwnerType <: BsonRecord[OwnerType], RefType <: MongoRecord[RefType]]( - rec: OwnerType, val refMeta: MongoMetaRecord[RefType] -) extends OptionalLongField[OwnerType](rec) with MongoRefField[RefType, Long] + owner: OwnerType, val refMeta: MongoMetaRecord[RefType] +) extends OptionalLongField[OwnerType](owner) with MongoRefField[RefType, Long] diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/ObjectIdField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/ObjectIdField.scala index 916bd736e9..d40e530aa6 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/ObjectIdField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/ObjectIdField.scala @@ -82,11 +82,11 @@ trait ObjectIdTypedField[OwnerType <: BsonRecord[OwnerType]] extends TypedField[ } -class ObjectIdField[OwnerType <: BsonRecord[OwnerType]](override val owner: OwnerType) +class ObjectIdField[OwnerType <: BsonRecord[OwnerType]](@deprecatedName('rec) override val owner: OwnerType) extends MandatoryTypedField[ObjectId] with ObjectIdTypedField[OwnerType] { - def this(rec: OwnerType, value: ObjectId) = { - this(rec) + def this(owner: OwnerType, value: ObjectId) = { + this(owner) setBox(Full(value)) } @@ -99,8 +99,8 @@ class ObjectIdField[OwnerType <: BsonRecord[OwnerType]](override val owner: Owne class OptionalObjectIdField[OwnerType <: BsonRecord[OwnerType]](override val owner: OwnerType) extends OptionalTypedField[ObjectId] with ObjectIdTypedField[OwnerType] { - def this(rec: OwnerType, value: Box[ObjectId]) = { - this(rec) + def this(owner: OwnerType, value: Box[ObjectId]) = { + this(owner) setBox(value) } diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/PatternField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/PatternField.scala index ee549cfdc0..186909b11d 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/PatternField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/PatternField.scala @@ -65,8 +65,8 @@ abstract class PatternTypedField[OwnerType <: BsonRecord[OwnerType]](override va } class PatternField[OwnerType <: BsonRecord[OwnerType]](@deprecatedName('rec) owner: OwnerType) extends PatternTypedField[OwnerType](owner) with MandatoryTypedField[Pattern] { - def this(rec: OwnerType, value: Pattern) = { - this(rec) + def this(owner: OwnerType, value: Pattern) = { + this(owner) setBox(Full(value)) } @@ -74,8 +74,8 @@ class PatternField[OwnerType <: BsonRecord[OwnerType]](@deprecatedName('rec) own } class OptionalPatternField[OwnerType <: BsonRecord[OwnerType]](owner: OwnerType) extends PatternTypedField[OwnerType](owner) with OptionalTypedField[Pattern] { - def this(rec: OwnerType, value: Box[Pattern]) = { - this(rec) + def this(owner: OwnerType, value: Box[Pattern]) = { + this(owner) setBox(value) } } diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/UUIDField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/UUIDField.scala index e79335fd66..6c6dec0934 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/UUIDField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/UUIDField.scala @@ -74,11 +74,11 @@ trait UUIDTypedField[OwnerType <: BsonRecord[OwnerType]] extends TypedField[UUID override def asJValue: JValue = valueBox.map(v => JsonUUID(v)) openOr (JNothing: JValue) } -class UUIDField[OwnerType <: BsonRecord[OwnerType]](override val owner: OwnerType) +class UUIDField[OwnerType <: BsonRecord[OwnerType]](@deprecatedName('rec) override val owner: OwnerType) extends UUIDTypedField[OwnerType] with MandatoryTypedField[UUID] { - def this(rec: OwnerType, value: UUID) = { - this(rec) + def this(owner: OwnerType, value: UUID) = { + this(owner) setBox(Full(value)) } @@ -89,8 +89,8 @@ class UUIDField[OwnerType <: BsonRecord[OwnerType]](override val owner: OwnerTyp class OptionalUUIDField[OwnerType <: BsonRecord[OwnerType]](override val owner: OwnerType) extends UUIDTypedField[OwnerType] with OptionalTypedField[UUID] { - def this(rec: OwnerType, value: Box[UUID]) = { - this(rec) + def this(owner: OwnerType, value: Box[UUID]) = { + this(owner) setBox(value) } From 348e06233cead1fc9687ea31190d8e19296af135 Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sun, 3 Sep 2017 11:22:30 +0530 Subject: [PATCH 1670/1949] Make names and descriptions more consistent with some of the renamed classes. I.e. the 'mongo' prefix was removed in some cases --- .../scala/net/liftweb/mongodb/record/Fixtures.scala | 6 +++--- .../net/liftweb/mongodb/record/MongoFieldSpec.scala | 6 +++--- .../mongodb/record/MongoRecordExamplesSpec.scala | 2 -- .../net/liftweb/mongodb/record/MongoRecordSpec.scala | 10 +++++----- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala index a665413559..2fb790deb3 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala @@ -228,7 +228,7 @@ class MongoFieldTypeTestRecord private () extends MongoRecord[MongoFieldTypeTest object mandatoryUUIDField extends UUIDField(this) object legacyOptionalUUIDField extends UUIDField(this) { override def optional_? = true } - object mandatoryMongoCaseClassField extends CaseClassField[MongoFieldTypeTestRecord, MongoCaseClassTestObject](this) { + object mandatoryCaseClassField extends CaseClassField[MongoFieldTypeTestRecord, CaseClassTestObject](this) { override def formats = owner.meta.formats } } @@ -259,7 +259,7 @@ class PasswordTestRecord private () extends MongoRecord[PasswordTestRecord] with } object PasswordTestRecord extends PasswordTestRecord with MongoMetaRecord[PasswordTestRecord] -case class MongoCaseClassTestObject(intField: Int, stringField: String, enum: MyTestEnum.Value) +case class CaseClassTestObject(intField: Int, stringField: String, enum: MyTestEnum.Value) class ListTestRecord private () extends MongoRecord[ListTestRecord] with UUIDPk[ListTestRecord] { def meta = ListTestRecord @@ -268,7 +268,7 @@ class ListTestRecord private () extends MongoRecord[ListTestRecord] with UUIDPk[ object mandatoryMongoRefListField extends ObjectIdRefListField(this, FieldTypeTestRecord) object mandatoryIntListField extends MongoListField[ListTestRecord, Int](this) object mandatoryJsonObjectListField extends JsonObjectListField(this, TypeTestJsonObject) - object caseClassListField extends CaseClassListField[ListTestRecord, MongoCaseClassTestObject](this) { + object caseClassListField extends CaseClassListField[ListTestRecord, CaseClassTestObject](this) { override def formats = owner.meta.formats } } diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala index 2a7e57b2d8..0247f1376e 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala @@ -481,7 +481,7 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundEach { } } - "MongoJsonObjectListField" should { + "JsonObjectListField" should { "function correctly" in { val rec = ListTestRecord.createRecord val lst = List(TypeTestJsonObject(1, "jsonobj1", Map("x" -> "1")), TypeTestJsonObject(2, "jsonobj2", Map("x" -> "2"))) @@ -503,10 +503,10 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundEach { } } - "MongoCaseClassListField" should { + "CaseClassListField" should { "setFromAny a List" in { val rec = ListTestRecord.createRecord - val lst = List(MongoCaseClassTestObject(1,"str1", MyTestEnum.THREE)) + val lst = List(CaseClassTestObject(1,"str1", MyTestEnum.THREE)) rec.caseClassListField.setFromAny(lst) rec.caseClassListField.value must_== lst } diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordExamplesSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordExamplesSpec.scala index b93279d509..73ca90cafe 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordExamplesSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordExamplesSpec.scala @@ -227,8 +227,6 @@ class MongoRecordExamplesSpec extends Specification with MongoTestKit { fromDb.isDefined must_== true - - for (t <- fromDb) { t.id.value must_== tr.id.value t.booleanfield.value must_== tr.booleanfield.value diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala index ccba4a0d16..a3c2ebd908 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala @@ -54,7 +54,7 @@ class MongoRecordSpec extends Specification with MongoTestKit { "MongoRecord field introspection" should { val rec = MongoFieldTypeTestRecord.createRecord - val allExpectedFieldNames: List[String] = "_id" :: "mandatoryMongoCaseClassField" :: + val allExpectedFieldNames: List[String] = "_id" :: "mandatoryCaseClassField" :: (for { typeName <- "Date JsonObject ObjectId UUID".split(" ") flavor <- "mandatory legacyOptional".split(" ") @@ -209,7 +209,7 @@ class MongoRecordSpec extends Specification with MongoTestKit { .mandatoryJsonObjectField(TypeTestJsonObject(1, "jsonobj1", Map("x" -> "1"))) .mandatoryObjectIdField(ObjectId.get) .mandatoryUUIDField(UUID.randomUUID) - .mandatoryMongoCaseClassField(MongoCaseClassTestObject(1,"str",MyTestEnum.TWO)) + .mandatoryCaseClassField(CaseClassTestObject(1,"str",MyTestEnum.TWO)) val mfttrJson = ("_id" -> ("$oid" -> mfttr.id.toString)) ~ @@ -221,7 +221,7 @@ class MongoRecordSpec extends Specification with MongoTestKit { ("legacyOptionalObjectIdField" -> (None: Option[JObject])) ~ ("mandatoryUUIDField" -> ("$uuid" -> mfttr.mandatoryUUIDField.value.toString)) ~ ("legacyOptionalUUIDField" -> (None: Option[JObject])) ~ - ("mandatoryMongoCaseClassField" -> ("intField" -> 1) ~ ("stringField" -> "str") ~ ("enum" -> 1)) + ("mandatoryCaseClassField" -> ("intField" -> 1) ~ ("stringField" -> "str") ~ ("enum" -> 1)) val pftr = PatternFieldTestRecord.createRecord .mandatoryPatternField(Pattern.compile("^Mo", Pattern.CASE_INSENSITIVE)) @@ -235,7 +235,7 @@ class MongoRecordSpec extends Specification with MongoTestKit { .mandatoryStringListField(List("abc", "def", "ghi")) .mandatoryIntListField(List(4, 5, 6)) .mandatoryJsonObjectListField(List(TypeTestJsonObject(1, "jsonobj1", Map("x" -> "1")), TypeTestJsonObject(2, "jsonobj2", Map("x" -> "2")))) - .caseClassListField(List(MongoCaseClassTestObject(1,"str",MyTestEnum.TWO))) + .caseClassListField(List(CaseClassTestObject(1,"str",MyTestEnum.TWO))) .mandatoryMongoRefListField(Nil) val ltrJson = @@ -849,7 +849,7 @@ class MongoRecordSpec extends Specification with MongoTestKit { ltr.mandatoryJsonObjectListField(List(TypeTestJsonObject(1, "jsonobj1", Map("x" -> "1")), TypeTestJsonObject(2, "jsonobj2", Map("x" -> "2")))) ltr.mandatoryJsonObjectListField.dirty_? must_== true - ltr.caseClassListField(List(MongoCaseClassTestObject(1,"str",MyTestEnum.TWO))) + ltr.caseClassListField(List(CaseClassTestObject(1,"str",MyTestEnum.TWO))) ltr.caseClassListField.dirty_? must_== true ltr.dirty_? must_== true From 6d7ec66cb00e79fb4ab99e91fc747cfe4cab50ad Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sun, 3 Sep 2017 11:29:48 +0530 Subject: [PATCH 1671/1949] Add test for OptionalPatternField json serialization --- .../src/test/scala/net/liftweb/mongodb/record/Fixtures.scala | 3 +-- .../scala/net/liftweb/mongodb/record/MongoRecordSpec.scala | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala index 2fb790deb3..fb75d613d6 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala @@ -240,9 +240,8 @@ object MongoFieldTypeTestRecord extends MongoFieldTypeTestRecord with MongoMetaR class PatternFieldTestRecord private () extends MongoRecord[PatternFieldTestRecord] with ObjectIdPk[PatternFieldTestRecord] { def meta = PatternFieldTestRecord - import java.util.regex.Pattern - object mandatoryPatternField extends PatternField(this) + object optionalPatternField extends OptionalPatternField(this) object legacyOptionalPatternField extends PatternField(this) { override def optional_? = true } override def equals(other: Any): Boolean = equalsWithPatternCheck(other) diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala index a3c2ebd908..3108c949d9 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala @@ -229,6 +229,7 @@ class MongoRecordSpec extends Specification with MongoTestKit { val pftrJson = ("_id" -> ("$oid" -> pftr.id.toString)) ~ ("mandatoryPatternField" -> (("$regex" -> pftr.mandatoryPatternField.value.pattern) ~ ("$flags" -> pftr.mandatoryPatternField.value.flags))) ~ + ("optionalPatternField" -> JNothing) ~ ("legacyOptionalPatternField" -> (None: Option[JObject])) val ltr = ListTestRecord.createRecord From 75c3063884bdf1c47ab4afc04cc695fe4b7e0aa7 Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sun, 3 Sep 2017 11:55:42 +0530 Subject: [PATCH 1672/1949] Optional version for JsonObjectField --- .../record/field/JsonObjectField.scala | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JsonObjectField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JsonObjectField.scala index 8947e1fff8..da1515f844 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JsonObjectField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JsonObjectField.scala @@ -19,15 +19,15 @@ package field import scala.xml.NodeSeq import net.liftweb.common.{Box, Empty, Failure, Full} import net.liftweb.json._ -import net.liftweb.record.{Field, FieldHelpers, MandatoryTypedField} +import net.liftweb.record.{Field, FieldHelpers, MandatoryTypedField, OptionalTypedField} import net.liftweb.util.Helpers.tryo import com.mongodb.{BasicDBList, DBObject} import scala.collection.JavaConverters._ -abstract class JsonObjectField[OwnerType <: BsonRecord[OwnerType], JObjectType <: JsonObject[JObjectType]] - (@deprecatedName('rec) override val owner: OwnerType, valueMeta: JsonObjectMeta[JObjectType]) - extends Field[JObjectType, OwnerType] with MandatoryTypedField[JObjectType] with MongoFieldFlavor[JObjectType] { +abstract class JsonObjectTypedField[OwnerType <: BsonRecord[OwnerType], JObjectType <: JsonObject[JObjectType]] +(override val owner: OwnerType, valueMeta: JsonObjectMeta[JObjectType]) + extends Field[JObjectType, OwnerType] with MongoFieldFlavor[JObjectType] { implicit val formats = owner.meta.formats @@ -44,7 +44,7 @@ abstract class JsonObjectField[OwnerType <: BsonRecord[OwnerType], JObjectType < * Returns Empty or Failure if the value could not be set */ def setFromJValue(jvalue: JValue): Box[JObjectType] = jvalue match { - case JNothing|JNull if optional_? => setBox(Empty) + case JNothing | JNull if optional_? => setBox(Empty) case o: JObject => setBox(tryo(valueMeta.create(o))) case other => setBox(FieldHelpers.expectedA("JObject", other)) } @@ -81,6 +81,26 @@ abstract class JsonObjectField[OwnerType <: BsonRecord[OwnerType], JObjectType < } +abstract class JsonObjectField[OwnerType <: BsonRecord[OwnerType], JObjectType <: JsonObject[JObjectType]] +(@deprecatedName('rec) owner: OwnerType, valueMeta: JsonObjectMeta[JObjectType]) + extends JsonObjectTypedField(owner, valueMeta) with MandatoryTypedField[JObjectType] { + + def this(owner: OwnerType, valueMeta: JsonObjectMeta[JObjectType], value: JObjectType) = { + this(owner, valueMeta) + setBox(Full(value)) + } +} + +class OptionalJsonObjectField[OwnerType <: BsonRecord[OwnerType], JObjectType <: JsonObject[JObjectType]] +(owner: OwnerType, valueMeta: JsonObjectMeta[JObjectType]) + extends JsonObjectTypedField(owner, valueMeta) with OptionalTypedField[JObjectType] { + + def this(owner: OwnerType, valueMeta: JsonObjectMeta[JObjectType], valueBox: Box[JObjectType]) = { + this(owner, valueMeta) + setBox(valueBox) + } +} + /* * List of JsonObject case classes */ From da1ddfe68f9e04c733e10fdfa328875eec77e7c1 Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sun, 3 Sep 2017 11:56:23 +0530 Subject: [PATCH 1673/1949] Add testing for optional versions of fields: UUID, ObjectId, CaseClass, JsonObject, Date --- .../scala/net/liftweb/mongodb/record/Fixtures.scala | 8 ++++++++ .../net/liftweb/mongodb/record/MongoRecordSpec.scala | 11 ++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala index fb75d613d6..0bbdcf2033 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala @@ -212,25 +212,33 @@ class MongoFieldTypeTestRecord private () extends MongoRecord[MongoFieldTypeTest def meta = MongoFieldTypeTestRecord object mandatoryDateField extends DateField(this) + object optionalDateField extends OptionalDateField(this) object legacyOptionalDateField extends DateField(this) { override def optional_? = true } + object mandatoryJsonObjectField extends JsonObjectField(this, TypeTestJsonObject) { def defaultValue = TypeTestJsonObject(0, "", Map[String, String]()) } + object optionalJsonObjectField extends OptionalJsonObjectField(this, TypeTestJsonObject) object legacyOptionalJsonObjectField extends JsonObjectField(this, TypeTestJsonObject) { override def optional_? = true def defaultValue = TypeTestJsonObject(0, "", Map[String, String]()) } object mandatoryObjectIdField extends ObjectIdField(this) + object optionalObjectIdField extends OptionalObjectIdField(this) object legacyOptionalObjectIdField extends ObjectIdField(this) { override def optional_? = true } object mandatoryUUIDField extends UUIDField(this) + object optionalUUIDField extends OptionalUUIDField(this) object legacyOptionalUUIDField extends UUIDField(this) { override def optional_? = true } object mandatoryCaseClassField extends CaseClassField[MongoFieldTypeTestRecord, CaseClassTestObject](this) { override def formats = owner.meta.formats } + object optionalCaseClassField extends OptionalCaseClassField[MongoFieldTypeTestRecord, CaseClassTestObject](this) { + override def formats = owner.meta.formats + } } object MongoFieldTypeTestRecord extends MongoFieldTypeTestRecord with MongoMetaRecord[MongoFieldTypeTestRecord] { diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala index 3108c949d9..a050b34695 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala @@ -54,10 +54,10 @@ class MongoRecordSpec extends Specification with MongoTestKit { "MongoRecord field introspection" should { val rec = MongoFieldTypeTestRecord.createRecord - val allExpectedFieldNames: List[String] = "_id" :: "mandatoryCaseClassField" :: + val allExpectedFieldNames: List[String] = "_id" :: "mandatoryCaseClassField" :: "optionalCaseClassField" :: (for { typeName <- "Date JsonObject ObjectId UUID".split(" ") - flavor <- "mandatory legacyOptional".split(" ") + flavor <- "mandatory legacyOptional optional".split(" ") } yield flavor + typeName + "Field").toList "introspect only the expected fields" in { @@ -215,13 +215,18 @@ class MongoRecordSpec extends Specification with MongoTestKit { ("_id" -> ("$oid" -> mfttr.id.toString)) ~ ("mandatoryDateField" -> ("$dt" -> mfttr.meta.formats.dateFormat.format(mfttr.mandatoryDateField.value))) ~ ("legacyOptionalDateField" -> (None: Option[JObject])) ~ + ("optionalDateField" -> JNothing) ~ ("mandatoryJsonObjectField" -> (("intField" -> 1) ~ ("stringField" -> "jsonobj1") ~ ("mapField" -> ("x" -> "1")))) ~ + ("optionalJsonObjectField" -> JNothing) ~ ("legacyOptionalJsonObjectField" -> (None: Option[JObject])) ~ ("mandatoryObjectIdField", ("$oid" -> mfttr.mandatoryObjectIdField.value.toString)) ~ + ("optionalObjectIdField" -> JNothing) ~ ("legacyOptionalObjectIdField" -> (None: Option[JObject])) ~ ("mandatoryUUIDField" -> ("$uuid" -> mfttr.mandatoryUUIDField.value.toString)) ~ + ("optionalUUIDField" -> JNothing) ~ ("legacyOptionalUUIDField" -> (None: Option[JObject])) ~ - ("mandatoryCaseClassField" -> ("intField" -> 1) ~ ("stringField" -> "str") ~ ("enum" -> 1)) + ("mandatoryCaseClassField" -> ("intField" -> 1) ~ ("stringField" -> "str") ~ ("enum" -> 1)) ~ + ("optionalCaseClassField" -> JNothing) val pftr = PatternFieldTestRecord.createRecord .mandatoryPatternField(Pattern.compile("^Mo", Pattern.CASE_INSENSITIVE)) From bfad8b84adf2285149969b8a23c7fb3886260bab Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sun, 3 Sep 2017 13:05:03 +0530 Subject: [PATCH 1674/1949] More tests for optional fields. Pulled out common parts of the method that tested only mandatory fields into a common method that can be used for testing optional fields as well --- .../net/liftweb/mongodb/record/Fixtures.scala | 1 + .../mongodb/record/MongoFieldSpec.scala | 146 ++++++++++++------ .../mongodb/record/MongoRecordSpec.scala | 10 +- 3 files changed, 110 insertions(+), 47 deletions(-) diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala index 0bbdcf2033..1895c5ccda 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala @@ -365,6 +365,7 @@ class SubRecordTestRecord private () extends MongoRecord[SubRecordTestRecord] wi def meta = SubRecordTestRecord object mandatoryBsonRecordField extends BsonRecordField(this, SubRecord) + object optioalBsonRecordField extends OptionalBsonRecordField(this, SubRecord) object legacyOptionalBsonRecordField extends BsonRecordField(this, SubRecord) { override def optional_? = true } diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala index 0247f1376e..3b885cd757 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala @@ -61,11 +61,39 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundEach { example: A, example2: A, mandatory: MandatoryTypedField[A], + optionalBox: Box[OptionalTypedField[A]], legacyOptionalBox: Box[MandatoryTypedField[A]], canCheckDefaultValues: Boolean = true )(implicit m: scala.reflect.Manifest[A]): Unit = { - def commonBehaviorsForAllFlavors(field: MandatoryTypedField[A]) = { + def commonBehaviorsForAll(field: TypedField[A]) = { + "which are only flagged as dirty_? when setBox is called with a different value" in { + field.clear + field match { + case owned: OwnedField[_] => owned.owner.runSafe { + field.resetDirty + } + case _ => field.resetDirty + } + field.dirty_? must_== false + val valueBox = field.valueBox + field.setBox(valueBox) + field.dirty_? must_== false + val exampleBox = Full(example) + (valueBox === exampleBox) must_== false + field.setBox(exampleBox) + field.dirty_? must_== true + val exampleBox2 = Full(example2) + (exampleBox === exampleBox2) must_== false + field.setBox(exampleBox2) + field.dirty_? must_== true + field.setBox(valueBox) + success + } + } + + def commonBehaviorsForMandatory(field: MandatoryTypedField[A]) = { + commonBehaviorsForAll(field) "which have the correct initial value" in { field.value must be_==(field.defaultValue).when(canCheckDefaultValues) @@ -95,29 +123,7 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundEach { pending } - "which are only flagged as dirty_? when setBox is called with a different value" in { - field.clear - field match { - case owned: OwnedField[_] => owned.owner.runSafe { - field.resetDirty - } - case _ => field.resetDirty - } - field.dirty_? must_== false - val valueBox = field.valueBox - field.setBox(valueBox) - field.dirty_? must_== false - val exampleBox = Full(example) - (valueBox === exampleBox) must_== false - field.setBox(exampleBox) - field.dirty_? must_== true - val exampleBox2 = Full(example2) - (exampleBox === exampleBox2) must_== false - field.setBox(exampleBox2) - field.dirty_? must_== true - field.setBox(valueBox) - success - } + } "support mandatory fields" in { @@ -129,8 +135,8 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundEach { mandatory.valueBox.isDefined must_== true } - "common behaviors for all flavors" in { - commonBehaviorsForAllFlavors(mandatory) + "common behaviors for all MandatoryTypedField fields" in { + commonBehaviorsForMandatory(mandatory) } "which correctly fail to be set to Empty" in { @@ -150,8 +156,8 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundEach { legacyOptional.valueBox must_== Empty } - "common behaviors for all flavors" in { - commonBehaviorsForAllFlavors(legacyOptional) + "common behaviors for all MandatoryTypedField fields" in { + commonBehaviorsForMandatory(legacyOptional) } "which do not fail when set to Empty" in { @@ -175,6 +181,56 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundEach { } } } + + optionalBox map { optional => + "support optional fields" in { + commonBehaviorsForAll(optional) + + "which are configured correctly" in { + optional.optional_? must_== true + } + + "which initialize to Empty" in { + optional.valueBox must_== Empty + } + + "which have the correct initial value" in { + optional.valueBox must be_== (optional.defaultValueBox) when canCheckDefaultValues + optional.value must beNone when canCheckDefaultValues + } + + "which are readable and writable with box values" in { + optional.setBox(Full(example)) + optional.valueBox must_== Full(example) + optional.value must beSome(example) + optional.clear + optional.valueBox must be (Empty) + optional.value must beNone + optional.set(Some(example)) + optional.valueBox must_== Full(example) + } + + "which correctly clear back to the default box value" in { + { optional.clear; optional.value } must beNone when canCheckDefaultValues + + { optional.clear; optional.valueBox } must be_== (optional.defaultValueBox) when canCheckDefaultValues + } + + "which capture error conditions set in" in { + // FIXME: This needs to be rearranged just so that it doesn't foul with subsequent examples + // field.setBox(Failure("my failure")) + // Failure("my failure") must_== Failure("my failure") + pending + } + + "which do not fail when set to Empty" in { + optional.setBox(Empty) + optional.value must beNone when canCheckDefaultValues + optional.valueBox must be_== (Empty) when canCheckDefaultValues + success + } + } + } } def passConversionTests[A](example: A, mandatory: MandatoryTypedField[A], jsexp: JsExp, jvalue: JValue, formPattern: Box[NodeSeq], canCheckSetFromJValue: Boolean = true) = { @@ -225,7 +281,7 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundEach { val nowStr = rec.meta.formats.dateFormat.format(now) val now2 = Calendar.getInstance() now2.add(Calendar.DATE, 1) - passBasicTests(now, now2.getTime, rec.mandatoryDateField, Full(rec.legacyOptionalDateField), false) + passBasicTests(now, now2.getTime, rec.mandatoryDateField, Full(rec.optionalDateField), Full(rec.legacyOptionalDateField), false) passConversionTests( now, rec.mandatoryDateField, @@ -240,7 +296,7 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundEach { val ttjo = TypeTestJsonObject(1, "jsonobj1", Map("x" -> "a")) val ttjo2 = TypeTestJsonObject(2, "jsonobj2", Map("x" -> "b")) val json = ("intField" -> 1) ~ ("stringField" -> "jsonobj1") ~ ("mapField" -> (("x" -> "a"))) - passBasicTests(ttjo, ttjo2, rec.mandatoryJsonObjectField, Full(rec.legacyOptionalJsonObjectField)) + passBasicTests(ttjo, ttjo2, rec.mandatoryJsonObjectField, Full(rec.optionalJsonObjectField), Full(rec.legacyOptionalJsonObjectField)) passConversionTests( ttjo, rec.mandatoryJsonObjectField, @@ -258,7 +314,7 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundEach { val rec = MongoFieldTypeTestRecord.createRecord val oid = ObjectId.get val oid2 = ObjectId.get - passBasicTests(oid, oid2, rec.mandatoryObjectIdField, Full(rec.legacyOptionalObjectIdField), false) + passBasicTests(oid, oid2, rec.mandatoryObjectIdField, Full(rec.optionalObjectIdField), Full(rec.legacyOptionalObjectIdField), false) passConversionTests( oid, rec.mandatoryObjectIdField, @@ -276,7 +332,7 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundEach { val rec = PatternFieldTestRecord.createRecord val ptrn = Pattern.compile("^Mo", Pattern.CASE_INSENSITIVE) val ptrn2 = Pattern.compile("^MON", Pattern.CASE_INSENSITIVE) - passBasicTests(ptrn, ptrn2, rec.mandatoryPatternField, Full(rec.legacyOptionalPatternField), false) + passBasicTests(ptrn, ptrn2, rec.mandatoryPatternField, Full(rec.optionalPatternField), Full(rec.legacyOptionalPatternField), false) passConversionTests( ptrn, rec.mandatoryPatternField, @@ -291,7 +347,7 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundEach { val rec = MongoFieldTypeTestRecord.createRecord val uuid = UUID.randomUUID val uuid2 = UUID.randomUUID - passBasicTests(uuid, uuid2, rec.mandatoryUUIDField, Full(rec.legacyOptionalUUIDField), false) + passBasicTests(uuid, uuid2, rec.mandatoryUUIDField, Full(rec.optionalUUIDField), Full(rec.legacyOptionalUUIDField), false) passConversionTests( uuid, rec.mandatoryUUIDField, @@ -326,7 +382,7 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundEach { val rec = ListTestRecord.createRecord val lst = List("abc", "def", "ghi") val lst2 = List("ab", "de", "gh") - passBasicTests(lst, lst2, rec.mandatoryStringListField, Empty) + passBasicTests(lst, lst2, rec.mandatoryStringListField, Empty, Empty) passConversionTests( lst, rec.mandatoryStringListField, @@ -342,7 +398,7 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundEach { val rec = ListTestRecord.createRecord val lst = List(4, 5, 6) val lst2 = List(1, 2, 3) - passBasicTests(lst, lst2, rec.mandatoryIntListField, Empty) + passBasicTests(lst, lst2, rec.mandatoryIntListField, Empty, Empty) passConversionTests( lst, rec.mandatoryIntListField, @@ -364,7 +420,7 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundEach { val oid6 = ObjectId.get val lst = List(oid1, oid2, oid3) val lst2 = List(oid4, oid5, oid6) - passBasicTests(lst, lst2, rec.objectIdRefListField, Empty) + passBasicTests(lst, lst2, rec.objectIdRefListField, Empty, Empty) passConversionTests( lst, rec.objectIdRefListField, @@ -388,7 +444,7 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundEach { val ptrn4 = Pattern.compile("^WED") val lst1 = List(ptrn1, ptrn2) val lst2 = List(ptrn3, ptrn4) - passBasicTests(lst1, lst2, rec.patternListField, Empty) + passBasicTests(lst1, lst2, rec.patternListField, Empty, Empty) passConversionTests( lst1, rec.patternListField, @@ -414,7 +470,7 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundEach { val dt6 = new Date val lst = List(dt1, dt2, dt3) val lst2 = List(dt4, dt5, dt6) - passBasicTests(lst, lst2, rec.dateListField, Empty) + passBasicTests(lst, lst2, rec.dateListField, Empty, Empty) passConversionTests( lst, rec.dateListField, @@ -440,7 +496,7 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundEach { val uuid6 = UUID.randomUUID val lst = List(uuid1, uuid2, uuid3) val lst2 = List(uuid4, uuid5, uuid6) - passBasicTests(lst, lst2, rec.uuidListField, Empty) + passBasicTests(lst, lst2, rec.uuidListField, Empty, Empty) passConversionTests( lst, rec.uuidListField, @@ -466,7 +522,7 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundEach { val dt6 = new DateTime val lst = List(dt1, dt2, dt3) val lst2 = List(dt4, dt5, dt6) - passBasicTests(lst, lst2, rec.dateTimeListField, Empty) + passBasicTests(lst, lst2, rec.dateTimeListField, Empty, Empty) passConversionTests( lst, rec.dateTimeListField, @@ -490,7 +546,7 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundEach { ("intField" -> 1) ~ ("stringField" -> "jsonobj1") ~ ("mapField" -> (("x" -> "1"))), ("intField" -> 2) ~ ("stringField" -> "jsonobj2") ~ ("mapField" -> (("x" -> "2"))) ) - passBasicTests(lst, lst2, rec.mandatoryJsonObjectListField, Empty) + passBasicTests(lst, lst2, rec.mandatoryJsonObjectListField, Empty, Empty) passConversionTests( lst, rec.mandatoryJsonObjectListField, @@ -517,7 +573,7 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundEach { val rec = MapTestRecord.createRecord val map = Map("a" -> "abc", "b" -> "def", "c" -> "ghi") val map2 = Map("a" -> "ab", "b" -> "de", "c" -> "gh") - passBasicTests(map, map2, rec.mandatoryStringMapField, Empty) + passBasicTests(map, map2, rec.mandatoryStringMapField, Empty, Empty) passConversionTests( map, rec.mandatoryStringMapField, @@ -537,7 +593,7 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundEach { val rec = MapTestRecord.createRecord val map = Map("a" -> 4, "b" -> 5, "c" -> 6) val map2 = Map("a" -> 1, "b" -> 2, "c" -> 3) - passBasicTests(map, map2, rec.mandatoryIntMapField, Empty) + passBasicTests(map, map2, rec.mandatoryIntMapField, Empty, Empty) passConversionTests( map, rec.mandatoryIntMapField, @@ -589,7 +645,7 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundEach { def toJsCmd = compactRender(srJson) } - passBasicTests(subRec, subRec2, rec.mandatoryBsonRecordField, Full(rec.legacyOptionalBsonRecordField), false) + passBasicTests(subRec, subRec2, rec.mandatoryBsonRecordField, Full(rec.optioalBsonRecordField), Full(rec.legacyOptionalBsonRecordField), false) passConversionTests( subRec, rec.mandatoryBsonRecordField, @@ -641,7 +697,7 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundEach { def toJsCmd = compactRender(sr2Json) } - passBasicTests(lst, lst2, rec.mandatoryBsonRecordListField, Full(rec.legacyOptionalBsonRecordListField)) + passBasicTests(lst, lst2, rec.mandatoryBsonRecordListField, Empty, Full(rec.legacyOptionalBsonRecordListField)) passConversionTests( lst, rec.mandatoryBsonRecordListField, diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala index a050b34695..6441ac512a 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala @@ -810,8 +810,14 @@ class MongoRecordSpec extends Specification with MongoTestKit { mfttr2.legacyOptionalDateField(new Date) mfttr2.legacyOptionalDateField.dirty_? must_== true - mfttr2.legacyOptionalObjectIdField(ObjectId.get) - mfttr2.legacyOptionalObjectIdField.dirty_? must_== true + mfttr2.optionalDateField(new Date) + mfttr2.optionalDateField.dirty_? must_== true + + mfttr2.optionalObjectIdField(ObjectId.get) + mfttr2.optionalObjectIdField.dirty_? must_== true + + mfttr2.optionalUUIDField(UUID.randomUUID()) + mfttr2.optionalUUIDField.dirty_? must_== true mfttr2.dirty_? must_== true mfttr2.update From a49ba66aa28c440aceb59ebc0fa5e25032603d7e Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sun, 3 Sep 2017 13:53:45 +0530 Subject: [PATCH 1675/1949] Change constructor parameter name from 'rec' to 'owner', deprecating the old name --- .../liftweb/record/field/BinaryField.scala | 16 ++++----- .../liftweb/record/field/BooleanField.scala | 16 ++++----- .../liftweb/record/field/CountryField.scala | 10 +++--- .../liftweb/record/field/DateTimeField.scala | 16 ++++----- .../liftweb/record/field/DecimalField.scala | 36 +++++++++---------- .../liftweb/record/field/DoubleField.scala | 18 ++++------ .../net/liftweb/record/field/EmailField.scala | 8 ++--- .../net/liftweb/record/field/EnumField.scala | 24 ++++++------- .../liftweb/record/field/EnumNameField.scala | 24 ++++++------- .../net/liftweb/record/field/IntField.scala | 16 ++++----- .../liftweb/record/field/LocaleField.scala | 8 ++--- .../net/liftweb/record/field/LongField.scala | 16 ++++----- .../liftweb/record/field/PasswordField.scala | 27 +++++++------- .../record/field/PostalCodeField.scala | 6 ++-- .../liftweb/record/field/StringField.scala | 26 ++++++-------- .../liftweb/record/field/TextareaField.scala | 8 ++--- .../liftweb/record/field/TimeZoneField.scala | 8 ++--- .../record/field/joda/JodaTimeField.scala | 16 ++++----- 18 files changed, 129 insertions(+), 170 deletions(-) diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/BinaryField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/BinaryField.scala index cfe34b4fd1..6132b8539b 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/BinaryField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/BinaryField.scala @@ -47,26 +47,22 @@ trait BinaryTypedField extends TypedField[Array[Byte]] { def setFromJValue(jvalue: JValue) = setFromJString(jvalue)(s => tryo(base64Decode(s))) } -class BinaryField[OwnerType <: Record[OwnerType]](rec: OwnerType) +class BinaryField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) override val owner: OwnerType) extends Field[Array[Byte], OwnerType] with MandatoryTypedField[Array[Byte]] with BinaryTypedField { - def owner = rec - - def this(rec: OwnerType, value: Array[Byte]) = { - this(rec) + def this(@deprecatedName('rec) owner: OwnerType, value: Array[Byte]) = { + this(owner) set(value) } def defaultValue = Array(0) } -class OptionalBinaryField[OwnerType <: Record[OwnerType]](rec: OwnerType) +class OptionalBinaryField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) override val owner: OwnerType) extends Field[Array[Byte], OwnerType] with OptionalTypedField[Array[Byte]] with BinaryTypedField { - def owner = rec - - def this(rec: OwnerType, value: Box[Array[Byte]]) = { - this(rec) + def this(@deprecatedName('rec) owner: OwnerType, value: Box[Array[Byte]]) = { + this(owner) setBox(value) } } diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/BooleanField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/BooleanField.scala index 7751bdcc8d..f297d20617 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/BooleanField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/BooleanField.scala @@ -68,26 +68,22 @@ trait BooleanTypedField extends TypedField[Boolean] { } } -class BooleanField[OwnerType <: Record[OwnerType]](rec: OwnerType) +class BooleanField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) override val owner: OwnerType) extends Field[Boolean, OwnerType] with MandatoryTypedField[Boolean] with BooleanTypedField { - def owner = rec - - def this(rec: OwnerType, value: Boolean) = { - this(rec) + def this(@deprecatedName('rec) owner: OwnerType, value: Boolean) = { + this(owner) set(value) } def defaultValue = false } -class OptionalBooleanField[OwnerType <: Record[OwnerType]](rec: OwnerType) +class OptionalBooleanField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) override val owner: OwnerType) extends Field[Boolean, OwnerType] with OptionalTypedField[Boolean] with BooleanTypedField { - def owner = rec - - def this(rec: OwnerType, value: Box[Boolean]) = { - this(rec) + def this(@deprecatedName('rec) owner: OwnerType, value: Box[Boolean]) = { + this(owner) setBox(value) } } diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/CountryField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/CountryField.scala index 88fae7d8b4..5df760a142 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/CountryField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/CountryField.scala @@ -67,11 +67,9 @@ object Countries extends Enumeration(1) { } -class CountryField[OwnerType <: Record[OwnerType]](rec: OwnerType) extends EnumField(rec, Countries) { +class CountryField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) owner: OwnerType) + extends EnumField(owner, Countries) -} - -class OptionalCountryField[OwnerType <: Record[OwnerType]](rec: OwnerType) extends OptionalEnumField(rec, Countries) { - -} +class OptionalCountryField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) owner: OwnerType) + extends OptionalEnumField(owner, Countries) diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/DateTimeField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/DateTimeField.scala index 0103ec65d0..df1dde278f 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/DateTimeField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/DateTimeField.scala @@ -74,26 +74,22 @@ trait DateTimeTypedField extends TypedField[Calendar] { } } -class DateTimeField[OwnerType <: Record[OwnerType]](rec: OwnerType) +class DateTimeField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) override val owner: OwnerType) extends Field[Calendar, OwnerType] with MandatoryTypedField[Calendar] with DateTimeTypedField { - def owner = rec - - def this(rec: OwnerType, value: Calendar) = { - this(rec) + def this(@deprecatedName('rec) owner: OwnerType, value: Calendar) = { + this(owner) setBox(Full(value)) } def defaultValue = Calendar.getInstance } -class OptionalDateTimeField[OwnerType <: Record[OwnerType]](rec: OwnerType) +class OptionalDateTimeField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) override val owner: OwnerType) extends Field[Calendar, OwnerType] with OptionalTypedField[Calendar] with DateTimeTypedField { - def owner = rec - - def this(rec: OwnerType, value: Box[Calendar]) = { - this(rec) + def this(@deprecatedName('rec) owner: OwnerType, value: Box[Calendar]) = { + this(owner) setBox(value) } } diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/DecimalField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/DecimalField.scala index 26ac91b523..ff856e4de7 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/DecimalField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/DecimalField.scala @@ -70,11 +70,11 @@ trait DecimalTypedField extends NumericTypedField[BigDecimal] { * * @author Derek Chen-Becker * - * @param rec The Record that owns this field + * @param owner The Record that owns this field * @param context The MathContext that controls precision and rounding * @param scale Controls the scale of the underlying BigDecimal */ -class DecimalField[OwnerType <: Record[OwnerType]](rec: OwnerType, val context : MathContext, val scale : Int) +class DecimalField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) override val owner: OwnerType, val context : MathContext, val scale : Int) extends Field[BigDecimal, OwnerType] with MandatoryTypedField[BigDecimal] with DecimalTypedField { /** @@ -82,11 +82,11 @@ class DecimalField[OwnerType <: Record[OwnerType]](rec: OwnerType, val context : * is set to MathContext.UNLIMITED (see note above about default precision). * The scale is taken from the initial value. * - * @param rec The Record that owns this field + * @param owner The Record that owns this field * @param value The initial value */ - def this(rec : OwnerType, value : BigDecimal) = { - this(rec, MathContext.UNLIMITED, value.scale) + def this(@deprecatedName('rec) owner : OwnerType, value : BigDecimal) = { + this(owner, MathContext.UNLIMITED, value.scale) set(value) } @@ -94,16 +94,14 @@ class DecimalField[OwnerType <: Record[OwnerType]](rec: OwnerType, val context : * Constructs a DecimalField with the specified initial value and context. * The scale is taken from the initial value. * - * @param rec The Record that owns this field + * @param owner The Record that owns this field * @param value The initial value * @param context The MathContext that controls precision and rounding */ - def this(rec : OwnerType, value : BigDecimal, context : MathContext) = { - this(rec, context, value.scale) + def this(@deprecatedName('rec) owner : OwnerType, value : BigDecimal, context : MathContext) = { + this(owner, context, value.scale) set(value) } - - def owner = rec } @@ -123,11 +121,11 @@ class DecimalField[OwnerType <: Record[OwnerType]](rec: OwnerType, val context : * * @author Derek Chen-Becker * - * @param rec The Record that owns this field + * @param owner The Record that owns this field * @param context The MathContext that controls precision and rounding * @param scale Controls the scale of the underlying BigDecimal */ -class OptionalDecimalField[OwnerType <: Record[OwnerType]](rec: OwnerType, val context : MathContext, val scale : Int) +class OptionalDecimalField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) override val owner: OwnerType, val context : MathContext, val scale : Int) extends Field[BigDecimal, OwnerType] with OptionalTypedField[BigDecimal] with DecimalTypedField { /** @@ -135,12 +133,12 @@ class OptionalDecimalField[OwnerType <: Record[OwnerType]](rec: OwnerType, val c * is set to MathContext.UNLIMITED (see note above about default precision). * The scale is taken from the initial value. * - * @param rec The Record that owns this field + * @param owner The Record that owns this field * @param value The initial value * @param scale the scale of the decimal field, since there might be no value */ - def this(rec : OwnerType, value : Box[BigDecimal], scale : Int) = { - this(rec, MathContext.UNLIMITED, scale) + def this(@deprecatedName('rec) owner : OwnerType, value : Box[BigDecimal], scale : Int) = { + this(owner, MathContext.UNLIMITED, scale) setBox(value) } @@ -148,16 +146,14 @@ class OptionalDecimalField[OwnerType <: Record[OwnerType]](rec: OwnerType, val c * Constructs a DecimalField with the specified initial value and context. * The scale is taken from the initial value. * - * @param rec The Record that owns this field + * @param owner The Record that owns this field * @param value The initial value * @param scale the scale of the decimal field, since there might be no value * @param context The MathContext that controls precision and rounding */ - def this(rec : OwnerType, value : Box[BigDecimal], scale : Int, context : MathContext) = { - this(rec, context, scale) + def this(@deprecatedName('rec) owner : OwnerType, value : Box[BigDecimal], scale : Int, context : MathContext) = { + this(owner, context, scale) setBox(value) } - - def owner = rec } diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/DoubleField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/DoubleField.scala index 276e086c25..d3c08ba403 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/DoubleField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/DoubleField.scala @@ -18,13 +18,11 @@ package net.liftweb package record package field -import scala.xml._ import net.liftweb.common._ import net.liftweb.http.{S} import json._ import net.liftweb.util._ import Helpers._ -import S._ trait DoubleTypedField extends NumericTypedField[Double] { @@ -52,25 +50,21 @@ trait DoubleTypedField extends NumericTypedField[Double] { } } -class DoubleField[OwnerType <: Record[OwnerType]](rec: OwnerType) +class DoubleField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) override val owner: OwnerType) extends Field[Double, OwnerType] with MandatoryTypedField[Double] with DoubleTypedField { - def this(rec: OwnerType, value: Double) = { - this(rec) + def this(@deprecatedName('rec) owner: OwnerType, value: Double) = { + this(owner) set(value) } - - def owner = rec } -class OptionalDoubleField[OwnerType <: Record[OwnerType]](rec: OwnerType) +class OptionalDoubleField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) override val owner: OwnerType) extends Field[Double, OwnerType] with OptionalTypedField[Double] with DoubleTypedField { - def this(rec: OwnerType, value: Box[Double]) = { - this(rec) + def this(@deprecatedName('rec) owner: OwnerType, value: Box[Double]) = { + this(owner) setBox(value) } - - def owner = rec } diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/EmailField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/EmailField.scala index 275889346d..4e2e682151 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/EmailField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/EmailField.scala @@ -45,9 +45,9 @@ trait EmailTypedField extends TypedField[String] { override def validations = validateEmail _ :: Nil } -class EmailField[OwnerType <: Record[OwnerType]](rec: OwnerType, maxLength: Int) - extends StringField[OwnerType](rec, maxLength) with EmailTypedField +class EmailField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) owner: OwnerType, maxLength: Int) + extends StringField[OwnerType](owner, maxLength) with EmailTypedField -class OptionalEmailField[OwnerType <: Record[OwnerType]](rec: OwnerType, maxLength: Int) - extends OptionalStringField[OwnerType](rec, maxLength) with EmailTypedField +class OptionalEmailField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) owner: OwnerType, maxLength: Int) + extends OptionalStringField[OwnerType](owner, maxLength) with EmailTypedField diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/EnumField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/EnumField.scala index 74a5199f3e..3d712daebd 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/EnumField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/EnumField.scala @@ -105,27 +105,27 @@ trait EnumTypedField[EnumType <: Enumeration] extends TypedField[EnumType#Value] def setFromJValue(jvalue: JValue): Box[EnumType#Value] = setFromJIntOrdinal(jvalue) } -class EnumField[OwnerType <: Record[OwnerType], EnumType <: Enumeration](rec: OwnerType, protected val enum: EnumType)(implicit m: Manifest[EnumType#Value]) - extends Field[EnumType#Value, OwnerType] with MandatoryTypedField[EnumType#Value] with EnumTypedField[EnumType] -{ - def this(rec: OwnerType, enum: EnumType, value: EnumType#Value)(implicit m: Manifest[EnumType#Value]) = { - this(rec, enum) +class EnumField[OwnerType <: Record[OwnerType], EnumType <: Enumeration](@deprecatedName('rec) override val owner: OwnerType, + protected val enum: EnumType)(implicit m: Manifest[EnumType#Value] +) extends Field[EnumType#Value, OwnerType] with MandatoryTypedField[EnumType#Value] with EnumTypedField[EnumType] { + + def this(@deprecatedName('rec) owner: OwnerType, enum: EnumType, value: EnumType#Value)(implicit m: Manifest[EnumType#Value]) = { + this(owner, enum) set(value) } - def owner = rec protected val valueManifest = m } -class OptionalEnumField[OwnerType <: Record[OwnerType], EnumType <: Enumeration](rec: OwnerType, protected val enum: EnumType)(implicit m: Manifest[EnumType#Value]) - extends Field[EnumType#Value, OwnerType] with OptionalTypedField[EnumType#Value] with EnumTypedField[EnumType] -{ - def this(rec: OwnerType, enum: EnumType, value: Box[EnumType#Value])(implicit m: Manifest[EnumType#Value]) = { - this(rec, enum) +class OptionalEnumField[OwnerType <: Record[OwnerType], EnumType <: Enumeration](@deprecatedName('rec) override val owner: OwnerType, + protected val enum: EnumType)(implicit m: Manifest[EnumType#Value] +) extends Field[EnumType#Value, OwnerType] with OptionalTypedField[EnumType#Value] with EnumTypedField[EnumType] { + + def this(@deprecatedName('rec) owner: OwnerType, enum: EnumType, value: Box[EnumType#Value])(implicit m: Manifest[EnumType#Value]) = { + this(owner, enum) setBox(value) } - def owner = rec protected val valueManifest = m } diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/EnumNameField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/EnumNameField.scala index fa78bf5bb9..8042044d77 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/EnumNameField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/EnumNameField.scala @@ -79,27 +79,27 @@ trait EnumNameTypedField[EnumType <: Enumeration] extends TypedField[EnumType#Va def setFromJValue(jvalue: JValue): Box[EnumType#Value] = setFromJStringName(jvalue) } -class EnumNameField[OwnerType <: Record[OwnerType], EnumType <: Enumeration](rec: OwnerType, protected val enum: EnumType)(implicit m: Manifest[EnumType#Value]) - extends Field[EnumType#Value, OwnerType] with MandatoryTypedField[EnumType#Value] with EnumNameTypedField[EnumType] -{ - def this(rec: OwnerType, enum: EnumType, value: EnumType#Value)(implicit m: Manifest[EnumType#Value]) = { - this(rec, enum) +class EnumNameField[OwnerType <: Record[OwnerType], EnumType <: Enumeration](@deprecatedName('rec) override val owner: OwnerType, + protected val enum: EnumType)(implicit m: Manifest[EnumType#Value] +) extends Field[EnumType#Value, OwnerType] with MandatoryTypedField[EnumType#Value] with EnumNameTypedField[EnumType] { + + def this(@deprecatedName('rec) owner: OwnerType, enum: EnumType, value: EnumType#Value)(implicit m: Manifest[EnumType#Value]) = { + this(owner, enum) set(value) } - def owner = rec protected val valueManifest = m } -class OptionalEnumNameField[OwnerType <: Record[OwnerType], EnumType <: Enumeration](rec: OwnerType, protected val enum: EnumType)(implicit m: Manifest[EnumType#Value]) - extends Field[EnumType#Value, OwnerType] with OptionalTypedField[EnumType#Value] with EnumNameTypedField[EnumType] -{ - def this(rec: OwnerType, enum: EnumType, value: Box[EnumType#Value])(implicit m: Manifest[EnumType#Value]) = { - this(rec, enum) +class OptionalEnumNameField[OwnerType <: Record[OwnerType], EnumType <: Enumeration](@deprecatedName('rec) override val owner: OwnerType, + protected val enum: EnumType)(implicit m: Manifest[EnumType#Value] +) extends Field[EnumType#Value, OwnerType] with OptionalTypedField[EnumType#Value] with EnumNameTypedField[EnumType] { + + def this(@deprecatedName('rec) owner: OwnerType, enum: EnumType, value: Box[EnumType#Value])(implicit m: Manifest[EnumType#Value]) = { + this(owner, enum) setBox(value) } - def owner = rec protected val valueManifest = m } diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/IntField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/IntField.scala index df4f5c65fd..7335ef6aec 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/IntField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/IntField.scala @@ -48,24 +48,20 @@ trait IntTypedField extends NumericTypedField[Int] { } } -class IntField[OwnerType <: Record[OwnerType]](rec: OwnerType) +class IntField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) override val owner: OwnerType) extends Field[Int, OwnerType] with MandatoryTypedField[Int] with IntTypedField { - def owner = rec - - def this(rec: OwnerType, value: Int) = { - this(rec) + def this(@deprecatedName('rec) owner: OwnerType, value: Int) = { + this(owner) set(value) } } -class OptionalIntField[OwnerType <: Record[OwnerType]](rec: OwnerType) +class OptionalIntField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) override val owner: OwnerType) extends Field[Int, OwnerType] with OptionalTypedField[Int] with IntTypedField { - def owner = rec - - def this(rec: OwnerType, value: Box[Int]) = { - this(rec) + def this(@deprecatedName('rec) owner: OwnerType, value: Box[Int]) = { + this(owner) setBox(value) } } diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/LocaleField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/LocaleField.scala index e4db3463d8..f055fadcb1 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/LocaleField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/LocaleField.scala @@ -49,8 +49,8 @@ trait LocaleTypedField extends TypedField[String] { } } -class LocaleField[OwnerType <: Record[OwnerType]](rec: OwnerType) - extends StringField(rec, 16) with LocaleTypedField { +class LocaleField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) owner: OwnerType) + extends StringField(owner, 16) with LocaleTypedField { override def defaultValue = Locale.getDefault.toString @@ -63,8 +63,8 @@ class LocaleField[OwnerType <: Record[OwnerType]](rec: OwnerType) } -class OptionalLocaleField[OwnerType <: Record[OwnerType]](rec: OwnerType) - extends OptionalStringField(rec, 16) with LocaleTypedField { +class OptionalLocaleField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) owner: OwnerType) + extends OptionalStringField(owner, 16) with LocaleTypedField { /** Label for the selection item representing Empty, show when this field is optional. Defaults to the empty string. */ def emptyOptionLabel: String = "" diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/LongField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/LongField.scala index b81409b6b6..9723746e9b 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/LongField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/LongField.scala @@ -51,25 +51,21 @@ trait LongTypedField extends NumericTypedField[Long] { } } -class LongField[OwnerType <: Record[OwnerType]](rec: OwnerType) +class LongField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) override val owner: OwnerType) extends Field[Long, OwnerType] with MandatoryTypedField[Long] with LongTypedField { - def this(rec: OwnerType, value: Long) = { - this(rec) + def this(@deprecatedName('rec) owner: OwnerType, value: Long) = { + this(owner) set(value) } - - def owner = rec } -class OptionalLongField[OwnerType <: Record[OwnerType]](rec: OwnerType) +class OptionalLongField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) override val owner: OwnerType) extends Field[Long, OwnerType] with OptionalTypedField[Long] with LongTypedField { - def this(rec: OwnerType, value: Box[Long]) = { - this(rec) + def this(@deprecatedName('rec) owner: OwnerType, value: Box[Long]) = { + this(owner) setBox(value) } - - def owner = rec } diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/PasswordField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/PasswordField.scala index 1e1798ac2f..722fe9031c 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/PasswordField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/PasswordField.scala @@ -67,11 +67,14 @@ trait PasswordTypedField extends TypedField[String] { */ def setFromAny(in: Any): Box[String] = { in match { - case (a: Array[String]) if (a.length == 2 && a(0) == a(1)) => setBoxPlain(Full(a(0))) + case (a: Array[String]) if a.length == 2 && a(0) == a(1) => setBoxPlain(Full(a(0))) case (h1: String) :: (h2: String) :: Nil if h1 == h2 => setBoxPlain(Full(h1)) - case (hash: String) if(hash.startsWith("$2a$")) => setBox(Full(hash)) - case Full(hash: String) if(hash.startsWith("$2a$")) => setBox(Full(hash)) - case _ => {invalidPw = true; invalidMsg = S.?("passwords.do.not.match"); Failure(invalidMsg)} + case (hash: String) if hash.startsWith("$2a$") => setBox(Full(hash)) + case Full(hash: String) if hash.startsWith("$2a$") => setBox(Full(hash)) + case _ => + invalidPw = true + invalidMsg = S.?("passwords.do.not.match") + Failure(invalidMsg) } } @@ -128,16 +131,14 @@ trait PasswordTypedField extends TypedField[String] { } -class PasswordField[OwnerType <: Record[OwnerType]](rec: OwnerType) +class PasswordField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) override val owner: OwnerType) extends Field[String, OwnerType] with MandatoryTypedField[String] with PasswordTypedField { - def this(rec: OwnerType, value: String) = { - this(rec) + def this(@deprecatedName('rec) owner: OwnerType, value: String) = { + this(owner) setPlain(value) } - def owner = rec - override def apply(in: Box[String]): OwnerType = if(owner.meta.mutable_?) { this.setBoxPlain(in) @@ -148,15 +149,13 @@ class PasswordField[OwnerType <: Record[OwnerType]](rec: OwnerType) } -class OptionalPasswordField[OwnerType <: Record[OwnerType]](rec: OwnerType) +class OptionalPasswordField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) override val owner: OwnerType) extends Field[String, OwnerType] with OptionalTypedField[String] with PasswordTypedField { - def this(rec: OwnerType, value: Box[String]) = { - this(rec) + def this(@deprecatedName('rec) owner: OwnerType, value: Box[String]) = { + this(owner) setBoxPlain(value) } - - def owner = rec override def apply(in: Box[String]): OwnerType = if(owner.meta.mutable_?) { diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/PostalCodeField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/PostalCodeField.scala index 1c4d82609d..71ce1c26c0 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/PostalCodeField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/PostalCodeField.scala @@ -38,7 +38,7 @@ trait PostalCodeTypedField extends StringTypedField { def validatePostalCode(in: ValueType): List[FieldError] = { toBoxMyType(in) match { - case Full(zip) if (optional_? && zip.isEmpty) => Nil + case Full(zip) if optional_? && zip.isEmpty => Nil case _ => country.value match { case Countries.USA => valRegex(RegexPattern.compile("[0-9]{5}(\\-[0-9]{4})?"), S.?("invalid.zip.code"))(in) @@ -58,7 +58,7 @@ trait PostalCodeTypedField extends StringTypedField { } } -class PostalCodeField[OwnerType <: Record[OwnerType]](rec: OwnerType, val country: CountryField[OwnerType]) extends StringField(rec, 32) with PostalCodeTypedField +class PostalCodeField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) owner: OwnerType, val country: CountryField[OwnerType]) extends StringField(owner, 32) with PostalCodeTypedField -class OptionalPostalCodeField[OwnerType <: Record[OwnerType]](rec: OwnerType, val country: CountryField[OwnerType]) extends OptionalStringField(rec, 32) with PostalCodeTypedField +class OptionalPostalCodeField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) owner: OwnerType, val country: CountryField[OwnerType]) extends OptionalStringField(owner, 32) with PostalCodeTypedField diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/StringField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/StringField.scala index 01304d0308..b58c23c69b 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/StringField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/StringField.scala @@ -36,7 +36,7 @@ trait StringTypedField extends TypedField[String] with StringValidators { def maxLen = maxLength def setFromAny(in: Any): Box[String] = in match { - case seq: Seq[_] if !seq.isEmpty => setFromAny(seq.head) + case seq: Seq[_] if seq.nonEmpty => setFromAny(seq.head) case _ => genericSetFromAny(in) } @@ -73,21 +73,19 @@ trait StringTypedField extends TypedField[String] with StringValidators { } } -class StringField[OwnerType <: Record[OwnerType]](rec: OwnerType, val maxLength: Int) +class StringField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) override val owner: OwnerType, val maxLength: Int) extends Field[String, OwnerType] with MandatoryTypedField[String] with StringTypedField { - def this(rec: OwnerType, maxLength: Int, value: String) = { - this(rec, maxLength) + def this(@deprecatedName('rec) owner: OwnerType, maxLength: Int, value: String) = { + this(owner, maxLength) set(value) } - def this(rec: OwnerType, value: String) = { - this(rec, 100) + def this(@deprecatedName('rec) owner: OwnerType, value: String) = { + this(owner, 100) set(value) } - def owner = rec - protected def valueTypeToBoxString(in: ValueType): Box[String] = toBoxMyType(in) protected def boxStrToValType(in: Box[String]): ValueType = toValueType(in) } @@ -99,21 +97,19 @@ abstract class UniqueIdField[OwnerType <: Record[OwnerType]](rec: OwnerType, ove } -class OptionalStringField[OwnerType <: Record[OwnerType]](rec: OwnerType, val maxLength: Int) +class OptionalStringField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) override val owner: OwnerType, val maxLength: Int) extends Field[String, OwnerType] with OptionalTypedField[String] with StringTypedField { - def this(rec: OwnerType, maxLength: Int, value: Box[String]) = { - this(rec, maxLength) + def this(@deprecatedName('rec) owner: OwnerType, maxLength: Int, value: Box[String]) = { + this(owner, maxLength) setBox(value) } - def this(rec: OwnerType, value: Box[String]) = { - this(rec, 100) + def this(@deprecatedName('rec) owner: OwnerType, value: Box[String]) = { + this(owner, 100) setBox(value) } - def owner = rec - protected def valueTypeToBoxString(in: ValueType): Box[String] = toBoxMyType(in) protected def boxStrToValType(in: Box[String]): ValueType = toValueType(in) } diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/TextareaField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/TextareaField.scala index 7a39d6c1bd..31dd07a5f0 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/TextareaField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/TextareaField.scala @@ -50,9 +50,9 @@ trait TextareaTypedField extends StringTypedField { def textareaCols = 20 } -class TextareaField[OwnerType <: Record[OwnerType]](rec: OwnerType, maxLength: Int) - extends StringField(rec, maxLength) with TextareaTypedField +class TextareaField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) owner: OwnerType, maxLength: Int) + extends StringField(owner, maxLength) with TextareaTypedField -class OptionalTextareaField[OwnerType <: Record[OwnerType]](rec: OwnerType, maxLength: Int) - extends OptionalStringField(rec, maxLength) with TextareaTypedField +class OptionalTextareaField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) owner: OwnerType, maxLength: Int) + extends OptionalStringField(owner, maxLength) with TextareaTypedField diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/TimeZoneField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/TimeZoneField.scala index 506a982779..eb48c76b19 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/TimeZoneField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/TimeZoneField.scala @@ -52,8 +52,8 @@ trait TimeZoneTypedField extends StringTypedField { } } -class TimeZoneField[OwnerType <: Record[OwnerType]](rec: OwnerType) - extends StringField(rec, 32) with TimeZoneTypedField { +class TimeZoneField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) owner: OwnerType) + extends StringField(owner, 32) with TimeZoneTypedField { override def defaultValue = TimeZone.getDefault.getID @@ -63,6 +63,6 @@ class TimeZoneField[OwnerType <: Record[OwnerType]](rec: OwnerType) } } -class OptionalTimeZoneField[OwnerType <: Record[OwnerType]](rec: OwnerType) - extends OptionalStringField(rec, 32) with TimeZoneTypedField +class OptionalTimeZoneField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) owner: OwnerType) + extends OptionalStringField(owner, 32) with TimeZoneTypedField diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/joda/JodaTimeField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/joda/JodaTimeField.scala index 646d97d8d7..4a02593419 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/joda/JodaTimeField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/joda/JodaTimeField.scala @@ -74,26 +74,22 @@ trait JodaTimeTypedField extends TypedField[DateTime] with JodaHelpers { } } -class JodaTimeField[OwnerType <: Record[OwnerType]](rec: OwnerType) +class JodaTimeField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) override val owner: OwnerType) extends Field[DateTime, OwnerType] with MandatoryTypedField[DateTime] with JodaTimeTypedField { - def owner = rec - - def this(rec: OwnerType, value: DateTime) = { - this(rec) + def this(@deprecatedName('rec) owner: OwnerType, value: DateTime) = { + this(owner) setBox(Full(value)) } def defaultValue = DateTime.now } -class OptionalJodaTimeField[OwnerType <: Record[OwnerType]](rec: OwnerType) +class OptionalJodaTimeField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) override val owner: OwnerType) extends Field[DateTime, OwnerType] with OptionalTypedField[DateTime] with JodaTimeTypedField { - def owner = rec - - def this(rec: OwnerType, value: Box[DateTime]) = { - this(rec) + def this(@deprecatedName('rec) owner: OwnerType, value: Box[DateTime]) = { + this(owner) setBox(value) } } From 5473563e6e665ccac20787eb520c38283f81b22f Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sat, 9 Sep 2017 11:24:32 +0530 Subject: [PATCH 1676/1949] Remove the 'override' modified from places where there's no real overriding happening (i.e. we are implementing an abstract method) --- .../record/field/BsonRecordField.scala | 16 +++++++-------- .../mongodb/record/field/CaseClassField.scala | 12 +++++------ .../mongodb/record/field/JObjectField.scala | 20 +++++++++---------- .../record/field/JsonObjectField.scala | 4 ++-- .../mongodb/record/field/ObjectIdField.scala | 10 +++++----- .../mongodb/record/field/PatternField.scala | 14 ++++++------- .../mongodb/record/field/UUIDField.scala | 16 +++++++-------- .../liftweb/record/field/BinaryField.scala | 4 ++-- .../liftweb/record/field/BooleanField.scala | 4 ++-- .../liftweb/record/field/DateTimeField.scala | 4 ++-- .../liftweb/record/field/DecimalField.scala | 4 ++-- .../liftweb/record/field/DoubleField.scala | 4 ++-- .../net/liftweb/record/field/EnumField.scala | 4 ++-- .../liftweb/record/field/EnumNameField.scala | 4 ++-- .../net/liftweb/record/field/IntField.scala | 4 ++-- .../net/liftweb/record/field/LongField.scala | 4 ++-- .../liftweb/record/field/PasswordField.scala | 6 +++--- .../liftweb/record/field/StringField.scala | 4 ++-- .../record/field/joda/JodaTimeField.scala | 4 ++-- 19 files changed, 71 insertions(+), 71 deletions(-) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala index ce3aa46878..da6b339252 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala @@ -32,7 +32,7 @@ import scala.xml._ /** Field that contains an entire record represented as an inline object value. Inspired by JSONSubRecordField */ abstract class BsonRecordTypedField[OwnerType <: BsonRecord[OwnerType], SubRecordType <: BsonRecord[SubRecordType]] -(override val owner: OwnerType, valueMeta: BsonMetaRecord[SubRecordType])(implicit subRecordType: Manifest[SubRecordType]) +(val owner: OwnerType, valueMeta: BsonMetaRecord[SubRecordType])(implicit subRecordType: Manifest[SubRecordType]) extends Field[SubRecordType, OwnerType] { def this(owner: OwnerType, valueMeta: BsonMetaRecord[SubRecordType], value: Box[SubRecordType]) @@ -41,26 +41,26 @@ abstract class BsonRecordTypedField[OwnerType <: BsonRecord[OwnerType], SubRecor setBox(value) } - override def asJs = asJValue match { + def asJs = asJValue match { case JNothing => JsNull case jv => new JsExp { lazy val toJsCmd = compactRender(jv) } } - override def toForm: Box[NodeSeq] = Empty + def toForm: Box[NodeSeq] = Empty - override def setFromString(s: String): Box[SubRecordType] = valueMeta.fromJsonString(s) + def setFromString(s: String): Box[SubRecordType] = valueMeta.fromJsonString(s) - override def setFromAny(in: Any): Box[SubRecordType] = in match { + def setFromAny(in: Any): Box[SubRecordType] = in match { case dbo: DBObject => setBox(Full(valueMeta.fromDBObject(dbo))) case dbo: Document => setBox(Full(valueMeta.fromDocument(dbo))) case _ => genericSetFromAny(in) } - override def asJValue: JValue = valueBox.map(_.asJValue) openOr (JNothing: JValue) + def asJValue: JValue = valueBox.map(_.asJValue) openOr (JNothing: JValue) - override def setFromJValue(jvalue: JValue): Box[SubRecordType] = jvalue match { + def setFromJValue(jvalue: JValue): Box[SubRecordType] = jvalue match { case JNothing|JNull if optional_? => setBox(Empty) case _ => setBox(valueMeta.fromJValue(jvalue)) } @@ -75,7 +75,7 @@ class BsonRecordField[OwnerType <: BsonRecord[OwnerType], SubRecordType <: BsonR set(value) } - override def defaultValue = valueMeta.createRecord + def defaultValue = valueMeta.createRecord } class OptionalBsonRecordField[OwnerType <: BsonRecord[OwnerType], SubRecordType <: BsonRecord[SubRecordType]] diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/CaseClassField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/CaseClassField.scala index b4172a12d1..2a282587c2 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/CaseClassField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/CaseClassField.scala @@ -31,7 +31,7 @@ import net.liftweb.http.js.JsExp import org.bson.Document import scala.collection.JavaConverters._ -abstract class CaseClassTypedField[OwnerType <: Record[OwnerType], CaseType](override val owner: OwnerType)(implicit mf: Manifest[CaseType]) +abstract class CaseClassTypedField[OwnerType <: Record[OwnerType], CaseType](val owner: OwnerType)(implicit mf: Manifest[CaseType]) extends Field[CaseType, OwnerType] with MongoFieldFlavor[CaseType] { // override this for custom formats @@ -66,7 +66,7 @@ abstract class CaseClassTypedField[OwnerType <: Record[OwnerType], CaseType](ove setFromJValue(jvalue) } - override def setFromString(in: String): Box[CaseType] = Helpers.tryo { + def setFromString(in: String): Box[CaseType] = Helpers.tryo { JsonParser.parse(in).extract[CaseType] } @@ -90,7 +90,7 @@ class CaseClassField[OwnerType <: Record[OwnerType], CaseType](owner: OwnerType) setBox(Full(value)) } - override def defaultValue = null.asInstanceOf[MyType] + def defaultValue = null.asInstanceOf[MyType] } @deprecated("Use the more consistently named 'CaseClassField' instead. This class will be removed in Lift 4.", "3.2") @@ -106,7 +106,7 @@ class OptionalCaseClassField[OwnerType <: Record[OwnerType], CaseType](owner: Ow } } -class CaseClassListField[OwnerType <: Record[OwnerType], CaseType](override val owner: OwnerType)(implicit mf: Manifest[CaseType]) +class CaseClassListField[OwnerType <: Record[OwnerType], CaseType](val owner: OwnerType)(implicit mf: Manifest[CaseType]) extends Field[List[CaseType], OwnerType] with MandatoryTypedField[List[CaseType]] with MongoFieldFlavor[List[CaseType]] { // override this for custom formats @@ -119,7 +119,7 @@ class CaseClassListField[OwnerType <: Record[OwnerType], CaseType](override val def toForm: Box[NodeSeq] = Empty - override def defaultValue: MyType = Nil + def defaultValue: MyType = Nil def asJValue: JValue = JArray(value.map(v => Extraction.decompose(v))) @@ -168,7 +168,7 @@ class CaseClassListField[OwnerType <: Record[OwnerType], CaseType](override val case _ => setBox(Empty) } - override def setFromString(in: String): Box[MyType] = { + def setFromString(in: String): Box[MyType] = { setFromJValue(JsonParser.parse(in)) } } diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala index 2e869db9fd..77a7e43989 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JObjectField.scala @@ -31,13 +31,13 @@ import scala.xml.NodeSeq trait JObjectTypedField[OwnerType <: BsonRecord[OwnerType]] extends TypedField[JObject] with Field[JObject, OwnerType] with MongoFieldFlavor[JObject] { - override def setFromJValue(jvalue: JValue): Box[JObject] = jvalue match { + def setFromJValue(jvalue: JValue): Box[JObject] = jvalue match { case JNothing|JNull if optional_? => setBox(Empty) case jo: JObject => setBox(Full(jo)) case other => setBox(FieldHelpers.expectedA("JObject", other)) } - override def setFromAny(in: Any): Box[JObject] = in match { + def setFromAny(in: Any): Box[JObject] = in match { case dbo: DBObject => setBox(setFromDBObject(dbo)) case doc: Document => setBox(setFromDocument(doc)) case jv: JObject => setBox(Full(jv)) @@ -52,27 +52,27 @@ trait JObjectTypedField[OwnerType <: BsonRecord[OwnerType]] extends TypedField[J } // assume string is json - override def setFromString(in: String): Box[JObject] = { + def setFromString(in: String): Box[JObject] = { // use lift-json to parse string into a JObject setBox(tryo(JsonParser.parse(in).asInstanceOf[JObject])) } - override def toForm: Box[NodeSeq] = Empty + def toForm: Box[NodeSeq] = Empty - override def asDBObject: DBObject = valueBox + def asDBObject: DBObject = valueBox .map { v => JObjectParser.parse(v)(owner.meta.formats) } .openOr(new BasicDBObject) - override def setFromDBObject(obj: DBObject): Box[JObject] = + def setFromDBObject(obj: DBObject): Box[JObject] = Full(JObjectParser.serialize(obj)(owner.meta.formats).asInstanceOf[JObject]) def setFromDocument(obj: Document): Box[JObject] = Full(JObjectParser.serialize(obj)(owner.meta.formats).asInstanceOf[JObject]) - override def asJValue: JValue = valueBox openOr (JNothing: JValue) + def asJValue: JValue = valueBox openOr (JNothing: JValue) } -class JObjectField[OwnerType <: BsonRecord[OwnerType]](@deprecatedName('rec) override val owner: OwnerType) +class JObjectField[OwnerType <: BsonRecord[OwnerType]](@deprecatedName('rec) val owner: OwnerType) extends JObjectTypedField[OwnerType] with MandatoryTypedField[JObject] { def this(owner: OwnerType, value: JObject) = { @@ -80,11 +80,11 @@ class JObjectField[OwnerType <: BsonRecord[OwnerType]](@deprecatedName('rec) ove setBox(Full(value)) } - override def defaultValue = JObject(List()) + def defaultValue = JObject(List()) } -class OptionalJObjectField[OwnerType <: BsonRecord[OwnerType]](override val owner: OwnerType) +class OptionalJObjectField[OwnerType <: BsonRecord[OwnerType]](val owner: OwnerType) extends JObjectTypedField[OwnerType] with OptionalTypedField[JObject] { def this(owner: OwnerType, value: Box[JObject]) = { diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JsonObjectField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JsonObjectField.scala index da1515f844..076ae53482 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JsonObjectField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/JsonObjectField.scala @@ -26,7 +26,7 @@ import com.mongodb.{BasicDBList, DBObject} import scala.collection.JavaConverters._ abstract class JsonObjectTypedField[OwnerType <: BsonRecord[OwnerType], JObjectType <: JsonObject[JObjectType]] -(override val owner: OwnerType, valueMeta: JsonObjectMeta[JObjectType]) +(val owner: OwnerType, valueMeta: JsonObjectMeta[JObjectType]) extends Field[JObjectType, OwnerType] with MongoFieldFlavor[JObjectType] { implicit val formats = owner.meta.formats @@ -34,7 +34,7 @@ abstract class JsonObjectTypedField[OwnerType <: BsonRecord[OwnerType], JObjectT /** * Convert the field value to an XHTML representation */ - override def toForm: Box[NodeSeq] = Empty // FIXME + def toForm: Box[NodeSeq] = Empty // FIXME /** Encode the field value into a JValue */ def asJValue: JValue = valueBox.map(_.asJObject) openOr (JNothing: JValue) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/ObjectIdField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/ObjectIdField.scala index d40e530aa6..98e610fb15 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/ObjectIdField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/ObjectIdField.scala @@ -68,12 +68,12 @@ trait ObjectIdTypedField[OwnerType <: BsonRecord[OwnerType]] extends TypedField[ tabindex={tabIndex.toString}/> } - override def toForm = uniqueFieldId match { + def toForm = uniqueFieldId match { case Full(id) => Full(elem % ("id" -> id)) case _ => Full(elem) } - override def asJs: JsExp = asJValue match { + def asJs: JsExp = asJValue match { case JNothing => JsNull case jv => JsRaw(compactRender(jv)) } @@ -82,7 +82,7 @@ trait ObjectIdTypedField[OwnerType <: BsonRecord[OwnerType]] extends TypedField[ } -class ObjectIdField[OwnerType <: BsonRecord[OwnerType]](@deprecatedName('rec) override val owner: OwnerType) +class ObjectIdField[OwnerType <: BsonRecord[OwnerType]](@deprecatedName('rec) val owner: OwnerType) extends MandatoryTypedField[ObjectId] with ObjectIdTypedField[OwnerType] { def this(owner: OwnerType, value: ObjectId) = { @@ -90,13 +90,13 @@ class ObjectIdField[OwnerType <: BsonRecord[OwnerType]](@deprecatedName('rec) ov setBox(Full(value)) } - override def defaultValue = new ObjectId + def defaultValue = new ObjectId def createdAt: Date = this.get.getDate } -class OptionalObjectIdField[OwnerType <: BsonRecord[OwnerType]](override val owner: OwnerType) +class OptionalObjectIdField[OwnerType <: BsonRecord[OwnerType]](val owner: OwnerType) extends OptionalTypedField[ObjectId] with ObjectIdTypedField[OwnerType] { def this(owner: OwnerType, value: Box[ObjectId]) = { diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/PatternField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/PatternField.scala index 186909b11d..f6f4d84193 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/PatternField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/PatternField.scala @@ -26,8 +26,8 @@ import net.liftweb.util.Helpers.tryo import scala.xml.NodeSeq -abstract class PatternTypedField[OwnerType <: BsonRecord[OwnerType]](override val owner: OwnerType) extends Field[Pattern, OwnerType] { - override def setFromAny(in: Any): Box[Pattern] = in match { +abstract class PatternTypedField[OwnerType <: BsonRecord[OwnerType]](val owner: OwnerType) extends Field[Pattern, OwnerType] { + def setFromAny(in: Any): Box[Pattern] = in match { case p: Pattern => setBox(Full(p)) case Some(p: Pattern) => setBox(Full(p)) case Full(p: Pattern) => setBox(Full(p)) @@ -40,7 +40,7 @@ abstract class PatternTypedField[OwnerType <: BsonRecord[OwnerType]](override va case o => setFromString(o.toString) } - override def setFromJValue(jvalue: JValue): Box[Pattern] = jvalue match { + def setFromJValue(jvalue: JValue): Box[Pattern] = jvalue match { case JNothing|JNull if optional_? => setBox(Empty) case JObject(JField("$regex", JString(s)) :: JField("$flags", JInt(f)) :: Nil) => setBox(Full(Pattern.compile(s, f.intValue))) @@ -48,15 +48,15 @@ abstract class PatternTypedField[OwnerType <: BsonRecord[OwnerType]](override va } // parse String into a JObject - override def setFromString(in: String): Box[Pattern] = tryo(JsonParser.parse(in)) match { + def setFromString(in: String): Box[Pattern] = tryo(JsonParser.parse(in)) match { case Full(jv: JValue) => setFromJValue(jv) case f: Failure => setBox(f) case other => setBox(Failure("Error parsing String into a JValue: "+in)) } - override def toForm: Box[NodeSeq] = Empty + def toForm: Box[NodeSeq] = Empty - override def asJs = asJValue match { + def asJs = asJValue match { case JNothing => JsNull case jv => Str(compactRender(jv)) } @@ -70,7 +70,7 @@ class PatternField[OwnerType <: BsonRecord[OwnerType]](@deprecatedName('rec) own setBox(Full(value)) } - override def defaultValue = Pattern.compile("") + def defaultValue = Pattern.compile("") } class OptionalPatternField[OwnerType <: BsonRecord[OwnerType]](owner: OwnerType) extends PatternTypedField[OwnerType](owner) with OptionalTypedField[Pattern] { diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/UUIDField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/UUIDField.scala index 6c6dec0934..94d6787721 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/UUIDField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/UUIDField.scala @@ -42,13 +42,13 @@ trait UUIDTypedField[OwnerType <: BsonRecord[OwnerType]] extends TypedField[UUID case o => setFromString(o.toString) } - override def setFromJValue(jvalue: JValue): Box[UUID] = jvalue match { + def setFromJValue(jvalue: JValue): Box[UUID] = jvalue match { case JNothing|JNull if optional_? => setBox(Empty) case JObject(JField("$uuid", JString(s)) :: Nil) => setFromString(s) case other => setBox(FieldHelpers.expectedA("JObject", other)) } - override def setFromString(in: String): Box[UUID] = tryo(UUID.fromString(in)) match { + def setFromString(in: String): Box[UUID] = tryo(UUID.fromString(in)) match { case Full(uid: UUID) => setBox(Full(uid)) case f: Failure => setBox(f) case _ => setBox(Failure(s"Invalid UUID string: $in")) @@ -61,20 +61,20 @@ trait UUIDTypedField[OwnerType <: BsonRecord[OwnerType]] extends TypedField[UUID tabindex={tabIndex.toString}/> } - override def toForm: Box[NodeSeq] = uniqueFieldId match { + def toForm: Box[NodeSeq] = uniqueFieldId match { case Full(id) => Full(elem % ("id" -> id)) case _ => Full(elem) } - override def asJs: JsExp = asJValue match { + def asJs: JsExp = asJValue match { case JNothing => JsNull case jv => JsRaw(compactRender(jv)) } - override def asJValue: JValue = valueBox.map(v => JsonUUID(v)) openOr (JNothing: JValue) + def asJValue: JValue = valueBox.map(v => JsonUUID(v)) openOr (JNothing: JValue) } -class UUIDField[OwnerType <: BsonRecord[OwnerType]](@deprecatedName('rec) override val owner: OwnerType) +class UUIDField[OwnerType <: BsonRecord[OwnerType]](@deprecatedName('rec) val owner: OwnerType) extends UUIDTypedField[OwnerType] with MandatoryTypedField[UUID] { def this(owner: OwnerType, value: UUID) = { @@ -82,11 +82,11 @@ class UUIDField[OwnerType <: BsonRecord[OwnerType]](@deprecatedName('rec) overri setBox(Full(value)) } - override def defaultValue = UUID.randomUUID + def defaultValue = UUID.randomUUID } -class OptionalUUIDField[OwnerType <: BsonRecord[OwnerType]](override val owner: OwnerType) +class OptionalUUIDField[OwnerType <: BsonRecord[OwnerType]](val owner: OwnerType) extends UUIDTypedField[OwnerType] with OptionalTypedField[UUID] { def this(owner: OwnerType, value: Box[UUID]) = { diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/BinaryField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/BinaryField.scala index 6132b8539b..53e7f84f29 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/BinaryField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/BinaryField.scala @@ -47,7 +47,7 @@ trait BinaryTypedField extends TypedField[Array[Byte]] { def setFromJValue(jvalue: JValue) = setFromJString(jvalue)(s => tryo(base64Decode(s))) } -class BinaryField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) override val owner: OwnerType) +class BinaryField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) val owner: OwnerType) extends Field[Array[Byte], OwnerType] with MandatoryTypedField[Array[Byte]] with BinaryTypedField { def this(@deprecatedName('rec) owner: OwnerType, value: Array[Byte]) = { @@ -58,7 +58,7 @@ class BinaryField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) override def defaultValue = Array(0) } -class OptionalBinaryField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) override val owner: OwnerType) +class OptionalBinaryField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) val owner: OwnerType) extends Field[Array[Byte], OwnerType] with OptionalTypedField[Array[Byte]] with BinaryTypedField { def this(@deprecatedName('rec) owner: OwnerType, value: Box[Array[Byte]]) = { diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/BooleanField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/BooleanField.scala index f297d20617..6219df1861 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/BooleanField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/BooleanField.scala @@ -68,7 +68,7 @@ trait BooleanTypedField extends TypedField[Boolean] { } } -class BooleanField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) override val owner: OwnerType) +class BooleanField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) val owner: OwnerType) extends Field[Boolean, OwnerType] with MandatoryTypedField[Boolean] with BooleanTypedField { def this(@deprecatedName('rec) owner: OwnerType, value: Boolean) = { @@ -79,7 +79,7 @@ class BooleanField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) overrid def defaultValue = false } -class OptionalBooleanField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) override val owner: OwnerType) +class OptionalBooleanField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) val owner: OwnerType) extends Field[Boolean, OwnerType] with OptionalTypedField[Boolean] with BooleanTypedField { def this(@deprecatedName('rec) owner: OwnerType, value: Box[Boolean]) = { diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/DateTimeField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/DateTimeField.scala index df1dde278f..d5897c9635 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/DateTimeField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/DateTimeField.scala @@ -74,7 +74,7 @@ trait DateTimeTypedField extends TypedField[Calendar] { } } -class DateTimeField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) override val owner: OwnerType) +class DateTimeField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) val owner: OwnerType) extends Field[Calendar, OwnerType] with MandatoryTypedField[Calendar] with DateTimeTypedField { def this(@deprecatedName('rec) owner: OwnerType, value: Calendar) = { @@ -85,7 +85,7 @@ class DateTimeField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) overri def defaultValue = Calendar.getInstance } -class OptionalDateTimeField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) override val owner: OwnerType) +class OptionalDateTimeField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) val owner: OwnerType) extends Field[Calendar, OwnerType] with OptionalTypedField[Calendar] with DateTimeTypedField { def this(@deprecatedName('rec) owner: OwnerType, value: Box[Calendar]) = { diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/DecimalField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/DecimalField.scala index ff856e4de7..53251d9629 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/DecimalField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/DecimalField.scala @@ -74,7 +74,7 @@ trait DecimalTypedField extends NumericTypedField[BigDecimal] { * @param context The MathContext that controls precision and rounding * @param scale Controls the scale of the underlying BigDecimal */ -class DecimalField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) override val owner: OwnerType, val context : MathContext, val scale : Int) +class DecimalField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) val owner: OwnerType, val context : MathContext, val scale : Int) extends Field[BigDecimal, OwnerType] with MandatoryTypedField[BigDecimal] with DecimalTypedField { /** @@ -125,7 +125,7 @@ class DecimalField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) overrid * @param context The MathContext that controls precision and rounding * @param scale Controls the scale of the underlying BigDecimal */ -class OptionalDecimalField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) override val owner: OwnerType, val context : MathContext, val scale : Int) +class OptionalDecimalField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) val owner: OwnerType, val context : MathContext, val scale : Int) extends Field[BigDecimal, OwnerType] with OptionalTypedField[BigDecimal] with DecimalTypedField { /** diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/DoubleField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/DoubleField.scala index d3c08ba403..8aff8c103f 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/DoubleField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/DoubleField.scala @@ -50,7 +50,7 @@ trait DoubleTypedField extends NumericTypedField[Double] { } } -class DoubleField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) override val owner: OwnerType) +class DoubleField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) val owner: OwnerType) extends Field[Double, OwnerType] with MandatoryTypedField[Double] with DoubleTypedField { def this(@deprecatedName('rec) owner: OwnerType, value: Double) = { @@ -59,7 +59,7 @@ class DoubleField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) override } } -class OptionalDoubleField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) override val owner: OwnerType) +class OptionalDoubleField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) val owner: OwnerType) extends Field[Double, OwnerType] with OptionalTypedField[Double] with DoubleTypedField { def this(@deprecatedName('rec) owner: OwnerType, value: Box[Double]) = { diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/EnumField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/EnumField.scala index 3d712daebd..473b149870 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/EnumField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/EnumField.scala @@ -105,7 +105,7 @@ trait EnumTypedField[EnumType <: Enumeration] extends TypedField[EnumType#Value] def setFromJValue(jvalue: JValue): Box[EnumType#Value] = setFromJIntOrdinal(jvalue) } -class EnumField[OwnerType <: Record[OwnerType], EnumType <: Enumeration](@deprecatedName('rec) override val owner: OwnerType, +class EnumField[OwnerType <: Record[OwnerType], EnumType <: Enumeration](@deprecatedName('rec) val owner: OwnerType, protected val enum: EnumType)(implicit m: Manifest[EnumType#Value] ) extends Field[EnumType#Value, OwnerType] with MandatoryTypedField[EnumType#Value] with EnumTypedField[EnumType] { @@ -117,7 +117,7 @@ class EnumField[OwnerType <: Record[OwnerType], EnumType <: Enumeration](@deprec protected val valueManifest = m } -class OptionalEnumField[OwnerType <: Record[OwnerType], EnumType <: Enumeration](@deprecatedName('rec) override val owner: OwnerType, +class OptionalEnumField[OwnerType <: Record[OwnerType], EnumType <: Enumeration](@deprecatedName('rec) val owner: OwnerType, protected val enum: EnumType)(implicit m: Manifest[EnumType#Value] ) extends Field[EnumType#Value, OwnerType] with OptionalTypedField[EnumType#Value] with EnumTypedField[EnumType] { diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/EnumNameField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/EnumNameField.scala index 8042044d77..fb1487120c 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/EnumNameField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/EnumNameField.scala @@ -79,7 +79,7 @@ trait EnumNameTypedField[EnumType <: Enumeration] extends TypedField[EnumType#Va def setFromJValue(jvalue: JValue): Box[EnumType#Value] = setFromJStringName(jvalue) } -class EnumNameField[OwnerType <: Record[OwnerType], EnumType <: Enumeration](@deprecatedName('rec) override val owner: OwnerType, +class EnumNameField[OwnerType <: Record[OwnerType], EnumType <: Enumeration](@deprecatedName('rec) val owner: OwnerType, protected val enum: EnumType)(implicit m: Manifest[EnumType#Value] ) extends Field[EnumType#Value, OwnerType] with MandatoryTypedField[EnumType#Value] with EnumNameTypedField[EnumType] { @@ -91,7 +91,7 @@ class EnumNameField[OwnerType <: Record[OwnerType], EnumType <: Enumeration](@de protected val valueManifest = m } -class OptionalEnumNameField[OwnerType <: Record[OwnerType], EnumType <: Enumeration](@deprecatedName('rec) override val owner: OwnerType, +class OptionalEnumNameField[OwnerType <: Record[OwnerType], EnumType <: Enumeration](@deprecatedName('rec) val owner: OwnerType, protected val enum: EnumType)(implicit m: Manifest[EnumType#Value] ) extends Field[EnumType#Value, OwnerType] with OptionalTypedField[EnumType#Value] with EnumNameTypedField[EnumType] { diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/IntField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/IntField.scala index 7335ef6aec..d02cf60ff2 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/IntField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/IntField.scala @@ -48,7 +48,7 @@ trait IntTypedField extends NumericTypedField[Int] { } } -class IntField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) override val owner: OwnerType) +class IntField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) val owner: OwnerType) extends Field[Int, OwnerType] with MandatoryTypedField[Int] with IntTypedField { def this(@deprecatedName('rec) owner: OwnerType, value: Int) = { @@ -57,7 +57,7 @@ class IntField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) override va } } -class OptionalIntField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) override val owner: OwnerType) +class OptionalIntField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) val owner: OwnerType) extends Field[Int, OwnerType] with OptionalTypedField[Int] with IntTypedField { def this(@deprecatedName('rec) owner: OwnerType, value: Box[Int]) = { diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/LongField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/LongField.scala index 9723746e9b..5a70da19d6 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/LongField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/LongField.scala @@ -51,7 +51,7 @@ trait LongTypedField extends NumericTypedField[Long] { } } -class LongField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) override val owner: OwnerType) +class LongField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) val owner: OwnerType) extends Field[Long, OwnerType] with MandatoryTypedField[Long] with LongTypedField { def this(@deprecatedName('rec) owner: OwnerType, value: Long) = { @@ -60,7 +60,7 @@ class LongField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) override v } } -class OptionalLongField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) override val owner: OwnerType) +class OptionalLongField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) val owner: OwnerType) extends Field[Long, OwnerType] with OptionalTypedField[Long] with LongTypedField { def this(@deprecatedName('rec) owner: OwnerType, value: Box[Long]) = { diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/PasswordField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/PasswordField.scala index 722fe9031c..40c1d612ae 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/PasswordField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/PasswordField.scala @@ -131,7 +131,7 @@ trait PasswordTypedField extends TypedField[String] { } -class PasswordField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) override val owner: OwnerType) +class PasswordField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) val owner: OwnerType) extends Field[String, OwnerType] with MandatoryTypedField[String] with PasswordTypedField { def this(@deprecatedName('rec) owner: OwnerType, value: String) = { @@ -149,7 +149,7 @@ class PasswordField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) overri } -class OptionalPasswordField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) override val owner: OwnerType) +class OptionalPasswordField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) val owner: OwnerType) extends Field[String, OwnerType] with OptionalTypedField[String] with PasswordTypedField { def this(@deprecatedName('rec) owner: OwnerType, value: Box[String]) = { @@ -157,7 +157,7 @@ class OptionalPasswordField[OwnerType <: Record[OwnerType]](@deprecatedName('rec setBoxPlain(value) } - override def apply(in: Box[String]): OwnerType = + override def apply(in: Box[String]): OwnerType = if(owner.meta.mutable_?) { this.setBoxPlain(in) owner diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/StringField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/StringField.scala index b58c23c69b..013fb0b15b 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/StringField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/StringField.scala @@ -73,7 +73,7 @@ trait StringTypedField extends TypedField[String] with StringValidators { } } -class StringField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) override val owner: OwnerType, val maxLength: Int) +class StringField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) val owner: OwnerType, val maxLength: Int) extends Field[String, OwnerType] with MandatoryTypedField[String] with StringTypedField { def this(@deprecatedName('rec) owner: OwnerType, maxLength: Int, value: String) = { @@ -97,7 +97,7 @@ abstract class UniqueIdField[OwnerType <: Record[OwnerType]](rec: OwnerType, ove } -class OptionalStringField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) override val owner: OwnerType, val maxLength: Int) +class OptionalStringField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) val owner: OwnerType, val maxLength: Int) extends Field[String, OwnerType] with OptionalTypedField[String] with StringTypedField { def this(@deprecatedName('rec) owner: OwnerType, maxLength: Int, value: Box[String]) = { diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/joda/JodaTimeField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/joda/JodaTimeField.scala index 4a02593419..f33642a4c1 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/joda/JodaTimeField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/joda/JodaTimeField.scala @@ -74,7 +74,7 @@ trait JodaTimeTypedField extends TypedField[DateTime] with JodaHelpers { } } -class JodaTimeField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) override val owner: OwnerType) +class JodaTimeField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) val owner: OwnerType) extends Field[DateTime, OwnerType] with MandatoryTypedField[DateTime] with JodaTimeTypedField { def this(@deprecatedName('rec) owner: OwnerType, value: DateTime) = { @@ -85,7 +85,7 @@ class JodaTimeField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) overri def defaultValue = DateTime.now } -class OptionalJodaTimeField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) override val owner: OwnerType) +class OptionalJodaTimeField[OwnerType <: Record[OwnerType]](@deprecatedName('rec) val owner: OwnerType) extends Field[DateTime, OwnerType] with OptionalTypedField[DateTime] with JodaTimeTypedField { def this(@deprecatedName('rec) owner: OwnerType, value: Box[DateTime]) = { From 94cc62c1902d8fe42537fe9b6d61389ce67f3c97 Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Sun, 10 Sep 2017 10:55:06 +0530 Subject: [PATCH 1677/1949] Implement `transform` method to transform a Box into another Box. Remove the `mapFailure` method as it's convered by the `transform` method. Implement `or` using `transform`. Change `flip` to take a function, instead of a `PartialFunction` --- .../main/scala/net/liftweb/common/Box.scala | 77 +++++++------------ .../scala/net/liftweb/common/BoxSpec.scala | 72 ++++++++++------- 2 files changed, 73 insertions(+), 76 deletions(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index cc0d03b833..827df3f1e8 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -518,30 +518,6 @@ sealed abstract class Box[+A] extends Product with Serializable{ */ def foreach[U](f: A => U): Unit = {} - /** - * If this box is a `Failure`, returns a `Failure` box that results from passing this box - * to `fn`. Otherwise, returns this box. Used for transforming Failures without having to - * cover all three cases of Box, which is required by `map`. - * - * `map` is only applied if the box is full, `mapFailure` is only applied if this box is a `Failure`. - * - * @example {{{ - * // Returns `Failure("Changed failure")` because this box is a failure. - * Failure("Original Failure") mapFailure { failed => Failure("Changed failure") } - * - * // Returns Full("some-value") on which it was called - * Full("some-value") mapFailure { failure => Failure("Changed failure") } - * - * // Returns this Empty instance - * Empty mapFailure { case value => value } - * }}} - * - * @param fn the function that will be used to transform the Failure if this box is a failure. - * @return A `Failure` instance that results from applying `fn` if this box is a `Failure`, otherwise - * the same instance on which it was called. - */ - def mapFailure(fn: Failure => Failure): Box[A] = this - /** * If this box is `Full` and contains an object of type `B`, returns a `Full` * of type `Box[B]`. Otherwise, returns `Empty`. @@ -575,18 +551,18 @@ sealed abstract class Box[+A] extends Product with Serializable{ */ def asA[B](implicit m: Manifest[B]): Box[B] = Empty - /** - * Return this Box if `Full`, or the specified alternative if it is - * empty. Equivalent to `Option`'s `[[scala.Option.orElse orElse]]`. - */ - def or[B >: A](alternative: => Box[B]): Box[B] = alternative - /** * Returns an `[[scala.collection.Iterator Iterator]]` over the value * contained in this `Box`, if any. */ def elements: Iterator[A] = Iterator.empty + /** + * Return this Box if `Full`, or the specified alternative if it is + * empty. Equivalent to `Option`'s `[[scala.Option.orElse orElse]]`. + */ + def or[B >: A](alternative: => Box[B]): Box[B] = transform { case _: EmptyBox => alternative } + /** * Get a `java.util.Iterator` from the Box. */ @@ -814,30 +790,39 @@ sealed abstract class Box[+A] extends Product with Serializable{ } /** - * Returns a `Full` box containing the results of applying `flipFn` to this box if it is a `Failure`, - * `ParamFailure` or `Empty`, and `flipFn` is defined for it. Returns `Empty` if this box is `Full`. - * In other words, it "flips" the full/empty status of this Box. + * Transforms this box using the `transformFn`. If `transformFn` is defined for this box, + * returns the result of applying `transformFn` to it. Otherwise, returns this box unchanged. * * @example {{{ + * * // Returns Full("alternative") because the partial function covers the case. - * Failure("error") flip { case Failure("error", Empty, Empty) => "alternative" } + * Full("error") transform { case Full("error") => Full("alternative") } * - * // Returns Empty because the partial function doesn't cover the case. - * Failure("another-error") flip { case Failure("error", Empty, Empty) => "alternative" } + * // Returns Full(1), this Full box unchanged, because the partial function doesn't cover the case. + * Full(1) transform { case Full(2) => Failure("error") } + * + * // Returns this Failure("another-error") unchanged because the partial function doesn't cover the case. + * Failure("another-error") transform { case Failure("error", Empty, Empty) => Full("alternative") } * * // Returns Full("alternative") for an Empty box since `partialFn` is defined for Empty - * Empty flip { case Empty => "alternative" } + * Empty transform { case Empty => Full("alternative") } * * // Returns Empty because the partial function is not defined for Empty - * Empty flip { case Failure("error", Empty, Empty) => "alternative" } - * - * // Returns Empty for a Full box - * Full(1) flip { case _ => Failure("error") } + * Empty transform { case Failure("error", Empty, Empty) => Full("alternative") } * * }}} */ - def flip[B](flipFn: PartialFunction[EmptyBox, B]): Box[B] = this match { - case e: EmptyBox if flipFn.isDefinedAt(e) => Full(flipFn(e)) + def transform[B >: A](transformFn: PartialFunction[Box[A], Box[B]]): Box[B] = { + transformFn.applyOrElse(this, (thisBox: Box[A]) => thisBox) + } + + /** + * Returns a `Full` box containing the results of applying `flipFn` to this box if it is a `Failure`, + * `ParamFailure` or `Empty`. Returns `Empty` if this box is `Full`. In other words, it "flips" the + * full/empty status of this Box. + */ + def flip[B](flipFn: EmptyBox => B): Box[B] = this match { + case e: EmptyBox => Full(flipFn(e)) case _ => Empty } } @@ -852,8 +837,6 @@ final case class Full[+A](value: A) extends Box[A] { override def openOr[B >: A](default: => B): B = value - override def or[B >: A](alternative: => Box[B]): Box[B] = this - override def exists(func: A => Boolean): Boolean = func(value) override def forall(func: A => Boolean): Boolean = func(value) @@ -920,8 +903,6 @@ sealed abstract class EmptyBox extends Box[Nothing] with Serializable { override def openOr[B >: Nothing](default: => B): B = default - override def or[B >: Nothing](alternative: => Box[B]): Box[B] = alternative - override def filter(p: Nothing => Boolean): Box[Nothing] = this override def ?~(msg: => String): Failure = Failure(msg, Empty, Empty) @@ -956,8 +937,6 @@ sealed case class Failure(msg: String, exception: Box[Throwable], chain: Box[Fai override def map[B](f: A => B): Box[B] = this - override def mapFailure(f: (Failure) => Failure): Box[A] = f(this) - override def flatMap[B](f: A => Box[B]): Box[B] = this override def isA[B](cls: Class[B]): Box[B] = this diff --git a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala index bf797feba4..a920aa4a7c 100644 --- a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala +++ b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala @@ -152,10 +152,6 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { Full(Empty).flatten must_== Empty } } - "define a 'mapFailure' method returning itself." in { - val failure = Failure("new-error", Empty, Empty) - Full(1).mapFailure(_ => failure) must_== Full(1) - } "define a 'collect' method that takes a PartialFunction to transform its contents" in { "If the partial-function is defined for the contents of this box, returns a full box containing the result of applying that partial function to this Box's contents" in { Full("Albus") collect { case "Albus" => "Dumbledore"} must_== Full("Dumbledore") @@ -164,8 +160,21 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { Full("Hermione") collect { case "Albus" => "Dumbledore"} must beEmpty } } + "define a 'transform' method that takes a PartialFunction to transform this box into another box" in { + "If the partial-function is defined for this box, returns the result of applying the partial function to it" in { + Full(404) transform { + case Full(x: Int) if x != 200 => Failure("Server error") + } must_== Failure("Server error") + } + "If the partial-function is not defined for this box, returns itself unchanged" in { + Full("Intended Result") transform { + case _: EmptyBox => Full("Alternative") + case Full("Unexpected Result") => Full("Alternative") + } must_== Full("Intended Result") + } + } "define a 'flip' method returning Empty" in { - Full(1) flip { case _ => "alternative" } must_== Empty + Full(1) flip { _ => "No data found" } mustEqual Empty } "define an 'elements' method returning an iterator containing its value" in { Full(1).elements.next must_== 1 @@ -313,25 +322,26 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { "define a 'flatten' method returning Empty" in { Empty.flatten must beEmpty } - "define a 'mapFailure' method returning itself." in { - val failure = Failure("new-error", Empty, Empty) - Empty.mapFailure(_ => failure) must beEmpty - } "define a 'collect' method returning Empty" in { Empty collect { case _ => "Some Value" } must beEmpty } - "define a 'flip' method that takes a PartialFunction to transform this Empty box into something else" in { - "If the partial-function is defined for Empty, returns a full box containing the result of applying the partial function to it" in { - Empty flip { case Empty => "Return Of The Jedi" } must_== Full("Return Of The Jedi") - Empty flip { - case Failure("The Phantom Menace", Empty, Empty) => "Attack" - case Empty => "Return Of The Jedi" - } must_== Full("Return Of The Jedi") + "define a 'transform' method that takes a PartialFunction to transform this Empty box into another box" in { + "If the partial-function is defined for Empty, returns the result of applying the partial function to it" in { + Empty transform { + case Failure("error", Empty, Empty) => Full("failure-alternative") + case Empty => Full("alternative") + } must_== Full("alternative") } "If the partial-function is not defined for Empty, returns Empty" in { - Empty flip { case Failure("The Phantom Menace", Empty, Empty) => "Return Of The Jedi" } must_== Empty + Empty transform { case Failure("The Phantom Menace", Empty, Empty) => Full("Return Of The Jedi") } must_== Empty } } + "define a 'flip' method returning a Full box" in { + Empty flip { + case Empty => "flipped-empty" + case _ => "flipped-failure" + } mustEqual Full("flipped-empty") + } "define an 'elements' method returning an empty iterator" in { Empty.elements.hasNext must beFalse } @@ -392,22 +402,30 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { Failure("error", Empty, Empty) flatMap {x: String => Full(x.toString)} must_== Failure("error", Empty, Empty) Failure("error", Empty, Empty).flatten must_== Failure("error", Empty, Empty) } - "define a 'mapFailure' method that transforms it into another Failure instance." in { - val exception = new Exception("transformed") - Failure("error", Empty, Empty) mapFailure { _ => Failure("new-error", Full(exception), Empty) } must_== Failure("new-error", Full(exception), Empty) - } "define a 'collect' method returning itself" in { Failure("error", Empty, Empty) collect { case _ => "Some Value" } must_== Failure("error", Empty, Empty) } - "define a 'flip' method that takes a PartialFunction to transform this Failure into something else" in { - "If the partial-function is defined for this Failure, returns a full box containing the result of applying the partial function to it" in { - Failure("The Phantom Menace") flip { case Failure("The Phantom Menace", Empty, Empty) => "Return Of The Jedi" } must_== Full("Return Of The Jedi") - Failure("The Phantom Menace") flip { case Failure("The Phantom Menace", Empty, Empty) => Failure("Attack") } must_== Full(Failure("Attack")) + "define a 'transform' method that takes a PartialFunction to transform this Failure into another box" in { + "If the partial-function is defined for this Failure, returns the result of applying the partial function to it" in { + Failure("The Phantom Menace") transform { + case Failure("The Phantom Menace", Empty, Empty) => Full("Return Of The Jedi") + } must_== Full("Return Of The Jedi") + + Failure("The Phantom Menace") transform { + case Failure("The Phantom Menace", Empty, Empty) => Failure("Clones") + case _ => Full("Jedi") + } must_== Failure("Clones") } - "If the partial-function is not defined for this Failure, returns Empty" in { - Failure("Attack Of The Clones") flip { case Failure("The Phantom Menace", Empty, Empty) => "Return Of The Jedi" } must_== Empty + "If the partial-function is not defined for this Failure, returns itself unchanged" in { + Failure("Clones") transform { case Failure("The Phantom Menace", Empty, Empty) => Full("Jedi") } must_== Failure("Clones") } } + "define a 'flip' method returning a Full box" in { + Failure("error", Empty, Empty) flip { + case Empty => "flipped-empty" + case _: Failure => "flipped-failure" + } must_== Full("flipped-failure") + } "return itself when asked for its status with the operator ?~" in { Failure("error", Empty, Empty) ?~ "nothing" must_== Failure("error", Empty, Empty) } From 36b8bc88a13803b149c70e49f5daabada6d3ce89 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sun, 10 Sep 2017 20:41:44 -0400 Subject: [PATCH 1678/1949] Work around awkwardness with snippet timer installation w/ installSnippetTimer --- .../scala/net/liftweb/http/LiftRules.scala | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 79ac130eda..c94d36a68f 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -859,11 +859,29 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { * - NoOpSnippetTimer that does nothing * - LoggingSnippetTimer that logs snippet times. * - * Since this is a FactoryMaker you can programmatically override it for an individual request + * To enable snippet timing, invoke `LiftRules.installSnippetTimer`. Once enabled you can use + * the snippet timer like a regular `FactoryMaker`. If you only want snippet timing for certain + * sessions or requests, invoke `installSnippetTimer` with the `NoOpSnippetTimer` and change + * the value for the sessions or requests where you want snippet timing. + * + * Since this is a `FactoryMaker` you can programmatically override it for an individual request * or session as you see fit. You can also implement your own timer! */ val snippetTimer = new LiftRulesGuardedSetting[Option[FactoryMaker[SnippetTimer]]]("snippetTimer", None) + /** + * Enable snippet timing and install a default snippet timer. + * This method can only be invoked during boot. + * + * This method only enables snippet timing and sets the default snippet timer. If you want to + * change snipping timing behavior for specific sessions or requests, you'll want to interact + * with the underlying `FactoryMaker` after its set up. + */ + def installSnippetTimer(default: SnippetTimer): Unit = { + val factoryMaker = new FactoryMaker[SnippetTimer](default) {} + snippetTimer.set(Some(factoryMaker)) + } + /** * Implementation for snippetNamesToSearch that looks first in a package named by taking the current template path. * For example, suppose the following is configured in Boot: From 734dba7f0c31103e12f568bfdd587f9b6e1f3275 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sun, 10 Sep 2017 20:52:33 -0400 Subject: [PATCH 1679/1949] Switch to using NamedPF.applyBox helper --- web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index 6943543a74..f717ad5b75 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -2576,7 +2576,7 @@ class LiftSession(private[http] val _contextPath: String, val underlyingId: Stri // Runs some base setup tasks before returning the comet. private def buildCometByCreationInfo(creationInfo: CometCreationInfo): Box[LiftCometActor] = { LiftRules.cometCreationFactory.vend.apply(creationInfo) or - LiftRules.cometCreation.toList.find(_.isDefinedAt(creationInfo)).map(_.apply(creationInfo)) or { + NamedPF.applyBox(creationInfo, LiftRules.cometCreation.toList) or { val cometType = findType[LiftCometActor]( creationInfo.cometType, From 05587032a93aacb68b4086b366f4f44984f7076e Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sun, 10 Sep 2017 20:55:04 -0400 Subject: [PATCH 1680/1949] Simplify including context path on comet session loss --- web/webkit/src/main/resources/toserve/lift.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/web/webkit/src/main/resources/toserve/lift.js b/web/webkit/src/main/resources/toserve/lift.js index 1da92f990e..bf376f3097 100644 --- a/web/webkit/src/main/resources/toserve/lift.js +++ b/web/webkit/src/main/resources/toserve/lift.js @@ -77,11 +77,7 @@ cometGetTimeout: 140000, cometFailureRetryTimeout: 10000, cometOnSessionLost: function(contextPath) { - if (contextPath === null || contextPath === undefined) { - window.location.href = "/"; - } else { - window.location.href = contextPath; - } + window.location.href = contextPath || "/"; }, cometServer: null, cometOnError: function(e) { From 22f2a51dad0d4dc6e1e0831bfcc7b5819494c132 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sun, 10 Sep 2017 21:00:12 -0400 Subject: [PATCH 1681/1949] URL encode context path and escape single quotes --- web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index f7edc1dd49..6a1fce3c32 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -776,7 +776,7 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { * lift.cometOnSessionLost reloads the current page by default. */ val noCometSessionCmd = new FactoryMaker[JsCmd]( - () => JsCmds.Run(s"lift.cometOnSessionLost('${S.contextPath}')") + () => JsCmds.Run(s"lift.cometOnSessionLost('${urlEncode(S.contextPath).replace("'", "\\'")}')") ) {} /** From f504a823edee83a02e1431aea546081a5b25a27d Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Mon, 11 Sep 2017 20:16:27 +0530 Subject: [PATCH 1682/1949] Rollback 'or' to use its own simpler implementation. Add an additional explanatory comment on `transform` about its intended use --- .../common/src/main/scala/net/liftweb/common/Box.scala | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index 827df3f1e8..8c50154dae 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -561,7 +561,7 @@ sealed abstract class Box[+A] extends Product with Serializable{ * Return this Box if `Full`, or the specified alternative if it is * empty. Equivalent to `Option`'s `[[scala.Option.orElse orElse]]`. */ - def or[B >: A](alternative: => Box[B]): Box[B] = transform { case _: EmptyBox => alternative } + def or[B >: A](alternative: => Box[B]): Box[B] /** * Get a `java.util.Iterator` from the Box. @@ -793,6 +793,10 @@ sealed abstract class Box[+A] extends Product with Serializable{ * Transforms this box using the `transformFn`. If `transformFn` is defined for this box, * returns the result of applying `transformFn` to it. Otherwise, returns this box unchanged. * + * If you want to change the content of a `Full` box, using [[map]] or [[collect]] might be better + * suited to that purpose. If you want to convert an `Empty`, `Failure` or a `ParamFailure` into a + * `Full` box, you should use [[flip]]. + * * @example {{{ * * // Returns Full("alternative") because the partial function covers the case. @@ -837,6 +841,8 @@ final case class Full[+A](value: A) extends Box[A] { override def openOr[B >: A](default: => B): B = value + override def or[B >: A](alternative: => Box[B]): Box[B] = this + override def exists(func: A => Boolean): Boolean = func(value) override def forall(func: A => Boolean): Boolean = func(value) @@ -903,6 +909,8 @@ sealed abstract class EmptyBox extends Box[Nothing] with Serializable { override def openOr[B >: Nothing](default: => B): B = default + override def or[B >: Nothing](alternative: => Box[B]): Box[B] = alternative + override def filter(p: Nothing => Boolean): Box[Nothing] = this override def ?~(msg: => String): Failure = Failure(msg, Empty, Empty) From 783bf6e1f2a922c15157731336757ed6dce0987e Mon Sep 17 00:00:00 2001 From: bhashit parikh Date: Mon, 11 Sep 2017 21:10:34 +0530 Subject: [PATCH 1683/1949] Put backticks around the method-name links --- core/common/src/main/scala/net/liftweb/common/Box.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index 8c50154dae..a5e3052d91 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -793,9 +793,9 @@ sealed abstract class Box[+A] extends Product with Serializable{ * Transforms this box using the `transformFn`. If `transformFn` is defined for this box, * returns the result of applying `transformFn` to it. Otherwise, returns this box unchanged. * - * If you want to change the content of a `Full` box, using [[map]] or [[collect]] might be better + * If you want to change the content of a `Full` box, using `[[map]]` or `[[collect]]` might be better * suited to that purpose. If you want to convert an `Empty`, `Failure` or a `ParamFailure` into a - * `Full` box, you should use [[flip]]. + * `Full` box, you should use `[[flip]]`. * * @example {{{ * From d506eb4b19e1dd65d164ece8a52dd31bce4fa2d5 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Tue, 12 Sep 2017 22:14:03 -0400 Subject: [PATCH 1684/1949] Back out the urlencode when passing context path to client --- web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index cf4a26371c..b96e61141e 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -776,7 +776,7 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { * lift.cometOnSessionLost reloads the current page by default. */ val noCometSessionCmd = new FactoryMaker[JsCmd]( - () => JsCmds.Run(s"lift.cometOnSessionLost('${urlEncode(S.contextPath).replace("'", "\\'")}')") + () => JsCmds.Run(s"lift.cometOnSessionLost('${S.contextPath.replace("'", "\\'")}')") ) {} /** From 77c2e9a8ac7cd012b758431bca3c8e2866d234b2 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Tue, 12 Sep 2017 22:21:01 -0400 Subject: [PATCH 1685/1949] Further iteration on LAPinger doc --- core/actor/src/main/scala/net/liftweb/actor/LAPinger.scala | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/actor/src/main/scala/net/liftweb/actor/LAPinger.scala b/core/actor/src/main/scala/net/liftweb/actor/LAPinger.scala index c7d745f18e..a444ff242a 100644 --- a/core/actor/src/main/scala/net/liftweb/actor/LAPinger.scala +++ b/core/actor/src/main/scala/net/liftweb/actor/LAPinger.scala @@ -34,8 +34,8 @@ object ThreadPoolRules { } /** - * The LAPinger object schedules a LiftActor to be pinged "delay" miliseconds in the future. - * The schedule method return a ScheduledFuture object which can be cancelled if necessary. + * LAPinger is for scheduling LiftActors to be pinged with an arbitrary message at some point + * in the future. */ object LAPinger { @@ -99,4 +99,3 @@ private object TF extends ThreadFactory { d } } - From cd491b45ecd2106ec8bcc24bd0ac6ea91cdb8b9d Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Wed, 13 Sep 2017 08:52:54 -0400 Subject: [PATCH 1686/1949] Add some scaladocs for session cookie path stuff --- .../net/liftweb/mapper/ProtoExtendedSession.scala | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/persistence/mapper/src/main/scala/net/liftweb/mapper/ProtoExtendedSession.scala b/persistence/mapper/src/main/scala/net/liftweb/mapper/ProtoExtendedSession.scala index e1fb46c3ea..df040fc8d7 100644 --- a/persistence/mapper/src/main/scala/net/liftweb/mapper/ProtoExtendedSession.scala +++ b/persistence/mapper/src/main/scala/net/liftweb/mapper/ProtoExtendedSession.scala @@ -59,6 +59,13 @@ trait UserIdAsString { def userIdAsString: String } +/** + * The root trait for defining the session cookie path for extended sessions + * that defines the default session cookie path: "/". + * + * Extend this trait then mix it into an extended session singleton to change + * the cookie path. See also: [[ContextPathExtendedCookie]] + */ trait ProtoSessionCookiePath { def sessionCookiePath: String = "/" } @@ -131,6 +138,12 @@ KeyedMetaMapper[Long, T] with ProtoSessionCookiePath { } } +/** + * Mix this in to your extended session singleton to set the cookie path + * to the context path for your application. This is useful if you have + * multiple applications on a single application server and want to ensure + * their cookies don't cross-pollinate. + */ trait ContextPathExtendedCookie extends ProtoSessionCookiePath { override def sessionCookiePath = S.contextPath } From 87595529b7e281feeed61ab75ed765edb36134d2 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Wed, 13 Sep 2017 08:55:15 -0400 Subject: [PATCH 1687/1949] Add example usage to the scaladoc --- .../scala/net/liftweb/mapper/ProtoExtendedSession.scala | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/persistence/mapper/src/main/scala/net/liftweb/mapper/ProtoExtendedSession.scala b/persistence/mapper/src/main/scala/net/liftweb/mapper/ProtoExtendedSession.scala index df040fc8d7..e1d200a9f9 100644 --- a/persistence/mapper/src/main/scala/net/liftweb/mapper/ProtoExtendedSession.scala +++ b/persistence/mapper/src/main/scala/net/liftweb/mapper/ProtoExtendedSession.scala @@ -143,6 +143,14 @@ KeyedMetaMapper[Long, T] with ProtoSessionCookiePath { * to the context path for your application. This is useful if you have * multiple applications on a single application server and want to ensure * their cookies don't cross-pollinate. + * + * Example usage: + * + * {{{ + * case class AppExtendedSession extends ProtoExtendedSession[AppExtendedSession] + * object MetaAppExtendedSession extends MetaProtoExtendedSession[AppExtendedSession] + * with ContextPathExtendedCookie + * }}} */ trait ContextPathExtendedCookie extends ProtoSessionCookiePath { override def sessionCookiePath = S.contextPath From a02ad7e4013624ac1346e911bb9bc77852a4e9ad Mon Sep 17 00:00:00 2001 From: Andreas Joseph Krogh Date: Fri, 15 Sep 2017 12:30:13 +0200 Subject: [PATCH 1688/1949] Log exceptions too when logging errors --- core/util/src/main/scala/net/liftweb/util/Schedule.scala | 4 ++-- .../main/scala/net/liftweb/builtin/snippet/Embed.scala | 9 +++++++-- .../src/main/scala/net/liftweb/http/LiftSession.scala | 6 +++++- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/core/util/src/main/scala/net/liftweb/util/Schedule.scala b/core/util/src/main/scala/net/liftweb/util/Schedule.scala index 0c7a80e0a0..ef91d9668b 100644 --- a/core/util/src/main/scala/net/liftweb/util/Schedule.scala +++ b/core/util/src/main/scala/net/liftweb/util/Schedule.scala @@ -151,7 +151,7 @@ sealed trait Schedule extends Loggable { try { f.apply() } catch { - case e: Exception => logger.error(e) + case e: Exception => logger.error(e.getMessage, e) } } } @@ -162,7 +162,7 @@ sealed trait Schedule extends Loggable { Schedule.this.restart pool.execute(r) } catch { - case e: Exception => logger.error(e) + case e: Exception => logger.error(e.getMessage, e) } } } diff --git a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Embed.scala b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Embed.scala index 9b8ae917fa..c4db9e2471 100644 --- a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Embed.scala +++ b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Embed.scala @@ -79,8 +79,13 @@ object Embed extends DispatchSnippet { bindFn(template) } - case Failure(msg, _, _) => - logger.error("'embed' snippet failed with message: "+msg) + case Failure(msg, ex, _) => + ex match { + case Full(e) => + logger.error("'embed' snippet failed with message: "+msg, e) + case _ => + logger.error("'embed' snippet failed with message: "+msg) + } throw new SnippetExecutionException("Embed Snippet failed: "+msg) case _ => diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index a32355e845..2f9ce5d9a0 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -2106,7 +2106,11 @@ class LiftSession(private[http] val _contextPath: String, val underlyingId: Stri in.!(xlate(p) match { case Full(v) => v case Empty => logger.error("Failed to deserialize JSON message "+p); p - case Failure(msg, _, _) => logger.error("Failed to deserialize JSON message "+p+". Error "+msg); p + case Failure(msg, ex, _) => ex match { + case Full(e) => logger.error("Failed to deserialize JSON message "+p+". Error "+msg, e) + case _ => logger.error("Failed to deserialize JSON message "+p+". Error "+msg) + } + p }) JsCmds.Noop }).cmd) From 7bea36b38b05d7a07e12925157ceb3a8f35c750c Mon Sep 17 00:00:00 2001 From: Andreas Joseph Krogh Date: Fri, 15 Sep 2017 15:42:48 +0200 Subject: [PATCH 1689/1949] Unify case style as per @farmdawgnation suggestion --- .../scala/net/liftweb/builtin/snippet/Embed.scala | 13 ++++++------- .../main/scala/net/liftweb/http/LiftSession.scala | 7 ++----- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Embed.scala b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Embed.scala index c4db9e2471..11204cdce4 100644 --- a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Embed.scala +++ b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Embed.scala @@ -79,13 +79,12 @@ object Embed extends DispatchSnippet { bindFn(template) } - case Failure(msg, ex, _) => - ex match { - case Full(e) => - logger.error("'embed' snippet failed with message: "+msg, e) - case _ => - logger.error("'embed' snippet failed with message: "+msg) - } + case Failure(msg, Full(ex), _) => + logger.error("'embed' snippet failed with message: "+msg, ex) + throw new SnippetExecutionException("Embed Snippet failed: "+msg) + + case Failure(msg, _, _) => + logger.error("'embed' snippet failed with message: "+msg) throw new SnippetExecutionException("Embed Snippet failed: "+msg) case _ => diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index 2f9ce5d9a0..3b06c918c4 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -2106,11 +2106,8 @@ class LiftSession(private[http] val _contextPath: String, val underlyingId: Stri in.!(xlate(p) match { case Full(v) => v case Empty => logger.error("Failed to deserialize JSON message "+p); p - case Failure(msg, ex, _) => ex match { - case Full(e) => logger.error("Failed to deserialize JSON message "+p+". Error "+msg, e) - case _ => logger.error("Failed to deserialize JSON message "+p+". Error "+msg) - } - p + case Failure(msg, Full(ex), _) => logger.error("Failed to deserialize JSON message "+p+". Error "+msg, ex); p + case Failure(msg, _, _) => logger.error("Failed to deserialize JSON message "+p+". Error "+msg); p }) JsCmds.Noop }).cmd) From f36a8019980caed17f78666f1e2273bdea4b234f Mon Sep 17 00:00:00 2001 From: Joe Barnes Date: Fri, 22 Sep 2017 11:45:22 -0500 Subject: [PATCH 1690/1949] First pass at making LiftSession serializable via kryo --- .../scala/net/liftweb/http/LiftSession.scala | 83 ++++++++++--------- 1 file changed, 46 insertions(+), 37 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index 3b06c918c4..2615a52c7a 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -199,6 +199,41 @@ object LiftSession { } } + private[http] class DataAttrNode(liftSession: LiftSession) { + val dataAttributeProcessors = LiftRules.dataAttributeProcessor.toList + + def unapply(in: Node): Option[DataAttributeProcessorAnswer] = { + in match { + case element: Elem if dataAttributeProcessors.nonEmpty => + element.attributes.toStream.flatMap { + case UnprefixedAttribute(key, value, _) if key.toLowerCase().startsWith("data-") => + val dataProcessorName = key.substring(5).toLowerCase() + val dataProcessorInputValue = value.text + val filteredElement = removeAttribute(key, element) + + NamedPF.applyBox( + (dataProcessorName, dataProcessorInputValue, filteredElement, liftSession), + dataAttributeProcessors + ) + case _ => Empty + }.headOption + + case _ => None + } + } + } + + private[http] class TagProcessingNode(liftSession: LiftSession) { + val rules = LiftRules.tagProcessor.toList + + def unapply(in: Node): Option[DataAttributeProcessorAnswer] = { + in match { + case e: Elem if !rules.isEmpty => + NamedPF.applyBox((e.label, e, liftSession), rules) + case _ => None + } + } + } } object PageName extends RequestVar[String]("") @@ -336,6 +371,12 @@ private[http] class BooleanThreadGlobal extends ThreadGlobal[Boolean] { */ class LiftSession(private[http] val _contextPath: String, val underlyingId: String, val httpSession: Box[HTTPSession]) extends LiftMerge with Loggable with HowStateful { + + /** + * Private no-arg constructor needed for deserialization. + */ + private[this] def this() = this("", "", Empty) + def sessionHtmlProperties = LiftRules.htmlProperties.session.is.make openOr LiftRules.htmlProperties.default.is.vend val requestHtmlProperties: TransientRequestVar[HtmlProperties] = @@ -371,9 +412,9 @@ class LiftSession(private[http] val _contextPath: String, val underlyingId: Stri private case class CometId(cometType: String, cometName: Box[String]) - private val nasyncComponents = new ConcurrentHashMap[CometId, LiftCometActor] + @transient private val nasyncComponents = new ConcurrentHashMap[CometId, LiftCometActor] - private val nasyncById = new ConcurrentHashMap[String, LiftCometActor] + @transient private val nasyncById = new ConcurrentHashMap[String, LiftCometActor] private val asyncSync = new Object @@ -455,7 +496,7 @@ class LiftSession(private[http] val _contextPath: String, val underlyingId: Stri def running_? = _running_? - private var cometList: Vector[(LiftActor, Req)] = Vector.empty + @transient private var cometList: Vector[(LiftActor, Req)] = Vector.empty private[http] def breakOutComet(): Unit = { val cl = asyncSync.synchronized { @@ -2011,41 +2052,9 @@ class LiftSession(private[http] val _contextPath: String, val underlyingId: Stri private object _lastFoundSnippet extends ThreadGlobal[String] - private object DataAttrNode { - val dataAttributeProcessors = LiftRules.dataAttributeProcessor.toList - - def unapply(in: Node): Option[DataAttributeProcessorAnswer] = { - in match { - case element: Elem if dataAttributeProcessors.nonEmpty => - element.attributes.toStream.flatMap { - case UnprefixedAttribute(key, value, _) if key.toLowerCase().startsWith("data-") => - val dataProcessorName = key.substring(5).toLowerCase() - val dataProcessorInputValue = value.text - val filteredElement = removeAttribute(key, element) - - NamedPF.applyBox( - (dataProcessorName, dataProcessorInputValue, filteredElement, LiftSession.this), - dataAttributeProcessors - ) - case _ => Empty - }.headOption - - case _ => None - } - } - } - - private object TagProcessingNode { - val rules = LiftRules.tagProcessor.toList + @transient private val DataAttrNode = new LiftSession.DataAttrNode(this) - def unapply(in: Node): Option[DataAttributeProcessorAnswer] = { - in match { - case e: Elem if !rules.isEmpty => - NamedPF.applyBox((e.label, e, LiftSession.this), rules) - case _ => None - } - } - } + @transient private val TagProcessingNode = new LiftSession.TagProcessingNode(this) /** * Pass in a LiftActor and get a JavaScript expression (function(x) {...}) that From bd116a50b3aafdc5ce0d158e3d11237634848c1b Mon Sep 17 00:00:00 2001 From: Joe Barnes Date: Fri, 22 Sep 2017 12:52:06 -0500 Subject: [PATCH 1691/1949] Marking the snapshot function in PageStateHolder as transient until I can untangle it later --- web/webkit/src/main/scala/net/liftweb/http/S.scala | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/S.scala b/web/webkit/src/main/scala/net/liftweb/http/S.scala index 3bb4617da6..0fcc54b4bf 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/S.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/S.scala @@ -215,8 +215,14 @@ object S extends S { * We create one of these dudes and put it */ private[http] final case class PageStateHolder(owner: Box[String], session: LiftSession) extends AFuncHolder { + + /** + * Private no-arg constructor needed for deserialization. + */ + private[this] def this() = this(Empty, null) + private val loc = S.location - private val snapshot: Function1[Function0[Any], Any] = RequestVarHandler.generateSnapshotRestorer() + @transient private val snapshot: Function1[Function0[Any], Any] = RequestVarHandler.generateSnapshotRestorer() override def sessionLife: Boolean = false def apply(in: List[String]): Any = { From 23755f0016b868e7e43862c7948590916d9546b8 Mon Sep 17 00:00:00 2001 From: Joe Barnes Date: Mon, 25 Sep 2017 19:50:11 -0500 Subject: [PATCH 1692/1949] Now able to remove transient tag from the snapshot function, allowing RequestVars to survive a session migration --- web/webkit/src/main/scala/net/liftweb/http/S.scala | 2 +- .../net/liftweb/http/provider/servlet/HTTPRequestServlet.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/S.scala b/web/webkit/src/main/scala/net/liftweb/http/S.scala index 0fcc54b4bf..431baf314c 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/S.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/S.scala @@ -222,7 +222,7 @@ object S extends S { private[this] def this() = this(Empty, null) private val loc = S.location - @transient private val snapshot: Function1[Function0[Any], Any] = RequestVarHandler.generateSnapshotRestorer() + private val snapshot: Function1[Function0[Any], Any] = RequestVarHandler.generateSnapshotRestorer() override def sessionLife: Boolean = false def apply(in: List[String]): Any = { diff --git a/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPRequestServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPRequestServlet.scala index 966970e593..18b3de58e4 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPRequestServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/provider/servlet/HTTPRequestServlet.scala @@ -28,7 +28,7 @@ import net.liftweb.common._ import net.liftweb.util._ import Helpers._ -class HTTPRequestServlet(val req: HttpServletRequest, val provider: HTTPProvider) extends HTTPRequest { +class HTTPRequestServlet(@transient val req: HttpServletRequest, @transient val provider: HTTPProvider) extends HTTPRequest { private lazy val ctx = { new HTTPServletContext(req.getSession.getServletContext) } From 4c1e2ad004282b12f38db6dae1b954e508f4506a Mon Sep 17 00:00:00 2001 From: Joe Barnes Date: Tue, 3 Oct 2017 11:02:25 -0500 Subject: [PATCH 1693/1949] Changing comet rehydration to use ajaxGet instead of ajaxPost --- web/webkit/src/main/resources/toserve/lift.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/web/webkit/src/main/resources/toserve/lift.js b/web/webkit/src/main/resources/toserve/lift.js index b45aa3cd16..a076166fc5 100644 --- a/web/webkit/src/main/resources/toserve/lift.js +++ b/web/webkit/src/main/resources/toserve/lift.js @@ -428,7 +428,7 @@ settings.logError(err); } - settings.ajaxPost(location.toString(), {}, "html", onSuccess, onFailure); + settings.ajaxGet(location.toString(), {}, onSuccess, onFailure, "html"); } @@ -715,12 +715,16 @@ contentType: contentType }); }, - ajaxGet: function(url, data, onSuccess, onFailure) { + ajaxGet: function(url, data, onSuccess, onFailure, dataType) { + if (!dataType) { + dataType = "script"; + } + return jQuery.ajax({ url: url, data: data, type: "GET", - dataType: "script", + dataType: dataType, timeout: this.cometGetTimeout, cache: false, success: onSuccess, From 40ee23c146a41ce497cf9f28a5447c6f824079b5 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 28 Oct 2017 17:21:08 -0400 Subject: [PATCH 1694/1949] Remove a confusing scaladoc note --- .../main/scala/net/liftweb/mapper/ProtoExtendedSession.scala | 3 --- 1 file changed, 3 deletions(-) diff --git a/persistence/mapper/src/main/scala/net/liftweb/mapper/ProtoExtendedSession.scala b/persistence/mapper/src/main/scala/net/liftweb/mapper/ProtoExtendedSession.scala index e1d200a9f9..75b4d74b9a 100644 --- a/persistence/mapper/src/main/scala/net/liftweb/mapper/ProtoExtendedSession.scala +++ b/persistence/mapper/src/main/scala/net/liftweb/mapper/ProtoExtendedSession.scala @@ -62,9 +62,6 @@ trait UserIdAsString { /** * The root trait for defining the session cookie path for extended sessions * that defines the default session cookie path: "/". - * - * Extend this trait then mix it into an extended session singleton to change - * the cookie path. See also: [[ContextPathExtendedCookie]] */ trait ProtoSessionCookiePath { def sessionCookiePath: String = "/" From 64b206f316cf69aee73737654afe02d3b6669f88 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 28 Oct 2017 17:37:14 -0400 Subject: [PATCH 1695/1949] Add a unit test covering errors in no-arg comet constructors --- .../net/liftweb/http/LiftSessionSpec.scala | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/web/webkit/src/test/scala/net/liftweb/http/LiftSessionSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/LiftSessionSpec.scala index 50673d5c08..f8184bf1a4 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/LiftSessionSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/LiftSessionSpec.scala @@ -44,6 +44,15 @@ object LiftSessionSpec extends Specification with BeforeEach { } } + private class ExplodesInConstructorCometActor extends CometActor { + def render = NodeSeq.Empty + + throw new RuntimeException("boom, this explodes in the constructor!") + override def lowPriority = { + case _ => + } + } + "A LiftSession" should { "Send accumulated messages to a newly-created comet actor in the order in which they arrived" in { @@ -91,6 +100,22 @@ object LiftSessionSpec extends Specification with BeforeEach { receivedMessages mustEqual Vector(1, 1) } } + + "Surface exceptions from the no-arg comet constructor" in { + val session = new LiftSession("Test Session", "", Empty) + + S.init(Empty, session) { + val result = session.findOrCreateComet[ExplodesInConstructorCometActor](Empty, NodeSeq.Empty, Map.empty) + + result match { + case Failure(_, Full(ex: java.lang.reflect.InvocationTargetException), _) => + success + + case other => + failure("Comet did not fail with an InvocationTargetException. Please check to ensure error handling in no-arg comet constructors wasn't broken.") + } + } + } } "LiftSession when building deferred functions" should { From dc85e6d2520bda401f4be234042b98028f8be86a Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 28 Oct 2017 17:40:25 -0400 Subject: [PATCH 1696/1949] Check for NoSuchMethodException before trying secondary constructor The previous implementation of this code would mask exceptions that happened in the no-arg constructor of a comet. In this implementation, we actually check for evidence that the no-arg constructor doesn't exist before resorting to the secondary constructor. --- .../main/scala/net/liftweb/http/LiftSession.scala | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index 3b06c918c4..a460903311 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -2615,7 +2615,17 @@ class LiftSession(private[http] val _contextPath: String, val underlyingId: Stri constructor.newInstance(this, name, defaultXml, attributes).asInstanceOf[T] } - val attemptedComet = tryo(buildWithNoArgConstructor) or tryo(buildWithCreateInfoConstructor) + // We first attempt to use the no argument constructor. If we get a NoSuchMethodException, + // we _then_ try to use the create info constructor. If anything else happens, including + // others kinds of exceptions, we abort construction attempts intentionally so we surface + // the correct error. + val attemptedComet = tryo(buildWithNoArgConstructor) match { + case fail @ Failure(_, Full(e: java.lang.NoSuchMethodException), _) => + fail or tryo(buildWithCreateInfoConstructor) + + case other => + other + } attemptedComet match { case fail @ Failure(_, Full(e: java.lang.NoSuchMethodException), _) => From e390ccc28f4369517fb6f440f3c0bea521d670a1 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 4 Nov 2017 10:30:43 -0400 Subject: [PATCH 1697/1949] Make Codacy happy using private[this] --- .../src/test/scala/net/liftweb/http/LiftSessionSpec.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/webkit/src/test/scala/net/liftweb/http/LiftSessionSpec.scala b/web/webkit/src/test/scala/net/liftweb/http/LiftSessionSpec.scala index f8184bf1a4..4f4aaf2ed2 100644 --- a/web/webkit/src/test/scala/net/liftweb/http/LiftSessionSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/http/LiftSessionSpec.scala @@ -32,7 +32,7 @@ object LiftSessionSpec extends Specification with BeforeEach { override def before = receivedMessages = Vector[Int]() - private class TestCometActor extends CometActor { + private[this] class TestCometActor extends CometActor { def render = NodeSeq.Empty override def lowPriority = { @@ -44,7 +44,7 @@ object LiftSessionSpec extends Specification with BeforeEach { } } - private class ExplodesInConstructorCometActor extends CometActor { + private[this] class ExplodesInConstructorCometActor extends CometActor { def render = NodeSeq.Empty throw new RuntimeException("boom, this explodes in the constructor!") From 14cefb6ac2722952fcf59d9815fb06377a94d5f7 Mon Sep 17 00:00:00 2001 From: ricsirigu Date: Sat, 4 Nov 2017 17:32:49 +0100 Subject: [PATCH 1698/1949] Correctly ovveriding list item markup in markdown --- .../scala/net/liftweb/markdown/BlockParsers.scala | 3 +++ .../scala/net/liftweb/markdown/BlockParsersTest.scala | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/core/markdown/src/main/scala/net/liftweb/markdown/BlockParsers.scala b/core/markdown/src/main/scala/net/liftweb/markdown/BlockParsers.scala index 7f2fb19d6b..7f3c1be1d1 100644 --- a/core/markdown/src/main/scala/net/liftweb/markdown/BlockParsers.scala +++ b/core/markdown/src/main/scala/net/liftweb/markdown/BlockParsers.scala @@ -182,6 +182,9 @@ trait BlockParsers extends Parsers { * recursively builds the content of an item. */ class ListItem(val lines:List[MarkdownLine], lookup:Map[String, LinkDefinition]) extends LineParsers { + + override def deco(): Decorator = BlockParsers.this.deco + def endsWithNewline = lines.size > 1 && (lines.last.isInstanceOf[EmptyLine]) def addResult(level:Int, out:StringBuilder, paragraph_? : Boolean) { diff --git a/core/markdown/src/test/scala/net/liftweb/markdown/BlockParsersTest.scala b/core/markdown/src/test/scala/net/liftweb/markdown/BlockParsersTest.scala index 9e78b83354..1e9a70def0 100644 --- a/core/markdown/src/test/scala/net/liftweb/markdown/BlockParsersTest.scala +++ b/core/markdown/src/test/scala/net/liftweb/markdown/BlockParsersTest.scala @@ -51,4 +51,15 @@ class BlockParsersTest extends FlatSpec with Matchers with BlockParsers{ apply(p, List(new CodeLine(" ", "code"))) should equal (new CodeLine(" ", "code")) an [IllegalArgumentException] should be thrownBy(apply(p, List(new OtherLine("foo")))) } + + it should "correctly ovverride list items markup" in { + object MyDecorator extends Decorator { + override def decorateItemOpen(): String = "" + override def decorateItemClose(): String = "" + } + object MyTransformer extends Transformer { + override def deco(): Decorator = MyDecorator + } + MyTransformer.apply("* Content") should equal ("
                \nContent
              \n") + } } From f42102e0764c962b73b604341c8f7d7d84b1a001 Mon Sep 17 00:00:00 2001 From: Riccardo Sirigu Date: Sun, 5 Nov 2017 09:34:22 +0100 Subject: [PATCH 1699/1949] Typo --- .../src/test/scala/net/liftweb/markdown/BlockParsersTest.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/markdown/src/test/scala/net/liftweb/markdown/BlockParsersTest.scala b/core/markdown/src/test/scala/net/liftweb/markdown/BlockParsersTest.scala index 1e9a70def0..376b720c6a 100644 --- a/core/markdown/src/test/scala/net/liftweb/markdown/BlockParsersTest.scala +++ b/core/markdown/src/test/scala/net/liftweb/markdown/BlockParsersTest.scala @@ -52,7 +52,7 @@ class BlockParsersTest extends FlatSpec with Matchers with BlockParsers{ an [IllegalArgumentException] should be thrownBy(apply(p, List(new OtherLine("foo")))) } - it should "correctly ovverride list items markup" in { + it should "correctly override list items markup" in { object MyDecorator extends Decorator { override def decorateItemOpen(): String = "" override def decorateItemClose(): String = "" From 62cb1ea0ebe79edf214577009cd6aed445fb36ba Mon Sep 17 00:00:00 2001 From: Joe Barnes Date: Tue, 14 Nov 2017 14:18:31 -0600 Subject: [PATCH 1700/1949] Removing comment after code review with farmdawg --- web/webkit/src/main/resources/toserve/lift.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/web/webkit/src/main/resources/toserve/lift.js b/web/webkit/src/main/resources/toserve/lift.js index a076166fc5..5f7e5c5cb5 100644 --- a/web/webkit/src/main/resources/toserve/lift.js +++ b/web/webkit/src/main/resources/toserve/lift.js @@ -419,9 +419,6 @@ } restartComet(); - - // Under the impression that a parentless node needn't be deleted. - // It should be a candidate for GC after it goes out of scope } function onFailure(err) { // Try again?? From 1cc64e153c1efde3f859884035bc0ac8f94875b0 Mon Sep 17 00:00:00 2001 From: Joe Barnes Date: Tue, 14 Nov 2017 14:23:16 -0600 Subject: [PATCH 1701/1949] Breaking up long lines of code after code review with farmdawg --- .../main/scala/net/liftweb/builtin/snippet/Comet.scala | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Comet.scala b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Comet.scala index 03771d708f..ba21e120d4 100644 --- a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Comet.scala +++ b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/Comet.scala @@ -34,10 +34,16 @@ object Comet extends DispatchSnippet with LazyLoggable { // `containerId`. private [this] def buildContainer(cometHtml: NodeSeq, cometActor: LiftCometActor, containerId: String): NodeSeq = { val cometVersion = http.S.requestCometVersions.is - .collectFirst { case CometVersionPair(guid, ver) if guid == cometActor.uniqueId => ver } + .collectFirst { + case CometVersionPair(guid, ver) if guid == cometActor.uniqueId => + ver + } .getOrElse("") - cometActor.parentTag.copy(child = cometHtml) % ("id" -> containerId) % ("data-lift-comet-version" -> cometVersion) + cometActor.parentTag + .copy(child = cometHtml) % + ("id" -> containerId) % + ("data-lift-comet-version" -> cometVersion) } /** From 641667361486834f489e8ebdd916ba23cf580d96 Mon Sep 17 00:00:00 2001 From: Joe Barnes Date: Thu, 30 Nov 2017 15:45:30 -0500 Subject: [PATCH 1702/1949] Defined LiftRules.enableCometRehydration --- .../src/main/scala/net/liftweb/http/LiftRules.scala | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 4bc2d03b28..47814ee1cc 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -836,6 +836,16 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { */ @volatile var cometRenderTimeout: Long = 30.seconds + /** + * Adjusts LiftRules to allow a page's comets to "rehydrate" (i.e. reconnect to the page) after a server restart. + * This works by reloading the page into an iframe and stealing all of the comets. When the refresh occurs, + * new comet actors will be instantiated, so beware that any state therein will NOT be reinstated. + */ + def enableCometRehydration(): Unit = { + LiftRules.redirectAsyncOnSessionLoss = false + LiftRules.noCometSessionCmd.default.set(() => JsCmds.Run("lift.rehydrateComets()")) + } + /** * The dispatcher that takes a Snippet and converts it to a * DispatchSnippet instance From 82b4bcaba13ff1b4f4ae48bbd5f5365f5dee964c Mon Sep 17 00:00:00 2001 From: Tim Nelson Date: Wed, 6 Dec 2017 14:59:29 -0600 Subject: [PATCH 1703/1949] Added maxIdLength to StringPk --- .../scala/net/liftweb/mongodb/record/field/MongoPk.scala | 5 +++-- .../scala/net/liftweb/mongodb/record/MongoFieldSpec.scala | 8 ++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoPk.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoPk.scala index 81a1afb9d6..03a20d7845 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoPk.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoPk.scala @@ -72,9 +72,10 @@ trait StringPk[OwnerType <: MongoRecord[OwnerType]] { self: OwnerType => - def defaultIdValue = StringHelpers.randomString(32) + def defaultIdValue = StringHelpers.randomString(maxIdLength) + def maxIdLength: Int = 32 - object id extends StringField(this.asInstanceOf[OwnerType], 12) { + object id extends StringField(this.asInstanceOf[OwnerType], maxIdLength) { override def name = "_id" override def defaultValue = defaultIdValue override def shouldDisplay_? = false diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala index 3b885cd757..5329e6443d 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala @@ -586,6 +586,14 @@ object MongoFieldSpec extends Specification with MongoTestKit with AroundEach { Empty ) } + "set proper maxlength on form element" in { + val rec = MapTestRecord.createRecord + val session = new LiftSession("", randomString(20), Empty) + S.initIfUninitted(session) { + val maxLenAttribute: Box[String] = rec.id.toForm.map(f => (f \ "@maxlength").text) + maxLenAttribute must_== Full(rec.maxIdLength.toString) + } + } } "MongoMapField (Int)" should { From 06260c6450cfcccf611577418560198e2de0fe86 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 9 Dec 2017 23:01:58 -0500 Subject: [PATCH 1704/1949] Update with latest version numbers for 3.0 and 3.1 --- SUPPORT.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SUPPORT.md b/SUPPORT.md index fe7a475446..af1e5ba571 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -27,15 +27,15 @@ of Lift: * Major releases aren't on a regular schedule, but we will announce and update this policy with actual dates when major releases become planned. -As of July 23rd, 2017 the current support status looks like this: +As of December 9th, 2017 the current support status looks like this: |Version |Initial Release | Last Release | Current Support Status | |---------|-----------------|------------------|---------------------------------| |< 2.5 | | | Not supported | |2.5 |June 2013 | 2.5.4 (Jan 2016) | Security fixes (until Lift 4.0) | |2.6 |January 2015 | 2.6.3 (Jan 2016) | Security fixes (until Lift 5.0) | -|3.0 |November 2016 | 3.0.1 (Dec 2016) | Minor fixes (until May 2018) | -|3.1 |July 2017 | 3.1.0 (Jul 2017) | Minor fixes (until Jan 2019) | +|3.0 |November 2016 | 3.0.2 (Sep 2016) | Minor fixes (until May 2018) | +|3.1 |July 2017 | 3.1.1 (Sep 2017) | Minor fixes (until Jan 2019) | Per this support policy please ensure you're using a currently supported version of Lift before: From 76ee394d731d22a889a8ed4b487f3371f82727de Mon Sep 17 00:00:00 2001 From: Joe Barnes Date: Mon, 11 Dec 2017 12:46:01 -0600 Subject: [PATCH 1705/1949] Updating enableCometRehydration's scaladoc per code review --- web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 47814ee1cc..2df65e5f68 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -839,7 +839,9 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { /** * Adjusts LiftRules to allow a page's comets to "rehydrate" (i.e. reconnect to the page) after a server restart. * This works by reloading the page into an iframe and stealing all of the comets. When the refresh occurs, - * new comet actors will be instantiated, so beware that any state therein will NOT be reinstated. + * new comet actors will be instantiated, so beware that any state therein will NOT be reinstated. Applications + * utilizing this feature should be designed to reconstruct comet state from the DB, cookies, client info, etc + * as required. */ def enableCometRehydration(): Unit = { LiftRules.redirectAsyncOnSessionLoss = false From 21751351065ed7d52152b9359a208a0ce1f3bb92 Mon Sep 17 00:00:00 2001 From: Joe Barnes Date: Mon, 11 Dec 2017 17:32:15 -0600 Subject: [PATCH 1706/1949] Adding a null guard to prevent alarming yet harmless NullPointerException messages in the appserver log. Resolves #1929 --- web/webkit/src/main/scala/net/liftweb/http/SessionMaster.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/SessionMaster.scala b/web/webkit/src/main/scala/net/liftweb/http/SessionMaster.scala index 8af0f4855e..d48e62a741 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/SessionMaster.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/SessionMaster.scala @@ -199,7 +199,7 @@ object SessionMaster extends LiftActor with Loggable { def isDead(sessionId: String): Boolean = killedSessions.containsKey(sessionId) private val reaction: PartialFunction[Any, Unit] = { - case RemoveSession(sessionId) => + case RemoveSession(sessionId) if sessionId != null => val ses = lockRead(nsessions) (Box !! ses.get(sessionId)).foreach { From 9362cd9f4e022e6b6393f97bbf200d36dbb20998 Mon Sep 17 00:00:00 2001 From: Jessica Ete Date: Fri, 29 Dec 2017 22:52:44 +0000 Subject: [PATCH 1707/1949] Add server instructions to README Have added instructions on how to run the server in the latest version of Lift to the README. --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index 9eabc90248..11a97b0f17 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,14 @@ sbt new lift/blank-app.g8 Follow the prompts to create your Lift application. +### Running the Server + +In order to run the server, navigate to the application folder and run the `sbt` command. In the SBT prompt, run: + + ~jetty:start + +By default, the server should run on http://localhost:8080. + ### With sbt (Existing project) If you're using Lift in an existing sbt project you'll need to: @@ -75,6 +83,14 @@ libraryDependencies ++= { [wpsbt]: https://github.com/lift/basic-app.g8/blob/master/src/main/g8/project/web-plugin.sbt [wpproj]: https://github.com/earldouglas/xsbt-web-plugin/ +### Running the Server + +In order to run the server, navigate to the application folder and run the `sbt` command. In the SBT prompt, run: + + ~jetty:start + +By default, the server should run on http://localhost:8080. + ### With Maven Add Lift to your `pom.xml` like so: From 643dd74aa1804350bfb619b6011643559dbf3fc6 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Wed, 3 Jan 2018 10:00:08 -0500 Subject: [PATCH 1708/1949] Minor change to the header levels for readability --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 11a97b0f17..407f911385 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ sbt new lift/blank-app.g8 Follow the prompts to create your Lift application. -### Running the Server +#### Running the Server In order to run the server, navigate to the application folder and run the `sbt` command. In the SBT prompt, run: @@ -83,7 +83,7 @@ libraryDependencies ++= { [wpsbt]: https://github.com/lift/basic-app.g8/blob/master/src/main/g8/project/web-plugin.sbt [wpproj]: https://github.com/earldouglas/xsbt-web-plugin/ -### Running the Server +#### Running the Server In order to run the server, navigate to the application folder and run the `sbt` command. In the SBT prompt, run: From f28f01f22c8be4c87246fbd09baface777ee8fab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20H=C3=A4rk=C3=B6nen?= Date: Tue, 16 Jan 2018 20:53:11 +0200 Subject: [PATCH 1709/1949] Issue#1919: Asset root path should be configurable Added 'assetPath' variable to LiftRules, with default value '/assets'. Rest of the path structure is still convention based, not configurable. This will address the issue where the Lift template projects are storing the lazy loading gif under assets folder and the framework library expects it to reside at the root level. On the other hand, also if the default place for the assets is in the way of the application for a reason or another, it is now easier to move somewhere else. Added also my name to the contributors.md file, as instructed in the documentation. --- contributors.md | 5 +++++ .../main/scala/net/liftweb/builtin/snippet/LazyLoad.scala | 2 +- web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala | 7 +++++++ web/webkit/src/main/scala/net/liftweb/http/package.scala | 2 +- 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/contributors.md b/contributors.md index b6aba536c6..2a0dab1c1b 100644 --- a/contributors.md +++ b/contributors.md @@ -276,3 +276,8 @@ Noel Kennedy ### Email: ### nkennedy at rvc dot ac dot uk +### Name: ### +Henrik Härkönen + +### Email: ### +heharkon@iki.fi diff --git a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/LazyLoad.scala b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/LazyLoad.scala index 1fec967b6e..2fd804fa0c 100644 --- a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/LazyLoad.scala +++ b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/LazyLoad.scala @@ -72,7 +72,7 @@ object LazyLoad extends DispatchSnippet { renderedTemplate } } openOr { -
              Loading
              +
              Loading
              } ) } diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 8125477c34..6a582481a4 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -1334,6 +1334,13 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { } } + /** + * The root path for assets folder. Assets under this configurable path + * follow conventions, for example the default lazy loading spinner + * ('ajax-loader.gif') is under 'images' folder. + */ + @volatile var assetPath: String = "/assets" + /** * Contains the URI path under which all built-in Lift-handled requests are * scoped. It does not include the context path and should not begin with a diff --git a/web/webkit/src/main/scala/net/liftweb/http/package.scala b/web/webkit/src/main/scala/net/liftweb/http/package.scala index f06478b183..d7f7806c3e 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/package.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/package.scala @@ -55,7 +55,7 @@ package object http { // Actually complete the render once the future is fulfilled. asyncResolveProvider.resolveAsync(concreteResolvable, resolvedResult => deferredRender(resolvedResult)) -
              Loading...
              +
              Loading
              } openOr { Comment("FIX"+"ME: Asynchronous rendering failed for unknown reason.") } From 498a944fab09d0a987de55fb6cb76127d5bbd32c Mon Sep 17 00:00:00 2001 From: Christopher Webster Date: Fri, 19 Jan 2018 06:36:17 -0800 Subject: [PATCH 1710/1949] Avoid the use of ListBuffer.append when a single element is being added, instead use += to avoid the variable argument overhead --- core/json/src/main/scala/net/liftweb/json/JsonParser.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala index 975233cef9..8a13f44fb5 100644 --- a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala +++ b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala @@ -214,8 +214,8 @@ object JsonParser { case JField(name, value) => vals.pop(classOf[JField]) val obj = vals.peek(classOf[IntermediateJObject]) - obj.fields.append(JField(name,v)) - case a: IntermediateJArray => a.bits.append(v) + obj.fields += (JField(name,v)) + case a: IntermediateJArray => a.bits += v case other => p.fail("expected field or array but got " + other) } else { vals.push(v) From ff578720f528e3495b0ebbff88e6cb666652c4bc Mon Sep 17 00:00:00 2001 From: Christopher Webster Date: Thu, 18 Jan 2018 13:29:51 -0800 Subject: [PATCH 1711/1949] Avoid extra array creation when creating a list of a single item --- core/json/src/main/scala/net/liftweb/json/JsonAST.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/json/src/main/scala/net/liftweb/json/JsonAST.scala b/core/json/src/main/scala/net/liftweb/json/JsonAST.scala index 33c03866ee..05290b4398 100644 --- a/core/json/src/main/scala/net/liftweb/json/JsonAST.scala +++ b/core/json/src/main/scala/net/liftweb/json/JsonAST.scala @@ -118,7 +118,8 @@ object JsonAST { * nothing is found, you'll get a `JNothing`. */ def \(nameToFind: String): JValue = { - findDirectByName(List(this), nameToFind) match { + // Use :: instead of List() to avoid the extra array allocation for the variable arguments + findDirectByName(this :: Nil, nameToFind) match { case Nil => JNothing case x :: Nil => x case x => JArray(x) From 1d24c9257bde82d73f66651fce5d319aa514ab1d Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 27 Jan 2018 14:28:33 -0500 Subject: [PATCH 1712/1949] Bump version to 3.3.0-SNAPSHOT for forward development --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index fd2f46ad1c..a1323df8d4 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ import Dependencies._ import LiftSbtHelpers._ organization in ThisBuild := "net.liftweb" -version in ThisBuild := "3.2.0-SNAPSHOT" +version in ThisBuild := "3.3.0-SNAPSHOT" homepage in ThisBuild := Some(url("http://www.liftweb.net")) licenses in ThisBuild += ("Apache License, Version 2.0", url("http://www.apache.org/licenses/LICENSE-2.0.txt")) startYear in ThisBuild := Some(2006) From b547d0d255f2ff5e66f1c1e021512ad8714b6035 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 27 Jan 2018 14:28:43 -0500 Subject: [PATCH 1713/1949] Bump sbt version to 0.3.16 --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index fccc3dd7c4..53b2a25b58 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1,2 +1,2 @@ # Deprecate using build.properties, use -Dsbt.version=... in launcher arg instead -sbt.version=0.13.8 +sbt.version=0.13.16 From 89f0988ab4ef98382df41960399d29bc9bbe8da9 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 27 Jan 2018 14:29:15 -0500 Subject: [PATCH 1714/1949] Bump Scala 2.12 version to 2.12.4 --- .travis.yml | 2 +- build.sbt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4653524786..f914c536e7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ sudo: false scala: - 2.11.11 - - 2.12.3 + - 2.12.4 cache: directories: diff --git a/build.sbt b/build.sbt index a1323df8d4..94254bd0f7 100644 --- a/build.sbt +++ b/build.sbt @@ -7,8 +7,8 @@ homepage in ThisBuild := Some(url("http://www.liftweb.net")) licenses in ThisBuild += ("Apache License, Version 2.0", url("http://www.apache.org/licenses/LICENSE-2.0.txt")) startYear in ThisBuild := Some(2006) organizationName in ThisBuild := "WorldWide Conferencing, LLC" -scalaVersion in ThisBuild := "2.12.2" -crossScalaVersions in ThisBuild := Seq("2.12.2", "2.11.11") +scalaVersion in ThisBuild := "2.12.4" +crossScalaVersions in ThisBuild := Seq("2.12.4", "2.11.11") libraryDependencies in ThisBuild ++= Seq(specs2, specs2Matchers, specs2Mock, scalacheck, scalatest) From 8fc7f828ade930f4a4351b1747892bdc861da1cf Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 27 Jan 2018 14:30:44 -0500 Subject: [PATCH 1715/1949] Update README for 3.2.0 release --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 407f911385..4101061718 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ Logback if you don't already have another SLF4J logging library in place. For ex ```scala libraryDependencies ++= { - val liftVersion = "3.1.0" + val liftVersion = "3.2.0" Seq( "net.liftweb" %% "lift-webkit" % liftVersion % "compile", "ch.qos.logback" % "logback-classic" % "1.2.5" @@ -98,7 +98,7 @@ Add Lift to your `pom.xml` like so: net.liftweb lift-webkit_${scala.version} - 3.1.0 + 3.2.0 Where `${scala.version}` is `2.11` or `2.12`. Individual patch releases of the Scala compiler From 682d86fb4f815de9c3db39e159bedb37d8cebe66 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 27 Jan 2018 14:38:19 -0500 Subject: [PATCH 1716/1949] Update version support table to include Lift 3.2.0 --- SUPPORT.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SUPPORT.md b/SUPPORT.md index af1e5ba571..9f6f1d6281 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -27,7 +27,7 @@ of Lift: * Major releases aren't on a regular schedule, but we will announce and update this policy with actual dates when major releases become planned. -As of December 9th, 2017 the current support status looks like this: +As of January 27th, 2018 the current support status looks like this: |Version |Initial Release | Last Release | Current Support Status | |---------|-----------------|------------------|---------------------------------| @@ -36,6 +36,7 @@ As of December 9th, 2017 the current support status looks like this: |2.6 |January 2015 | 2.6.3 (Jan 2016) | Security fixes (until Lift 5.0) | |3.0 |November 2016 | 3.0.2 (Sep 2016) | Minor fixes (until May 2018) | |3.1 |July 2017 | 3.1.1 (Sep 2017) | Minor fixes (until Jan 2019) | +|3.2 |January 2018 | 3.2.0 (Jan 2018) | Minor fixes (until July 2019) | Per this support policy please ensure you're using a currently supported version of Lift before: From a3ab6c58859065f57e379c31093b22ea1b2c51d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20H=C3=A4rk=C3=B6nen?= Date: Sun, 28 Jan 2018 21:47:54 +0200 Subject: [PATCH 1717/1949] Updated the default asset path to be "/" to avoid breaking old apps. Also the path must now end with "/" to denote a directory, rather that leave it open as it was before. --- .../scala/net/liftweb/builtin/snippet/LazyLoad.scala | 2 +- .../src/main/scala/net/liftweb/http/LiftRules.scala | 10 ++++++---- .../src/main/scala/net/liftweb/http/package.scala | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/LazyLoad.scala b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/LazyLoad.scala index 2fd804fa0c..d46968a940 100644 --- a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/LazyLoad.scala +++ b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/LazyLoad.scala @@ -72,7 +72,7 @@ object LazyLoad extends DispatchSnippet { renderedTemplate } } openOr { -
              Loading
              +
              Loading
              } ) } diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 6a582481a4..73796fcf9a 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -1335,11 +1335,13 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { } /** - * The root path for assets folder. Assets under this configurable path - * follow conventions, for example the default lazy loading spinner - * ('ajax-loader.gif') is under 'images' folder. + * The root path for assets folder. For example to store + * asset files under 'myassets' folder, use value '/myassets/'. Assets + * under this configurable path follow conventions, for example + * the default lazy loading spinner ('ajax-loader.gif') is under + * 'images' folder. */ - @volatile var assetPath: String = "/assets" + @volatile var assetPath: String = "/" /** * Contains the URI path under which all built-in Lift-handled requests are diff --git a/web/webkit/src/main/scala/net/liftweb/http/package.scala b/web/webkit/src/main/scala/net/liftweb/http/package.scala index d7f7806c3e..d75182e405 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/package.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/package.scala @@ -55,7 +55,7 @@ package object http { // Actually complete the render once the future is fulfilled. asyncResolveProvider.resolveAsync(concreteResolvable, resolvedResult => deferredRender(resolvedResult)) -
              Loading
              +
              Loading
              } openOr { Comment("FIX"+"ME: Asynchronous rendering failed for unknown reason.") } From 6dd339764cf001a82ea1f693ef970065189d62cb Mon Sep 17 00:00:00 2001 From: Andreas Joseph Krogh Date: Sun, 4 Feb 2018 17:20:44 +0100 Subject: [PATCH 1718/1949] Fixed compilation in IntelliJ IDEA, complaining about ambiguous imports --- .../net/liftweb/mongodb/record/MongoFieldSpec.scala | 2 +- .../test/scala/net/liftweb/record/RecordSpec.scala | 13 +++++-------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala index 5329e6443d..fad70252ca 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoFieldSpec.scala @@ -27,7 +27,6 @@ import org.specs2.specification._ import org.specs2.execute.AsResult import org.joda.time.DateTime import common._ -import json._ import mongodb.BsonDSL._ import util.Helpers.randomString import http.{LiftSession, S} @@ -39,6 +38,7 @@ import common.Box._ import xml.{Elem, NodeSeq, Text} import util.{FieldError, Helpers} import Helpers._ +import net.liftweb.json.JsonAST._ import org.bson.Document import org.bson.types.ObjectId diff --git a/persistence/record/src/test/scala/net/liftweb/record/RecordSpec.scala b/persistence/record/src/test/scala/net/liftweb/record/RecordSpec.scala index 7180ad4ce8..77a287ff00 100644 --- a/persistence/record/src/test/scala/net/liftweb/record/RecordSpec.scala +++ b/persistence/record/src/test/scala/net/liftweb/record/RecordSpec.scala @@ -20,19 +20,16 @@ package record import java.util.Calendar import org.specs2.mutable.Specification -import org.joda.time._ - -import http.js.JE._ +import org.joda.time.DateTime import common._ -import http.{S, LiftSession} -import json._ +import http.{LiftSession, S} import util._ import util.Helpers._ - import field.Countries import fixtures._ - -import JsonDSL._ +import net.liftweb.http.js.JE._ +import net.liftweb.json.JsonAST._ +import net.liftweb.json.JsonDSL._ /** From 893f013e6cf467d58436fb52d054a30f0e18d02f Mon Sep 17 00:00:00 2001 From: Andreas Joseph Krogh Date: Sun, 4 Feb 2018 17:43:38 +0100 Subject: [PATCH 1719/1949] Added ServiceRequestTimer, analogous to SnippetTimer --- .../scala/net/liftweb/http/LiftRules.scala | 18 ++++++++++++-- .../scala/net/liftweb/http/LiftServlet.scala | 10 +------- .../liftweb/http/ServiceRequestTimer.scala | 24 +++++++++++++++++++ 3 files changed, 41 insertions(+), 11 deletions(-) create mode 100644 web/webkit/src/main/scala/net/liftweb/http/ServiceRequestTimer.scala diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 8125477c34..8bda15f480 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -1978,8 +1978,22 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { */ @volatile var logUnreadRequestVars = true - /** Controls whether or not the service handling timing messages (Service request (GET) ... took ... Milliseconds) are logged. Defaults to true. */ - @volatile var logServiceRequestTiming = true +/** + * Handles logging of servicing a request + * two default implementations: + * - NoOpServiceTimer that does nothing + * - StandardServiceTimer that logs time it takes to serve the request (Service request (GET) ... took ... Milliseconds). + * This is the default used. + * + * Set custom in Boot: + * LiftRules.installServiceRequestTimer(MyCustomServiceTimer) + */ + val serviceRequestTimer = new LiftRulesGuardedSetting[FactoryMaker[ServiceRequestTimer]]("serviceRequestTimer", new FactoryMaker[ServiceRequestTimer](StandardServiceTimer){}) + + def installServiceRequestTimer(default: ServiceRequestTimer): Unit = { + val factoryMaker = new FactoryMaker[ServiceRequestTimer](default) {} + serviceRequestTimer.set(factoryMaker) + } /** Provides a function that returns random names for form variables, page ids, callbacks, etc. */ @volatile var funcNameGenerator: () => String = defaultFuncNameGenerator(Props.mode) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala index cd90dd1f2c..a14de3a549 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala @@ -159,15 +159,7 @@ class LiftServlet extends Loggable { ) } - if (LiftRules.logServiceRequestTiming) { - logTime { - val ret = doService(req, resp) - val msg = "Service request (" + req.request.method + ") " + req.request.uri + " returned " + resp.getStatus + "," - (msg, ret) - } - } else { - doService(req, resp) - } + LiftRules.serviceRequestTimer.get.vend.logTime(req, resp)(doService) } req.request.resumeInfo match { diff --git a/web/webkit/src/main/scala/net/liftweb/http/ServiceRequestTimer.scala b/web/webkit/src/main/scala/net/liftweb/http/ServiceRequestTimer.scala new file mode 100644 index 0000000000..3973dafe4b --- /dev/null +++ b/web/webkit/src/main/scala/net/liftweb/http/ServiceRequestTimer.scala @@ -0,0 +1,24 @@ +package net.liftweb.http + +import net.liftweb.http.provider.HTTPResponse +import net.liftweb.util.Helpers + +trait ServiceRequestTimer { + def logTime(req: Req, resp: HTTPResponse)(doService: (Req, HTTPResponse) => Boolean): Boolean +} + +object NoOpServiceTimer extends ServiceRequestTimer { + override def logTime(req: Req, resp: HTTPResponse)(doService: (Req, HTTPResponse) => Boolean): Boolean = { + doService(req, resp) + } +} + +object StandardServiceTimer extends ServiceRequestTimer { + override def logTime(req: Req, resp: HTTPResponse)(doService: (Req, HTTPResponse) => Boolean): Boolean = { + Helpers.logTime { + val ret = doService(req, resp) + val msg = "Service request (" + req.request.method + ") " + req.request.uri + " returned " + resp.getStatus + "," + (msg, ret) + } + } +} From 90c5351978e5bbdc182b19526fa4d1b1f1b6d822 Mon Sep 17 00:00:00 2001 From: Andreas Joseph Krogh Date: Wed, 7 Feb 2018 11:00:31 +0100 Subject: [PATCH 1720/1949] Re-added LiftRules.logServiceRequestTiming as we don't want any breaking changes --- .../src/main/scala/net/liftweb/http/LiftRules.scala | 9 ++++++++- .../scala/net/liftweb/http/provider/HTTPProvider.scala | 3 +++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 8bda15f480..f4cffd83fd 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -1978,7 +1978,14 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { */ @volatile var logUnreadRequestVars = true -/** + /** Controls whether or not the service handling timing messages (Service request (GET) ... took ... Milliseconds) are logged. + * If set to false NoOpServiceTimer is used. + * We should remove this setting in Lift-4 and only depend on serviceRequestTimer + * Defaults to true. + * */ + @volatile var logServiceRequestTiming = true + + /** * Handles logging of servicing a request * two default implementations: * - NoOpServiceTimer that does nothing diff --git a/web/webkit/src/main/scala/net/liftweb/http/provider/HTTPProvider.scala b/web/webkit/src/main/scala/net/liftweb/http/provider/HTTPProvider.scala index d9a7a8240c..cfb76bc7d0 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/provider/HTTPProvider.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/provider/HTTPProvider.scala @@ -132,6 +132,9 @@ trait HTTPProvider { } private def postBoot { + if (!LiftRules.logServiceRequestTiming) { + LiftRules.installServiceRequestTimer(NoOpServiceTimer) + } try { ResourceBundle getBundle (LiftRules.liftCoreResourceName) } catch { From 83f729f3e00374b86bb905e3095ebfa2bcad8b4f Mon Sep 17 00:00:00 2001 From: "zhong(hp)" Date: Fri, 9 Feb 2018 13:15:28 +0800 Subject: [PATCH 1721/1949] Changing a method of lift actor. --- core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala | 2 +- core/actor/src/main/scala/net/liftweb/actor/LiftActor.scala | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala b/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala index 4ade14aa13..44221bcaac 100644 --- a/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala +++ b/core/actor/src/main/scala/net/liftweb/actor/LAFuture.scala @@ -171,7 +171,7 @@ class LAFuture[T](val scheduler: LAScheduler = LAScheduler, context: Box[LAFutur /** * Has the future been aborted */ - def aborted_? = synchronized {satisfied} + def aborted_? = synchronized {aborted} /** * Java-friendly alias for completed_?. diff --git a/core/actor/src/main/scala/net/liftweb/actor/LiftActor.scala b/core/actor/src/main/scala/net/liftweb/actor/LiftActor.scala index 7a3a8644e2..b39d5ad2aa 100644 --- a/core/actor/src/main/scala/net/liftweb/actor/LiftActor.scala +++ b/core/actor/src/main/scala/net/liftweb/actor/LiftActor.scala @@ -130,7 +130,6 @@ trait SpecializedLiftActor[T] extends SimpleActor[T] { */ def remove() { - val newPrev = prev prev.next = next next.prev = prev } From 18fd2e8f50b2b1a99de781c98fb92447932aee8a Mon Sep 17 00:00:00 2001 From: Tim Nelson Date: Thu, 1 Mar 2018 02:54:40 -0600 Subject: [PATCH 1722/1949] Upgraded jBCrypt to v0.4 --- build.sbt | 3 +- .../main/java/net/liftweb/util/BCrypt.java | 786 ------------------ .../net/liftweb/mapper/MappedPassword.scala | 2 + .../liftweb/record/field/PasswordField.scala | 2 + project/Dependencies.scala | 1 + 5 files changed, 7 insertions(+), 787 deletions(-) delete mode 100644 core/util/src/main/java/net/liftweb/util/BCrypt.java diff --git a/build.sbt b/build.sbt index 94254bd0f7..f0243e1d67 100644 --- a/build.sbt +++ b/build.sbt @@ -222,7 +222,7 @@ lazy val mapper = .settings( description := "Mapper Library", parallelExecution in Test := false, - libraryDependencies ++= Seq(h2, derby), + libraryDependencies ++= Seq(h2, derby, jbcrypt), initialize in Test <<= (crossTarget in Test) { ct => System.setProperty("derby.stream.error.file", (ct / "derby.log").absolutePath) } @@ -231,6 +231,7 @@ lazy val mapper = lazy val record = persistenceProject("record") .dependsOn(proto) + .settings(libraryDependencies ++= Seq(jbcrypt)) lazy val squeryl_record = persistenceProject("squeryl-record") diff --git a/core/util/src/main/java/net/liftweb/util/BCrypt.java b/core/util/src/main/java/net/liftweb/util/BCrypt.java deleted file mode 100644 index 36718d7fd5..0000000000 --- a/core/util/src/main/java/net/liftweb/util/BCrypt.java +++ /dev/null @@ -1,786 +0,0 @@ -/* - * Copyright 2007-2011 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. - */ - -// --------------- -// This is a derivative work of jBCrypt, distributed under BSD licence -// and copyrighted as follows: -// -// Copyright (c) 2006 Damien Miller -// -// Permission to use, copy, modify, and distribute this software for any -// purpose with or without fee is hereby granted, provided that the above -// copyright notice and this permission notice appear in all copies. -// -// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -package net.liftweb.util; - -import java.io.UnsupportedEncodingException; - -import java.security.SecureRandom; - -/** - * BCrypt implements OpenBSD-style Blowfish password hashing using - * the scheme described in "A Future-Adaptable Password Scheme" by - * Niels Provos and David Mazieres. - *

              - * This password hashing system tries to thwart off-line password - * cracking using a computationally-intensive hashing algorithm, - * based on Bruce Schneier's Blowfish cipher. The work factor of - * the algorithm is parameterised, so it can be increased as - * computers get faster. - *

              - * Usage is really simple. To hash a password for the first time, - * call the hashpw method with a random salt, like this: - *

              - * - * String pw_hash = BCrypt.hashpw(plain_password, BCrypt.gensalt());
              - *
              - *

              - * To check whether a plaintext password matches one that has been - * hashed previously, use the checkpw method: - *

              - * - * if (BCrypt.checkpw(candidate_password, stored_hash))
              - *     System.out.println("It matches");
              - * else
              - *     System.out.println("It does not match");
              - *
              - *

              - * The gensalt() method takes an optional parameter (log_rounds) - * that determines the computational complexity of the hashing: - *

              - * - * String strong_salt = BCrypt.gensalt(10)
              - * String stronger_salt = BCrypt.gensalt(12)
              - *
              - *

              - * The amount of work increases exponentially (2**log_rounds), so - * each increment is twice as much work. The default log_rounds is - * 10, and the valid range is 4 to 31. - * - * @author Damien Miller - * @version 0.3m - */ -public class BCrypt { - // BCrypt parameters - - private static final int GENSALT_DEFAULT_LOG2_ROUNDS = 10; - private static final int BCRYPT_SALT_LEN = 16; - // Blowfish parameters - private static final int BLOWFISH_NUM_ROUNDS = 16; - // Initial contents of key schedule - private static final int P_orig[] = { - 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, - 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89, - 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, - 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, - 0x9216d5d9, 0x8979fb1b - }; - private static final int S_orig[] = { - 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, - 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99, - 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, - 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, - 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, - 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, - 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, - 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e, - 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, - 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, - 0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce, - 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, - 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, - 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677, - 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, - 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, - 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, - 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, - 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, - 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0, - 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, - 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, - 0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88, - 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, - 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, - 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d, - 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, - 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, - 0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, - 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, - 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, - 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09, - 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, - 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, - 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, - 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, - 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, - 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82, - 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, - 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, - 0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0, - 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, - 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, - 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8, - 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, - 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, - 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, - 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, - 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, - 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1, - 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, - 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, - 0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, - 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, - 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, - 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af, - 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, - 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, - 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, - 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, - 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, - 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915, - 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, - 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a, - 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, - 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266, - 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, - 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, - 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, - 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, - 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, - 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1, - 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, - 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, - 0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff, - 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd, - 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, - 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7, - 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, - 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, - 0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf, - 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, - 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, - 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87, - 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, - 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, - 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, - 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd, - 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, - 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509, - 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, - 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, - 0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, - 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a, - 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, - 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960, - 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, - 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, - 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802, - 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, - 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, - 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf, - 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, - 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, - 0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50, - 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, - 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, - 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281, - 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, - 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, - 0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128, - 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73, - 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, - 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0, - 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, - 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, - 0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, - 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, - 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, - 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061, - 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, - 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, - 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, - 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc, - 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, - 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340, - 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, - 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7, - 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, - 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068, - 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, - 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, - 0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, - 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, - 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, - 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb, - 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, - 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, - 0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42, - 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b, - 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, - 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb, - 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, - 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, - 0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, - 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c, - 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, - 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc, - 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, - 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, - 0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, - 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115, - 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, - 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728, - 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, - 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, - 0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37, - 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d, - 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, - 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b, - 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, - 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, - 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, - 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c, - 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, - 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9, - 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, - 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, - 0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d, - 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, - 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, - 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61, - 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, - 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, - 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, - 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c, - 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, - 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633, - 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, - 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, - 0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, - 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027, - 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, - 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62, - 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, - 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, - 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, - 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, - 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, - 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c, - 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, - 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0, - 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, - 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe, - 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, - 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, - 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, - 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, - 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, - 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22, - 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, - 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, - 0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9, - 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59, - 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, - 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51, - 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, - 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, - 0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b, - 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28, - 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, - 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd, - 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, - 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, - 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, - 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f, - 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, - 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32, - 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, - 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, - 0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae, - 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, - 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, - 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47, - 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, - 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, - 0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84, - 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048, - 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, - 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd, - 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, - 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, - 0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, - 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f, - 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, - 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525, - 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, - 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, - 0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964, - 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e, - 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, - 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d, - 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, - 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, - 0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02, - 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, - 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, - 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a, - 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, - 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, - 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, - 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060, - 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, - 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9, - 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, - 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6 - }; - // bcrypt IV: "OrpheanBeholderScryDoubt" - static private final int bf_crypt_ciphertext[] = { - 0x4f727068, 0x65616e42, 0x65686f6c, - 0x64657253, 0x63727944, 0x6f756274 - }; - // Table for Base64 encoding - static private final char base64_code[] = { - '.', '/', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', - 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', - 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', - 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', - 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', - '6', '7', '8', '9' - }; - // Table for Base64 decoding - static private final byte index_64[] = { - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, 0, 1, 54, 55, - 56, 57, 58, 59, 60, 61, 62, 63, -1, -1, - -1, -1, -1, -1, -1, 2, 3, 4, 5, 6, - 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, - 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, - -1, -1, -1, -1, -1, -1, 28, 29, 30, - 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, - 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, - 51, 52, 53, -1, -1, -1, -1, -1 - }; - // Expanded Blowfish key - private int P[]; - private int S[]; - - /** - * Encode a byte array using bcrypt's slightly-modified base64 - * encoding scheme. Note that this is *not* compatible with - * the standard MIME-base64 encoding. - * - * @param d the byte array to encode - * @param len the number of bytes to encode - * @return base64-encoded string - * @exception IllegalArgumentException if the length is invalid - */ - private static String encode_base64(byte d[], int len) - throws IllegalArgumentException { - int off = 0; - StringBuffer rs = new StringBuffer(); - int c1, c2; - - if (len <= 0 || len > d.length) { - throw new IllegalArgumentException("Invalid len"); - } - - while (off < len) { - c1 = d[off++] & 0xff; - rs.append(base64_code[(c1 >> 2) & 0x3f]); - c1 = (c1 & 0x03) << 4; - if (off >= len) { - rs.append(base64_code[c1 & 0x3f]); - break; - } - c2 = d[off++] & 0xff; - c1 |= (c2 >> 4) & 0x0f; - rs.append(base64_code[c1 & 0x3f]); - c1 = (c2 & 0x0f) << 2; - if (off >= len) { - rs.append(base64_code[c1 & 0x3f]); - break; - } - c2 = d[off++] & 0xff; - c1 |= (c2 >> 6) & 0x03; - rs.append(base64_code[c1 & 0x3f]); - rs.append(base64_code[c2 & 0x3f]); - } - return rs.toString(); - } - - /** - * Look up the 3 bits base64-encoded by the specified character, - * range-checking againt conversion table - * @param x the base64-encoded value - * @return the decoded value of x - */ - private static byte char64(char x) { - if ((int) x < 0 || (int) x > index_64.length) { - return -1; - } - return index_64[(int) x]; - } - - /** - * Decode a string encoded using bcrypt's base64 scheme to a - * byte array. Note that this is *not* compatible with - * the standard MIME-base64 encoding. - * @param s the string to decode - * @param maxolen the maximum number of bytes to decode - * @return an array containing the decoded bytes - * @throws IllegalArgumentException if maxolen is invalid - */ - private static byte[] decode_base64(String s, int maxolen) - throws IllegalArgumentException { - StringBuffer rs = new StringBuffer(); - int off = 0, slen = s.length(), olen = 0; - byte ret[]; - byte c1, c2, c3, c4, o; - - if (maxolen <= 0) { - throw new IllegalArgumentException("Invalid maxolen"); - } - - while (off < slen - 1 && olen < maxolen) { - c1 = char64(s.charAt(off++)); - c2 = char64(s.charAt(off++)); - if (c1 == -1 || c2 == -1) { - break; - } - o = (byte) (c1 << 2); - o |= (c2 & 0x30) >> 4; - rs.append((char) o); - if (++olen >= maxolen || off >= slen) { - break; - } - c3 = char64(s.charAt(off++)); - if (c3 == -1) { - break; - } - o = (byte) ((c2 & 0x0f) << 4); - o |= (c3 & 0x3c) >> 2; - rs.append((char) o); - if (++olen >= maxolen || off >= slen) { - break; - } - c4 = char64(s.charAt(off++)); - o = (byte) ((c3 & 0x03) << 6); - o |= c4; - rs.append((char) o); - ++olen; - } - - ret = new byte[olen]; - for (off = 0; off < olen; off++) { - ret[off] = (byte) rs.charAt(off); - } - return ret; - } - - /** - * Blowfish encipher a single 64-bit block encoded as - * two 32-bit halves - * @param lr an array containing the two 32-bit half blocks - * @param off the position in the array of the blocks - */ - private final void encipher(int lr[], int off) { - int i, n, l = lr[off], r = lr[off + 1]; - - l ^= P[0]; - for (i = 0; i <= BLOWFISH_NUM_ROUNDS - 2;) { - // Feistel substitution on left word - n = S[(l >> 24) & 0xff]; - n += S[0x100 | ((l >> 16) & 0xff)]; - n ^= S[0x200 | ((l >> 8) & 0xff)]; - n += S[0x300 | (l & 0xff)]; - r ^= n ^ P[++i]; - - // Feistel substitution on right word - n = S[(r >> 24) & 0xff]; - n += S[0x100 | ((r >> 16) & 0xff)]; - n ^= S[0x200 | ((r >> 8) & 0xff)]; - n += S[0x300 | (r & 0xff)]; - l ^= n ^ P[++i]; - } - lr[off] = r ^ P[BLOWFISH_NUM_ROUNDS + 1]; - lr[off + 1] = l; - } - - /** - * Cycically extract a word of key material - * @param data the string to extract the data from - * @param offp a "pointer" (as a one-entry array) to the - * current offset into data - * @return the next word of material from data - */ - private static int streamtoword(byte data[], int offp[]) { - int i; - int word = 0; - int off = offp[0]; - - for (i = 0; i < 4; i++) { - word = (word << 8) | (data[off] & 0xff); - off = (off + 1) % data.length; - } - - offp[0] = off; - return word; - } - - /** - * Initialise the Blowfish key schedule - */ - private void init_key() { - P = P_orig.clone(); - S = S_orig.clone(); - } - - /** - * Key the Blowfish cipher - * @param key an array containing the key - */ - private void key(byte key[]) { - int i; - int koffp[] = {0}; - int lr[] = {0, 0}; - int plen = P.length, slen = S.length; - - for (i = 0; i < plen; i++) { - P[i] = P[i] ^ streamtoword(key, koffp); - } - - for (i = 0; i < plen; i += 2) { - encipher(lr, 0); - P[i] = lr[0]; - P[i + 1] = lr[1]; - } - - for (i = 0; i < slen; i += 2) { - encipher(lr, 0); - S[i] = lr[0]; - S[i + 1] = lr[1]; - } - } - - /** - * Perform the "enhanced key schedule" step described by - * Provos and Mazieres in "A Future-Adaptable Password Scheme" - * http://www.openbsd.org/papers/bcrypt-paper.ps - * @param data salt information - * @param key password information - */ - private void ekskey(byte data[], byte key[]) { - int i; - int koffp[] = {0}, doffp[] = {0}; - int lr[] = {0, 0}; - int plen = P.length, slen = S.length; - - for (i = 0; i < plen; i++) { - P[i] = P[i] ^ streamtoword(key, koffp); - } - - for (i = 0; i < plen; i += 2) { - lr[0] ^= streamtoword(data, doffp); - lr[1] ^= streamtoword(data, doffp); - encipher(lr, 0); - P[i] = lr[0]; - P[i + 1] = lr[1]; - } - - for (i = 0; i < slen; i += 2) { - lr[0] ^= streamtoword(data, doffp); - lr[1] ^= streamtoword(data, doffp); - encipher(lr, 0); - S[i] = lr[0]; - S[i + 1] = lr[1]; - } - } - - /** - * Perform the central password hashing step in the - * bcrypt scheme - * @param password the password to hash - * @param salt the binary salt to hash with the password - * @param log_rounds the binary logarithm of the number - * of rounds of hashing to apply - * @return an array containing the binary hashed password - */ - private byte[] crypt_raw(byte password[], byte salt[], int log_rounds) { - int rounds, i, j; - int cdata[] = bf_crypt_ciphertext.clone(); - int clen = cdata.length; - byte ret[]; - - if (log_rounds < 4 || log_rounds > 31) { - throw new IllegalArgumentException("Bad number of rounds"); - } - rounds = 1 << log_rounds; - if (salt.length != BCRYPT_SALT_LEN) { - throw new IllegalArgumentException("Bad salt length"); - } - - init_key(); - ekskey(salt, password); - for (i = 0; i < rounds; i++) { - key(password); - key(salt); - } - - for (i = 0; i < 64; i++) { - for (j = 0; j < (clen >> 1); j++) { - encipher(cdata, j << 1); - } - } - - ret = new byte[clen * 4]; - for (i = 0, j = 0; i < clen; i++) { - ret[j++] = (byte) ((cdata[i] >> 24) & 0xff); - ret[j++] = (byte) ((cdata[i] >> 16) & 0xff); - ret[j++] = (byte) ((cdata[i] >> 8) & 0xff); - ret[j++] = (byte) (cdata[i] & 0xff); - } - return ret; - } - - /** - * Hash a password using the OpenBSD bcrypt scheme - * @param password the password to hash - * @param salt the salt to hash with (perhaps generated - * using BCrypt.gensalt) - * @return the hashed password - */ - public static String hashpw(String password, String salt) { - BCrypt B; - String real_salt; - byte passwordb[], saltb[], hashed[]; - char minor = (char) 0; - int rounds, off = 0; - StringBuffer rs = new StringBuffer(); - - if (salt.charAt(0) != '$' || salt.charAt(1) != '2') { - throw new IllegalArgumentException("Invalid salt version"); - } - if (salt.charAt(2) == '$') { - off = 3; - } else { - minor = salt.charAt(2); - if (minor != 'a' || salt.charAt(3) != '$') { - throw new IllegalArgumentException("Invalid salt revision"); - } - off = 4; - } - - // Extract number of rounds - if (salt.charAt(off + 2) > '$') { - throw new IllegalArgumentException("Missing salt rounds"); - } - rounds = Integer.parseInt(salt.substring(off, off + 2)); - - real_salt = salt.substring(off + 3, off + 25); - try { - passwordb = (password + (minor >= 'a' ? "\000" : "")).getBytes("UTF-8"); - } catch (UnsupportedEncodingException uee) { - throw new AssertionError("UTF-8 is not supported"); - } - - saltb = decode_base64(real_salt, BCRYPT_SALT_LEN); - - B = new BCrypt(); - hashed = B.crypt_raw(passwordb, saltb, rounds); - - rs.append("$2"); - if (minor >= 'a') { - rs.append(minor); - } - rs.append("$"); - if (rounds < 10) { - rs.append("0"); - } - rs.append(Integer.toString(rounds)); - rs.append("$"); - rs.append(encode_base64(saltb, saltb.length)); - rs.append(encode_base64(hashed, - bf_crypt_ciphertext.length * 4 - 1)); - return rs.toString(); - } - - /** - * Generate a salt for use with the BCrypt.hashpw() method - * @param log_rounds the log2 of the number of rounds of - * hashing to apply - the work factor therefore increases as - * 2**log_rounds. - * @param random an instance of SecureRandom to use - * @return an encoded salt value - */ - public static String gensalt(int log_rounds, SecureRandom random) { - StringBuffer rs = new StringBuffer(); - byte rnd[] = new byte[BCRYPT_SALT_LEN]; - - random.nextBytes(rnd); - - rs.append("$2a$"); - if (log_rounds < 10) { - rs.append("0"); - } - rs.append(Integer.toString(log_rounds)); - rs.append("$"); - rs.append(encode_base64(rnd, rnd.length)); - return rs.toString(); - } - - /** - * Generate a salt for use with the BCrypt.hashpw() method - * @param log_rounds the log2 of the number of rounds of - * hashing to apply - the work factor therefore increases as - * 2**log_rounds. - * @return an encoded salt value - */ - public static String gensalt(int log_rounds) { - return gensalt(log_rounds, new SecureRandom()); - } - - /** - * Generate a salt for use with the BCrypt.hashpw() method, - * selecting a reasonable default for the number of hashing - * rounds to apply - * @return an encoded salt value - */ - public static String gensalt() { - return gensalt(GENSALT_DEFAULT_LOG2_ROUNDS); - } - - /** - * Check that a plaintext password matches a previously hashed - * one - * @param plaintext the plaintext password to verify - * @param hashed the previously-hashed password - * @return true if the passwords match, false otherwise - */ - public static boolean checkpw(String plaintext, String hashed) { - return (hashed.compareTo(hashpw(plaintext, hashed)) == 0); - } -} diff --git a/persistence/mapper/src/main/scala/net/liftweb/mapper/MappedPassword.scala b/persistence/mapper/src/main/scala/net/liftweb/mapper/MappedPassword.scala index a9c16f38df..18cf9642e5 100644 --- a/persistence/mapper/src/main/scala/net/liftweb/mapper/MappedPassword.scala +++ b/persistence/mapper/src/main/scala/net/liftweb/mapper/MappedPassword.scala @@ -30,6 +30,8 @@ import net.liftweb.json._ import net.liftweb.common._ import net.liftweb.http.js._ +import org.mindrot.jbcrypt.BCrypt + object MappedPassword { val blankPw = "*******" diff --git a/persistence/record/src/main/scala/net/liftweb/record/field/PasswordField.scala b/persistence/record/src/main/scala/net/liftweb/record/field/PasswordField.scala index 40c1d612ae..7cde8710c2 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/field/PasswordField.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/field/PasswordField.scala @@ -29,6 +29,8 @@ import http.js._ import S._ import JE._ +import org.mindrot.jbcrypt.BCrypt + object PasswordField { @volatile var blankPw = "*******" @volatile var minPasswordLength = 5 diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 538698c8ff..9139e1539a 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -33,6 +33,7 @@ object Dependencies { lazy val commons_fileupload = "commons-fileupload" % "commons-fileupload" % "1.3.1" lazy val commons_httpclient = "commons-httpclient" % "commons-httpclient" % "3.1" lazy val javamail = "javax.mail" % "mail" % "1.4.7" + lazy val jbcrypt = "org.mindrot" % "jbcrypt" % "0.4" lazy val joda_time = "joda-time" % "joda-time" % "2.9.2" lazy val joda_convert = "org.joda" % "joda-convert" % "1.8.1" lazy val htmlparser = "nu.validator.htmlparser" % "htmlparser" % "1.4" From 84d2270433bd42d748df089198028bd963e85e8c Mon Sep 17 00:00:00 2001 From: Tim Nelson Date: Thu, 1 Mar 2018 03:14:41 -0600 Subject: [PATCH 1723/1949] Upgrade mongo-java-driver to v3.6.3 --- project/Dependencies.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 538698c8ff..67e0c6e6ea 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -36,8 +36,8 @@ object Dependencies { lazy val joda_time = "joda-time" % "joda-time" % "2.9.2" lazy val joda_convert = "org.joda" % "joda-convert" % "1.8.1" lazy val htmlparser = "nu.validator.htmlparser" % "htmlparser" % "1.4" - lazy val mongo_java_driver = "org.mongodb" % "mongodb-driver" % "3.4.1" - lazy val mongo_java_driver_async = "org.mongodb" % "mongodb-driver-async" % "3.4.1" + lazy val mongo_java_driver = "org.mongodb" % "mongodb-driver" % "3.6.3" + lazy val mongo_java_driver_async = "org.mongodb" % "mongodb-driver-async" % "3.6.3" lazy val paranamer = "com.thoughtworks.paranamer" % "paranamer" % "2.8" lazy val scalajpa = "org.scala-libs" % "scalajpa" % "1.5" cross CVMappingAll lazy val scalap: ModuleMap = "org.scala-lang" % "scalap" % _ From 668dd6d444fe84bcb717c6b8da6e502acd7a2bf5 Mon Sep 17 00:00:00 2001 From: Tim Nelson Date: Thu, 1 Mar 2018 03:49:51 -0600 Subject: [PATCH 1724/1949] Updated build.sbt to use newer syntax --- build.sbt | 59 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/build.sbt b/build.sbt index 94254bd0f7..263f95814f 100644 --- a/build.sbt +++ b/build.sbt @@ -22,11 +22,13 @@ publishTo in ThisBuild := { } } scmInfo in ThisBuild := Some(ScmInfo(url("https://github.com/lift/framework"), "scm:git:https://github.com/lift/framework.git")) -pomExtra in ThisBuild := Developers.toXml +pomExtra in ThisBuild := Developers.toXml credentials in ThisBuild += Credentials(BuildPaths.getGlobalSettingsDirectory(state.value, BuildPaths.getGlobalBase(state.value)) / ".credentials") -initialize <<= (name, version, scalaVersion).apply(printLogo) +initialize := { + printLogo(name.value, version.value, scalaVersion.value) +} resolvers in ThisBuild ++= Seq( "snapshots" at "https://oss.sonatype.org/content/repositories/snapshots", @@ -161,34 +163,37 @@ lazy val webkit = jasmineCore, jasmineAjax ), - initialize in Test <<= (sourceDirectory in Test) { src => - System.setProperty("net.liftweb.webapptest.src.test.webapp", (src / "webapp").absString) + initialize in Test := { + System.setProperty( + "net.liftweb.webapptest.src.test.webapp", + ((sourceDirectory in Test).value / "webapp").absString + ) }, - unmanagedSourceDirectories in Compile <+= (sourceDirectory in Compile, scalaBinaryVersion) { - (sourceDirectory, binaryVersion) => - sourceDirectory / ("scala_" + binaryVersion) + unmanagedSourceDirectories in Compile += { + (sourceDirectory in Compile).value / ("scala_" + scalaBinaryVersion.value) }, - unmanagedSourceDirectories in Test <+= (sourceDirectory in Test, scalaBinaryVersion) { - (sourceDirectory, binaryVersion) => - sourceDirectory / ("scala_" + binaryVersion) + unmanagedSourceDirectories in Test += { + (sourceDirectory in Test).value / ("scala_" + scalaBinaryVersion.value) }, - (compile in Compile) <<= (compile in Compile) dependsOn (WebKeys.assets), + compile in Compile := (compile in Compile).dependsOn(WebKeys.assets).value, /** * This is to ensure that the tests in net.liftweb.webapptest run last * so that other tests (MenuSpec in particular) run before the SiteMap * is set. */ - testGrouping in Test <<= (definedTests in Test).map { tests => - import Tests._ + testGrouping in Test := { + (definedTests in Test).map { tests => + import Tests._ - val (webapptests, others) = tests.partition { test => - test.name.startsWith("net.liftweb.webapptest") - } + val (webapptests, others) = tests.partition { test => + test.name.startsWith("net.liftweb.webapptest") + } - Seq( - new Group("others", others, InProcess), - new Group("webapptests", webapptests, InProcess) - ) + Seq( + new Group("others", others, InProcess), + new Group("webapptests", webapptests, InProcess) + ) + }.value }, scalacOptions in (Compile, doc) ++= { @@ -223,8 +228,11 @@ lazy val mapper = description := "Mapper Library", parallelExecution in Test := false, libraryDependencies ++= Seq(h2, derby), - initialize in Test <<= (crossTarget in Test) { ct => - System.setProperty("derby.stream.error.file", (ct / "derby.log").absolutePath) + initialize in Test := { + System.setProperty( + "derby.stream.error.file", + ((crossTarget in Test).value / "derby.log").absolutePath + ) } ) @@ -243,8 +251,11 @@ lazy val mongodb = .settings( parallelExecution in Test := false, libraryDependencies ++= Seq(mongo_java_driver, mongo_java_driver_async), - initialize in Test <<= (resourceDirectory in Test) { rd => - System.setProperty("java.util.logging.config.file", (rd / "logging.properties").absolutePath) + initialize in Test := { + System.setProperty( + "java.util.logging.config.file", + ((resourceDirectory in Test).value / "logging.properties").absolutePath + ) } ) From aa1d5a8de295861d8ae5ab3904534e8e5acc31fd Mon Sep 17 00:00:00 2001 From: Tim Nelson Date: Thu, 1 Mar 2018 04:40:55 -0600 Subject: [PATCH 1725/1949] Use JavaConverters instead of JavaConversions in mongodb and mongodb-record modules --- .../net/liftweb/mongodb/record/BsonRecord.scala | 8 ++++---- .../liftweb/mongodb/record/MongoMetaRecord.scala | 8 ++++---- .../mongodb/record/field/MongoMapField.scala | 8 +++----- .../scala/net/liftweb/mongodb/JObjectParser.scala | 10 +++++----- .../main/scala/net/liftweb/mongodb/Mongo.scala | 6 +++--- .../scala/net/liftweb/mongodb/MongoDocument.scala | 15 ++++++--------- project/Dependencies.scala | 2 +- 7 files changed, 26 insertions(+), 31 deletions(-) diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/BsonRecord.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/BsonRecord.scala index ee8dc0023d..9a75216233 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/BsonRecord.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/BsonRecord.scala @@ -1,5 +1,5 @@ /* - * Copyright 2011-2013 WorldWide Conferencing, LLC + * Copyright 2011-2018 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. @@ -21,7 +21,7 @@ package record import common._ import java.util.regex.Pattern -import scala.collection.JavaConversions._ +import scala.collection.JavaConverters._ import net.liftweb.record.{Field, MetaRecord, Record} import net.liftweb.record.field._ @@ -155,7 +155,7 @@ trait BsonMetaRecord[BaseRecord <: BsonRecord[BaseRecord]] extends MetaRecord[Ba * @return Unit */ def setFieldsFromDBObject(inst: BaseRecord, dbo: DBObject): Unit = { - for (k <- dbo.keySet; field <- inst.fieldByName(k.toString)) { + for (k <- dbo.keySet.asScala; field <- inst.fieldByName(k.toString)) { field.setFromAny(dbo.get(k.toString)) } inst.runSafe { @@ -164,7 +164,7 @@ trait BsonMetaRecord[BaseRecord <: BsonRecord[BaseRecord]] extends MetaRecord[Ba } def setFieldsFromDocument(inst: BaseRecord, doc: Document): Unit = { - for (k <- doc.keySet; field <- inst.fieldByName(k.toString)) { + for (k <- doc.keySet.asScala; field <- inst.fieldByName(k.toString)) { field.setFromAny(doc.get(k.toString)) } inst.runSafe { diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/MongoMetaRecord.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/MongoMetaRecord.scala index 5290506575..835cadaf89 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/MongoMetaRecord.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/MongoMetaRecord.scala @@ -1,5 +1,5 @@ /* - * Copyright 2010-2017 WorldWide Conferencing, LLC + * Copyright 2010-2018 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. @@ -30,7 +30,7 @@ import net.liftweb.record.MandatoryTypedField import org.bson.Document import org.bson.types.ObjectId -import scala.collection.JavaConversions._ +import scala.collection.JavaConverters._ import scala.concurrent.{Future, Promise} trait MongoMetaRecord[BaseRecord <: MongoRecord[BaseRecord]] @@ -149,7 +149,7 @@ trait MongoMetaRecord[BaseRecord <: MongoRecord[BaseRecord]] /** Mongo Cursors are both Iterable and Iterator, * so we need to reduce ambiguity for implicits */ - (coll.find: Iterator[DBObject]).map(fromDBObject).toList + coll.find.iterator.asScala.map(fromDBObject).toList } /** @@ -177,7 +177,7 @@ trait MongoMetaRecord[BaseRecord <: MongoRecord[BaseRecord]] ) sort.foreach(s => cur.sort(s)) // This retrieves all documents and puts them in memory. - (cur: Iterator[DBObject]).map(fromDBObject).toList + cur.iterator.asScala.map(fromDBObject).toList } } diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoMapField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoMapField.scala index 576017c4d1..463f8c1aab 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoMapField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoMapField.scala @@ -1,5 +1,5 @@ /* - * Copyright 2010-2017 WorldWide Conferencing, LLC + * Copyright 2010-2018 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. @@ -19,6 +19,7 @@ package mongodb package record package field +import scala.collection.JavaConverters._ import scala.xml.NodeSeq import net.liftweb.common.{Box, Empty, Failure, Full} @@ -101,10 +102,8 @@ class MongoMapField[OwnerType <: BsonRecord[OwnerType], MapValueType](rec: Owner // set this field's value using a DBObject returned from Mongo. def setFromDBObject(dbo: DBObject): Box[Map[String, MapValueType]] = { - import scala.collection.JavaConversions._ - setBox(Full( - Map() ++ dbo.keySet.map { + Map() ++ dbo.keySet.asScala.map { k => (k.toString, dbo.get(k).asInstanceOf[MapValueType]) } )) @@ -112,7 +111,6 @@ class MongoMapField[OwnerType <: BsonRecord[OwnerType], MapValueType](rec: Owner // set this field's value using a bson.Document returned from Mongo. def setFromDocument(doc: Document): Box[Map[String, MapValueType]] = { - import scala.collection.JavaConverters._ val map: Map[String, MapValueType] = doc.asScala.map { case (k, v) => k -> v.asInstanceOf[MapValueType] } (scala.collection.breakOut) diff --git a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/JObjectParser.scala b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/JObjectParser.scala index 19b78f7ece..ccaa291844 100644 --- a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/JObjectParser.scala +++ b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/JObjectParser.scala @@ -1,5 +1,5 @@ /* - * Copyright 2010-2011 WorldWide Conferencing, LLC + * Copyright 2010-2018 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. @@ -17,7 +17,7 @@ package net.liftweb package mongodb -import scala.collection.JavaConversions._ +import scala.collection.JavaConverters._ import java.util.{Date, UUID} import java.util.regex.Pattern @@ -60,14 +60,14 @@ object JObjectParser extends SimpleInjector { case x if primitive_?(x.getClass) => primitive2jvalue(x) case x if datetype_?(x.getClass) => datetype2jvalue(x)(formats) case x if mongotype_?(x.getClass) => mongotype2jvalue(x)(formats) - case x: BasicDBList => JArray(x.toList.map( x => serialize(x)(formats))) + case x: BasicDBList => JArray(x.asScala.toList.map( x => serialize(x)(formats))) case x: BasicDBObject => JObject( - x.keySet.toList.map { f => + x.keySet.asScala.toList.map { f => JField(f.toString, serialize(x.get(f.toString))(formats)) } ) case x: Document => JObject( - x.keySet.toList.map { f => + x.keySet.asScala.toList.map { f => JField(f.toString, serialize(x.get(f.toString), formats)) } ) diff --git a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/Mongo.scala b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/Mongo.scala index 61877a089d..7ade90f970 100644 --- a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/Mongo.scala +++ b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/Mongo.scala @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 WorldWide Conferencing, LLC + * Copyright 2010-2018 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. @@ -122,8 +122,8 @@ object MongoDB { * Calls close on each MongoClient instance and clears the HashMap. */ def closeAll(): Unit = { - import scala.collection.JavaConversions._ - dbs.values.foreach { case (mngo, _) => + import scala.collection.JavaConverters._ + dbs.values.asScala.foreach { case (mngo, _) => mngo.close() } dbs.clear() diff --git a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/MongoDocument.scala b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/MongoDocument.scala index 5ecf5068a9..8d660a19b9 100644 --- a/persistence/mongodb/src/main/scala/net/liftweb/mongodb/MongoDocument.scala +++ b/persistence/mongodb/src/main/scala/net/liftweb/mongodb/MongoDocument.scala @@ -1,5 +1,5 @@ /* - * Copyright 2010-2011 WorldWide Conferencing, LLC + * Copyright 2010-2018 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. @@ -17,9 +17,10 @@ package net.liftweb package mongodb -import util.{ConnectionIdentifier, DefaultConnectionIdentifier} +import net.liftweb.json.JsonAST.JObject +import net.liftweb.util.{ConnectionIdentifier, DefaultConnectionIdentifier} -import json.JsonAST.JObject +import scala.collection.JavaConverters.asScalaIteratorConverter import java.util.UUID @@ -120,13 +121,11 @@ trait MongoDocumentMeta[BaseDocument] extends JsonObjectMeta[BaseDocument] with * Find all documents in this collection */ def findAll: List[BaseDocument] = { - import scala.collection.JavaConversions._ - MongoDB.useCollection(connectionIdentifier, collectionName)(coll => { /** Mongo Cursors are both Iterable and Iterator, * so we need to reduce ambiguity for implicits */ - (coll.find: Iterator[DBObject]).map(create).toList + coll.find.iterator.asScala.map(create).toList }) } @@ -134,8 +133,6 @@ trait MongoDocumentMeta[BaseDocument] extends JsonObjectMeta[BaseDocument] with * Find all documents using a DBObject query. */ def findAll(qry: DBObject, sort: Option[DBObject], opts: FindOption*): List[BaseDocument] = { - import scala.collection.JavaConversions._ - val findOpts = opts.toList MongoDB.useCollection(connectionIdentifier, collectionName) ( coll => { @@ -148,7 +145,7 @@ trait MongoDocumentMeta[BaseDocument] extends JsonObjectMeta[BaseDocument] with /** Mongo Cursors are both Iterable and Iterator, * so we need to reduce ambiguity for implicits */ - (cur: Iterator[DBObject]).map(create).toList + cur.iterator.asScala.map(create).toList }) } diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 538698c8ff..59d1198b6c 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -1,5 +1,5 @@ /* - * Copyright 2011-2015 WorldWide Conferencing, LLC + * Copyright 2011-2018 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. From c4160a74652868bd975d0929bd3aacc24b1d167e Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 3 Mar 2018 10:00:18 -0500 Subject: [PATCH 1726/1949] Added some rephrasings and tweaks based on excellent review feedback --- .../1-view-first-development.adoc | 17 +++++++++-------- .../2-the-lift-menu-system.adoc | 9 ++++----- .../3-adding-snippet-bindings.adoc | 5 +++-- .../4-css-selector-transforms.adoc | 17 +++++++++-------- .../7-using-actors-for-chat.adoc | 9 +++++---- .../8-customizable-usernames.adoc | 4 ++-- .../9-comet-actors.adoc | 2 ++ 7 files changed, 34 insertions(+), 29 deletions(-) diff --git a/docs/getting-started-tutorial/1-view-first-development.adoc b/docs/getting-started-tutorial/1-view-first-development.adoc index e60bdf9887..a8e33a34d1 100644 --- a/docs/getting-started-tutorial/1-view-first-development.adoc +++ b/docs/getting-started-tutorial/1-view-first-development.adoc @@ -8,19 +8,20 @@ If you're developing a user-facing web site or application, one of Lift's greatest improvements over existing systems is view-first development. View-first development thoroughly separates the process of creating the user -interface from the process of putting data from the system into it, in a way -that lets you stay focused on users when you're creating the user interface and -worry about the the interface between your backend and the HTML only when +interface from the process of putting data from the system into it. This way, +you can stay focused on users when you're creating the user interface and +focus on the interface between your backend and the HTML only when you're working on the backend. The flip side of view-first development is that it takes some getting used to -if one is accustomed to the typical web MVC framework. The first stop when +if you are accustomed to the typical web MVC framework. The first stop when figuring out what's going on in a typical web MVC setup is the controller. In -Lift, your first stop is your HTML file. Everything starts in the HTML, and in -what it is that you want to present to the user. You don't just think about -user interactions first, you *build* them first, and let them guide your +Lift, your first stop is your HTML file. Everything starts in the HTML, where +you decide what it is that you want to present to the user. You don't just think +about user interactions first, you *build* them first, and let them guide your development forward and inform it at every step of the way. Turning a usability -tested high fidelity mockup into a live page has never been so straightforward. +tested, high-fidelity mockup into a live page has never been so +straightforward. For our chat app, we're going to focus first on two use cases, formulated as user stories: diff --git a/docs/getting-started-tutorial/2-the-lift-menu-system.adoc b/docs/getting-started-tutorial/2-the-lift-menu-system.adoc index cddf382db2..dc28ac1559 100644 --- a/docs/getting-started-tutorial/2-the-lift-menu-system.adoc +++ b/docs/getting-started-tutorial/2-the-lift-menu-system.adoc @@ -6,12 +6,11 @@ # The Lift Menu System Another distinguishing characteristic of Lift is that it is *secure by -default*. Amongst other things, this means that you can't access a file in your -`src/main/webapp` directory through your application unless you explicitly -define that it's meant to be accessed. You define this using Lift's menu -system, called `SiteMap`. +default*. Amongst other things, this means that if you enable Lift's `SiteMap` +menu system, you can't access a file in your `src/main/webapp` directory through +your application unless you explicitly define that it's meant to be accessed. -Hooking up a simple page like this one is easy, and seems redundant; rest +Hooking up a simple page in `SiteMap` is easy, and seems redundant; rest assured, we'll explore the real power of `SiteMap` as the application becomes more complicated. All you have to do for the chat page is add a line to your `SiteMap.scala` that names the page and points to the file in the `webapp` diff --git a/docs/getting-started-tutorial/3-adding-snippet-bindings.adoc b/docs/getting-started-tutorial/3-adding-snippet-bindings.adoc index 96e004dbb1..7dc67ac51f 100644 --- a/docs/getting-started-tutorial/3-adding-snippet-bindings.adoc +++ b/docs/getting-started-tutorial/3-adding-snippet-bindings.adoc @@ -38,8 +38,9 @@ And to the input form:

              ``` -These two indicate two methods in a class called `Chat`, which Lift searches -for in the `code.snippet` package footnote:[This can be changed using +The two references in the `data-lift` attributes we added indicate two methods +in a class called `Chat`, which Lift searches for in the `code.snippet` package +footnote:[This can be changed using link:++https://liftweb.net/api/30/api/index.html#net.liftweb.http.LiftRules@addToPackages(what:String):Unit++[`LiftRules.addPackage`.]. We'll write a very basic version that just passes through the contents of the list and form unchanged, and then in the next section we'll start adding some diff --git a/docs/getting-started-tutorial/4-css-selector-transforms.adoc b/docs/getting-started-tutorial/4-css-selector-transforms.adoc index 01f8e8cbee..f7bd576e55 100644 --- a/docs/getting-started-tutorial/4-css-selector-transforms.adoc +++ b/docs/getting-started-tutorial/4-css-selector-transforms.adoc @@ -5,16 +5,17 @@ # CSS Selector Transforms -Because Lift operates by transforming HTML trees, it needs an easy way to +Because Lift operates by transforming HTML trees, we need an easy way to specify those transformations. Otherwise we'd be doing a bunch of recursive -tree searches and munges and it would get ugly and probably slow to boot. To -deal with transformations easily, we use a small subset of CSS selectors, with -a few Lift idiosyncrasies to maximize performance and address some use cases -that are particularly useful when transforming trees. +tree searches and munges, which would get ugly, unpleasant, and probably end up +being a performance nightmare. To deal with transformations easily, we instead +use a small subset of CSS selectors, with a few Lift variations that allow us to +maximize performance and address additional use cases around tree +transformation. We'll leave forms for the next section, as forms always come with a catalog of related functionality, and focus on binding the list of chat messages in this -section. We'll also add a new one before every page load, so that we can see +section. We'll also add a new message before every page load, so that we can see the list changing. First, we'll define a variable to hold the messages: @@ -59,7 +60,7 @@ directory of the chat app and start up the application: ``` $ sbt -> container:start +> jetty:start [info] Compiling 4 Scala sources to /Users/Shadowfiend/github/lift-example/target/scala-2.9.2/classes... [info] jetty-8.1.7.v20120910 [info] NO JSP Support for /, did not find org.apache.jasper.servlet.JspServlet @@ -87,7 +88,7 @@ every time we render the message list: Let's recompile and restart the server: ``` -> container:stop +> jetty:stop [info] stopped o.e.j.w.WebAppContext{/,[file:/Users/Shadowfiend/github/lift-example/src/main/webapp/]} [success] Total time: 0 s, completed Oct 6, 2013 2:36:48 PM > container:start diff --git a/docs/getting-started-tutorial/7-using-actors-for-chat.adoc b/docs/getting-started-tutorial/7-using-actors-for-chat.adoc index 5ca26ff7e5..fd4e2f17e8 100644 --- a/docs/getting-started-tutorial/7-using-actors-for-chat.adoc +++ b/docs/getting-started-tutorial/7-using-actors-for-chat.adoc @@ -112,7 +112,7 @@ listing messages by updating the `Chat` snippet: ``` ... def messages = { - val messageEntries = Box.asA[List[ChatMessage]](ChatActor !? GetMessages).flatten + val messageEntries = Box.asA[List[ChatMessage]](ChatActor !? GetMessages) openOr List() ClearClearable & "li" #> messageEntries.map { entry => @@ -134,11 +134,12 @@ to the type `T`, and, if it succeeds, provides a `Full` `Box` with the converted value of the appropriate type. If it fails, it provides an `Empty` `Box` instead. -To deal with the fact that the `Box` may be `Full` or `Empty`, we use `flatten` +To deal with the fact that the `Box` may be `Full` or `Empty`, we use `openOr` on the `Box`. We do this because the type of `messageEntries` is now a `Box[List[ChatMessage]]`, meaning a box that *contains* a list of chat -messages. `flatten` will give us the plain list of messages if the `Box` is -`Full`, and an empty list if it's `Empty`, which is perfect. +messages. `openOr` will give us the plain list of messages if the `Box` is +`Full`, and return the second parameter, an empty `List`, if the `Box` is +`Empty`. It's worth mentioning that it seems like we *know* we'll be getting a `List[ChatMessage]` from the actor. However, the compiler *doesn't*, and that diff --git a/docs/getting-started-tutorial/8-customizable-usernames.adoc b/docs/getting-started-tutorial/8-customizable-usernames.adoc index c426f1294e..cbf62441c0 100644 --- a/docs/getting-started-tutorial/8-customizable-usernames.adoc +++ b/docs/getting-started-tutorial/8-customizable-usernames.adoc @@ -24,8 +24,8 @@ We'll add it to the top of our chat area in `chat.html`: ``` The ideal way for this to work would be for you to be able to change the value -of the field, and have it save. We can do exactly that using Lift's `ajaxText` -helper in `Chat.scala`: +of the field and have it save once the cursor leaves the field (i.e., on blur). +We can do exactly that using Lift's `ajaxText` helper in `Chat.scala`: ```scala ... diff --git a/docs/getting-started-tutorial/9-comet-actors.adoc b/docs/getting-started-tutorial/9-comet-actors.adoc index ae4a4bbd7c..4d0fce4ba0 100644 --- a/docs/getting-started-tutorial/9-comet-actors.adoc +++ b/docs/getting-started-tutorial/9-comet-actors.adoc @@ -18,3 +18,5 @@ anything. Our first move will be to change how exactly we handle binding chat messages. First, we'll do a quick conversion that puts everything in a `CometActor`, but doesn't add any additional functionality. Instead of calling + +TODO Apparently I stopped midsentence here, so there's more to fill in ;) From 46606aa946a5cf8fb28e8d890af5c77016233cb9 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 3 Mar 2018 10:05:42 -0500 Subject: [PATCH 1727/1949] Fill in code block filenames where relevant This sets us up for some nice preprocessing later to fill in context around our example blocks. --- .../2-the-lift-menu-system.adoc | 2 +- .../3-adding-snippet-bindings.adoc | 6 +++--- .../4-css-selector-transforms.adoc | 10 +++++----- docs/getting-started-tutorial/5-basic-forms.adoc | 8 ++++---- docs/getting-started-tutorial/6-adding-usernames.adoc | 10 +++++----- .../7-using-actors-for-chat.adoc | 10 +++++----- .../8-customizable-usernames.adoc | 6 +++--- 7 files changed, 26 insertions(+), 26 deletions(-) diff --git a/docs/getting-started-tutorial/2-the-lift-menu-system.adoc b/docs/getting-started-tutorial/2-the-lift-menu-system.adoc index dc28ac1559..373db1f283 100644 --- a/docs/getting-started-tutorial/2-the-lift-menu-system.adoc +++ b/docs/getting-started-tutorial/2-the-lift-menu-system.adoc @@ -16,7 +16,7 @@ more complicated. All you have to do for the chat page is add a line to your `SiteMap.scala` that names the page and points to the file in the `webapp` directory: -``` +```src/scala/bootstrap/liftweb/Boot.scala ... Menu.i("Chat") / "chat" ... diff --git a/docs/getting-started-tutorial/3-adding-snippet-bindings.adoc b/docs/getting-started-tutorial/3-adding-snippet-bindings.adoc index 7dc67ac51f..00334f75c9 100644 --- a/docs/getting-started-tutorial/3-adding-snippet-bindings.adoc +++ b/docs/getting-started-tutorial/3-adding-snippet-bindings.adoc @@ -28,13 +28,13 @@ Let's look at our chat app specifically. We're going to bind two things: the list of chat messages, and the text input that lets us actually chat. To the `ol` that contains the chat messages, we add: -``` +```html:src/main/webapp/index.html
                ``` And to the input form: -``` +```html:src/main/webapp/index.html ``` @@ -46,7 +46,7 @@ We'll write a very basic version that just passes through the contents of the list and form unchanged, and then in the next section we'll start adding some behavior. In `src/main/scala/code/snippet/Chat.scala`, add: -``` +```scala:src/main/scala/code/snippet/Chat.scala package code package snippet diff --git a/docs/getting-started-tutorial/4-css-selector-transforms.adoc b/docs/getting-started-tutorial/4-css-selector-transforms.adoc index f7bd576e55..2d7b7ee4bb 100644 --- a/docs/getting-started-tutorial/4-css-selector-transforms.adoc +++ b/docs/getting-started-tutorial/4-css-selector-transforms.adoc @@ -20,7 +20,7 @@ the list changing. First, we'll define a variable to hold the messages: -``` +```scala:src/main/scala/code/snippet/Chat.scala ... object Chat { var messageEntries = List[String]() @@ -31,7 +31,7 @@ object Chat { Then, we can change the definition of the `messages` method to bind the contents of the message list: -``` +```scala:src/main/scala/code/snippet/Chat.scala ... import net.liftweb.util.Helpers._ @@ -76,7 +76,7 @@ Once you see the success message, point your browser to there are no message entries. To fix this, we're going to add a chat message every time we render the message list: -``` +```scala:src/main/scala/code/snippet/Chat.scala ... def messages = { messageEntries :+= "It is now " + formattedTimeNow @@ -133,7 +133,7 @@ without getting nasty in our HTML? Lift lets us tag the extra elements with a class `clearable`: -``` +```html:src/main/webapp/index.html ...
              1. Hi!
              2. Oh, hey there.
              3. @@ -146,7 +146,7 @@ Then, in our snippet, we can use a special transform called `ClearClearable`, which will remove all of the tagged elements before we start transforming the template: -``` +```scala:src/main/scala/code/snippet/Chat.scala ... def messages = { messageEntries :+= "It is now " + formattedTimeNow diff --git a/docs/getting-started-tutorial/5-basic-forms.adoc b/docs/getting-started-tutorial/5-basic-forms.adoc index a2b10c8304..0cb9dcaaed 100644 --- a/docs/getting-started-tutorial/5-basic-forms.adoc +++ b/docs/getting-started-tutorial/5-basic-forms.adoc @@ -25,7 +25,7 @@ connection. Let's look at a simple example with our chat application. Currently our form looks like this: -``` +```html @@ -35,7 +35,7 @@ looks like this: Our `sendMessage` snippet looks like this: -``` +```scala ... def sendMessage(contents: NodeSeq) = contents ... @@ -45,7 +45,7 @@ We want to bind two things above. The first is the text field, which we want to bind so that we can get a message from the user, and the second is the submit button, so that we can process the new message. Here's how we can do that: -``` +```scala:src/main/scala/code/snippet/Chat.scala ... import net.liftweb.http.SHtml @@ -78,7 +78,7 @@ value of `message` to the existing message entries list. Before continuing, let's change the `messages` snippet so it doesn't keep adding a new message on each page load: -``` +```scala:src/main/scala/code/snippet/Chat.scala ... def messages = { ClearClearable & diff --git a/docs/getting-started-tutorial/6-adding-usernames.adoc b/docs/getting-started-tutorial/6-adding-usernames.adoc index 9935bd523b..2289cfe850 100644 --- a/docs/getting-started-tutorial/6-adding-usernames.adoc +++ b/docs/getting-started-tutorial/6-adding-usernames.adoc @@ -13,7 +13,7 @@ We're about to add another use case to our chat system: The first thing we'll do is change the HTML to look like we want it to. Let's add the username: -``` +```html:src/main/webapp/index.html ...
              4. Antonio Hi! @@ -40,7 +40,7 @@ thrown away, as will the associated `SessionVar` values and related data. For now, let's look at adding the `SessionVar` to the `Chat` snippet: -``` +```scala:src/main/scala/code/snippet/Chat.scala ... object username extends SessionVar[String]("username") @@ -51,7 +51,7 @@ object Chat { Here, we create a new `SessionVar`, whose default value will be “username” if it is accessed without having been set. We can change that to be random: -``` +```scala:src/main/scala/code/snippet/Chat.scala object username extends SessionVar[String]("User " + randomString(5)) ``` @@ -62,7 +62,7 @@ user session has a (reasonably) unique username. Now, we need to store usernames alongside messages. Let's do that by making the messageEntries list contain a case class instance instead of a simple `String`: -```scala +```scala:src/main/scala/code/snippet/Chat.scala ... case class ChatMessage(poster: String, body: String) // <1> class Chat { @@ -94,7 +94,7 @@ class Chat { Now let's update the binding of the `sendMessage` form to deal with the new `ChatMessage` class: -```scala +```scala:src/main/scala/code/snippet/Chat.scala def sendMessage = { var message = ChatMessage("", "") // <1> diff --git a/docs/getting-started-tutorial/7-using-actors-for-chat.adoc b/docs/getting-started-tutorial/7-using-actors-for-chat.adoc index fd4e2f17e8..35d3ef4004 100644 --- a/docs/getting-started-tutorial/7-using-actors-for-chat.adoc +++ b/docs/getting-started-tutorial/7-using-actors-for-chat.adoc @@ -22,7 +22,7 @@ http://aka.io[Akka], but they're only necessary in cases where you need more flexibility or fault tolerance. We'll stick to the easy stuff, starting with a new file at `src/main/scala/code/actor/ChatActor.scala`: -``` +```scala:src/main/scala/code/actor/ChatActor.scala package code package actor @@ -55,7 +55,7 @@ To ask the actor to add a message, we'll send it the `MessagePosted` message using the `!` operator. Here's how we can update our code in the `Chat` snippet: -``` +```scala:src/main/scala/code/snippet/Chat.scala ... import actor._ ... @@ -80,7 +80,7 @@ isn't useful if we can't get them back out! To retrieve messages, we can add a new message for the `ChatActor`: -``` +```scala:src/main/scala/code/actor/ChatActor.scala ... case class MessagePosted(message: ChatMessage) case object GetMessages @@ -89,7 +89,7 @@ case object GetMessages And a handler for it: -``` +```scala:src/main/scala/code/actor/ChatActor.scala ... def messageHandler = { ... @@ -109,7 +109,7 @@ with it. To wait for a reply, we have to use the `!?` operator instead. We do this when listing messages by updating the `Chat` snippet: -``` +```scala:src/main/scala/code/snippet/Chat.scala ... def messages = { val messageEntries = Box.asA[List[ChatMessage]](ChatActor !? GetMessages) openOr List() diff --git a/docs/getting-started-tutorial/8-customizable-usernames.adoc b/docs/getting-started-tutorial/8-customizable-usernames.adoc index cbf62441c0..76a2cab454 100644 --- a/docs/getting-started-tutorial/8-customizable-usernames.adoc +++ b/docs/getting-started-tutorial/8-customizable-usernames.adoc @@ -13,7 +13,7 @@ Let's deal with the next use case: What we really want is a text box on the client that will let us edit the name. We'll add it to the top of our chat area in `chat.html`: -```html +```html:src/main/webapp/index.html ...
                @@ -27,7 +27,7 @@ The ideal way for this to work would be for you to be able to change the value of the field and have it save once the cursor leaves the field (i.e., on blur). We can do exactly that using Lift's `ajaxText` helper in `Chat.scala`: -```scala +```scala:src/main/scala/code/snippet/Chat.scala ... def nameField = { "input" #> SHtml.ajaxText(username.is, username.set _) @@ -45,7 +45,7 @@ a change occurs on the client, and we hook it up directly to the ``SessionVar``' However, maybe we want to provide some feedback to the user to let them know the name has been updated. We can get a little more detailed: -```scala +```scala:src/main/scala/code/snippet/Chat.scala ... def nameField = { "input" #> SHtml.ajaxText(username.is, { updatedUsername: String => From bbeb966b7cf26ef7a06dfba48e1e701a29019e40 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 3 Mar 2018 10:27:20 -0500 Subject: [PATCH 1728/1949] Reintroduce pass-through deprecated net.liftweb.util.BCrypt We can't nuke that BCrypt class as it is public API in the net.liftweb.util package. We introduce a deprecated class that passes through both docs and public methods calls to org.mindrot.jbcrypt.BCrypt. --- build.sbt | 3 +- .../main/java/net/liftweb/util/BCrypt.java | 87 +++++++++++++++++++ 2 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 core/util/src/main/java/net/liftweb/util/BCrypt.java diff --git a/build.sbt b/build.sbt index f0243e1d67..c155f36141 100644 --- a/build.sbt +++ b/build.sbt @@ -125,7 +125,8 @@ lazy val util = javamail, log4j, htmlparser, - xerces + xerces, + jbcrypt ) ) diff --git a/core/util/src/main/java/net/liftweb/util/BCrypt.java b/core/util/src/main/java/net/liftweb/util/BCrypt.java new file mode 100644 index 0000000000..6eb74f63ad --- /dev/null +++ b/core/util/src/main/java/net/liftweb/util/BCrypt.java @@ -0,0 +1,87 @@ +/* + * Copyright 2007-2011 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. + */ + +// --------------- +// This is a derivative work of jBCrypt, distributed under BSD licence +// and copyrighted as follows: +// +// Copyright (c) 2006 Damien Miller +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +package net.liftweb.util; + +import java.io.UnsupportedEncodingException; + +import java.security.SecureRandom; + +/** + * This class is a passthrough to org.mindrot.jbcrypt.BCrypt, provided for + * backwards compatibility as we kept a copy in the Lift codebase before it was + * available for package dependencies. Prefer using org.mindrot.jbcrypt.BCrypt. + */ +@Deprecated +public class BCrypt { + /** + * @see org.mindrot.jbcrypt.BCrypt#hashpw(String,String) + */ + @Deprecated + public static String hashpw(String password, String salt) { + return org.mindrot.jbcrypt.BCrypt.hashpw(password, salt); + } + + /** + * @see org.mindrot.jbcrypt.BCrypt#gensalt(int,SecureRandom) + */ + @Deprecated + public static String gensalt(int log_rounds, SecureRandom random) { + return org.mindrot.jbcrypt.BCrypt.gensalt(log_rounds, random); + } + + /** + * @see org.mindrot.jbcrypt.BCrypt#gensalt(int) + */ + @Deprecated + public static String gensalt(int log_rounds) { + return org.mindrot.jbcrypt.BCrypt.gensalt(log_rounds); + } + + /** + * @see org.mindrot.jbcrypt.BCrypt#gensalt() + */ + @Deprecated + public static String gensalt() { + return org.mindrot.jbcrypt.BCrypt.gensalt(); + } + + /** + * @see org.mindrot.jbcrypt.BCrypt#checkpw(String,String) + */ + @Deprecated + public static boolean checkpw(String plaintext, String hashed) { + return org.mindrot.jbcrypt.BCrypt.checkpw(plaintext, hashed); + } +} From 3d1435dc067bfac91a4139a44a56e7feafcf6fb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20H=C3=A4rk=C3=B6nen?= Date: Mon, 5 Mar 2018 19:09:38 +0200 Subject: [PATCH 1729/1949] Changed the variable name. Also updated the scaladoc to be more precise about where this variable effects. --- .../liftweb/builtin/snippet/LazyLoad.scala | 2 +- .../scala/net/liftweb/http/LiftRules.scala | 19 +++++++++++++------ .../main/scala/net/liftweb/http/package.scala | 2 +- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/LazyLoad.scala b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/LazyLoad.scala index d46968a940..c9e3bad053 100644 --- a/web/webkit/src/main/scala/net/liftweb/builtin/snippet/LazyLoad.scala +++ b/web/webkit/src/main/scala/net/liftweb/builtin/snippet/LazyLoad.scala @@ -72,7 +72,7 @@ object LazyLoad extends DispatchSnippet { renderedTemplate } } openOr { -
                Loading
                +
                Loading
                } ) } diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala index 73796fcf9a..4c319f09ec 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala @@ -1335,13 +1335,20 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable { } /** - * The root path for assets folder. For example to store - * asset files under 'myassets' folder, use value '/myassets/'. Assets - * under this configurable path follow conventions, for example - * the default lazy loading spinner ('ajax-loader.gif') is under - * 'images' folder. + * The root path for the assets folder. + * + * This applies also to the URL domain, appended after the deployed context path. + * In app developer's work area this folder resides under the 'webapp' folder. + * + * For example to store asset files under 'myassets' folder, + * use value '/myassets/'. Assets under this configurable path follow + * conventions, for example the default lazy loading spinner ('ajax-loader.gif') + * is under 'images' folder. + * + * Thus the URL to the assets folder would be: + * 'http:////myassets/' */ - @volatile var assetPath: String = "/" + @volatile var assetRootPath: String = "/" /** * Contains the URI path under which all built-in Lift-handled requests are diff --git a/web/webkit/src/main/scala/net/liftweb/http/package.scala b/web/webkit/src/main/scala/net/liftweb/http/package.scala index d75182e405..2ea1e7d3e8 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/package.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/package.scala @@ -55,7 +55,7 @@ package object http { // Actually complete the render once the future is fulfilled. asyncResolveProvider.resolveAsync(concreteResolvable, resolvedResult => deferredRender(resolvedResult)) -
                Loading
                +
                Loading
                } openOr { Comment("FIX"+"ME: Asynchronous rendering failed for unknown reason.") } From de91b3aa1f66ae38b0ac0d3fda129d154ad75072 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Wed, 11 Apr 2018 23:48:35 -0400 Subject: [PATCH 1730/1949] Correct logback version typo in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4101061718..dbcda11381 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ libraryDependencies ++= { val liftVersion = "3.2.0" Seq( "net.liftweb" %% "lift-webkit" % liftVersion % "compile", - "ch.qos.logback" % "logback-classic" % "1.2.5" + "ch.qos.logback" % "logback-classic" % "1.2.3" ) } ``` From 47d4e06edcbdca97ccdd93962f9f8fed9300e131 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 12 May 2018 09:41:09 -0400 Subject: [PATCH 1731/1949] Fix some dangling whitespace --- .../main/scala/net/liftweb/sitemap/Loc.scala | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/sitemap/Loc.scala b/web/webkit/src/main/scala/net/liftweb/sitemap/Loc.scala index b8cb3e5996..db5b283841 100644 --- a/web/webkit/src/main/scala/net/liftweb/sitemap/Loc.scala +++ b/web/webkit/src/main/scala/net/liftweb/sitemap/Loc.scala @@ -57,7 +57,7 @@ trait Loc[T] { * By default, this lazy val looks for the MenuCssClass LocParam and * uses it. */ - protected lazy val cacheCssClassForMenuItem: Box[() => String] = + protected lazy val cacheCssClassForMenuItem: Box[() => String] = allParams.flatMap { case a: Loc.MenuCssClass => List(a) case _ => Nil @@ -78,7 +78,7 @@ trait Loc[T] { def defaultValue: Box[T] - + /** * The value of the Loc based on params (either Loc.Value or Loc.CalcValue) */ @@ -90,7 +90,7 @@ trait Loc[T] { v.asInstanceOf[T] } } - + private lazy val calcValue: Box[() => Box[T]] = { params.collectFirst { case Loc.CalcValue(f: Function0[_]) => @@ -102,13 +102,13 @@ trait Loc[T] { * Calculate the Query parameters */ def queryParameters(what: Box[T]): List[(String, String)] = - addlQueryParams.flatMap(_()) ::: + addlQueryParams.flatMap(_()) ::: calcQueryParams.flatMap(_(what)) - protected def appendQueryParams(what: T)(nodeSeq: NodeSeq): NodeSeq = + protected def appendQueryParams(what: T)(nodeSeq: NodeSeq): NodeSeq = Text(appendQueryParameters(nodeSeq.text, Full(what))) - protected def appendQueryParameters(in: String, what: Box[T]) = + protected def appendQueryParameters(in: String, what: Box[T]) = Helpers.appendQueryParameters(in, queryParameters(what)) private lazy val addlQueryParams: List[() => List[(String, String)]] = @@ -127,16 +127,16 @@ trait Loc[T] { def params: List[Loc.LocParam[T]] - def allParams: List[Loc.AnyLocParam] = - (params.asInstanceOf[List[Loc.AnyLocParam]]) ::: + def allParams: List[Loc.AnyLocParam] = + (params.asInstanceOf[List[Loc.AnyLocParam]]) ::: parentParams ::: siteMap.globalParams - private def parentParams: List[Loc.AnyLocParam] = + private def parentParams: List[Loc.AnyLocParam] = _menu match { case null => Nil case menu => menu._parent match { - case Full(parentMenu: Menu) => + case Full(parentMenu: Menu) => if (!params.collect{case i: Loc.UseParentParams => true}.isEmpty) { parentMenu.loc.allParams.asInstanceOf[List[Loc.LocParam[Any]]] } else { @@ -156,7 +156,7 @@ trait Loc[T] { def siteMap: SiteMap = _menu.siteMap - def createDefaultLink: Option[NodeSeq] = + def createDefaultLink: Option[NodeSeq] = currentValue.flatMap(p => link.createLink(p)).toOption. map(ns => Text(appendQueryParameters(ns.text, currentValue))) @@ -204,7 +204,7 @@ trait Loc[T] { * Is the Loc marked as Stateless (this will force rendering of * the page into stateless mode) */ - def stateless_? : Boolean = + def stateless_? : Boolean = if (Props.devMode) (calcStateless() || reqCalcStateless()) else (_frozenStateless || reqCalcStateless()) @@ -463,7 +463,7 @@ trait Loc[T] { trait ConvertableLoc[T] { self: Loc[T] => - + /** * Converts the String to T that can then be sent to * the Loc in createLink @@ -630,13 +630,13 @@ object Loc { * the groups can be specified and recalled at the top level */ case class LocGroup(group: String*) extends AnyLocParam - + /** * Calculate the value for the Loc. This is useful for parameterized * menus. It allows you to calculate the value of the Loc. */ case class CalcValue[T](func: () => Box[T]) extends LocParam[T] - + /** * The value of Loc */ @@ -685,7 +685,7 @@ object Loc { */ class Snippet(val name: String, _func: => NodeSeq => NodeSeq) extends ValueSnippets[Any] with AnyLocParam { /** - * The NodeSeq => NodeSeq function + * The NodeSeq => NodeSeq function */ def func: NodeSeq => NodeSeq = _func @@ -959,4 +959,3 @@ case class MenuItem(text: NodeSeq, uri: NodeSeq, kids: Seq[MenuItem], else this :: kids.toList.flatMap(_.breadCrumbs) } } - From 52b2e91689601992b79716e2edfa7b669759b26a Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 12 May 2018 10:09:41 -0400 Subject: [PATCH 1732/1949] Avoid surprise NPEs on param / sitemap methods It's possible to get a surprise NPE if you try to read the currentValue of a loc that's not the current loc instance. This is because the Loc you're trying to read may not have a SiteMap assigned to it, which will cause explosions in param calculation. We now guard against this in various high-profile public API methods so that we minimize the footprint of surprise NPEs. --- .../main/scala/net/liftweb/sitemap/Loc.scala | 28 ++++++++++++++----- .../scala/net/liftweb/sitemap/LocSpec.scala | 11 +++++++- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/web/webkit/src/main/scala/net/liftweb/sitemap/Loc.scala b/web/webkit/src/main/scala/net/liftweb/sitemap/Loc.scala index db5b283841..3680b7e7b7 100644 --- a/web/webkit/src/main/scala/net/liftweb/sitemap/Loc.scala +++ b/web/webkit/src/main/scala/net/liftweb/sitemap/Loc.scala @@ -127,10 +127,16 @@ trait Loc[T] { def params: List[Loc.LocParam[T]] - def allParams: List[Loc.AnyLocParam] = - (params.asInstanceOf[List[Loc.AnyLocParam]]) ::: - parentParams ::: - siteMap.globalParams + def allParams: List[Loc.AnyLocParam] = { + if (_menu == null) { + // Params are impossible without a menu + Nil + } else { + (params.asInstanceOf[List[Loc.AnyLocParam]]) ::: + parentParams ::: + siteMap.globalParams + } + } private def parentParams: List[Loc.AnyLocParam] = _menu match { @@ -154,7 +160,10 @@ trait Loc[T] { def hideIfNoKids_? = placeHolder_? || _hideIfNoKids_? - def siteMap: SiteMap = _menu.siteMap + // This implementation is less than ideal, but changing the type would + // have been a breaking API change. This at least avoids this method + // throwing an NPE + def siteMap: SiteMap = Option(_menu).map(_.siteMap).getOrElse(null) def createDefaultLink: Option[NodeSeq] = currentValue.flatMap(p => link.createLink(p)).toOption. @@ -401,7 +410,13 @@ trait Loc[T] { ) } - def breadCrumbs: List[Loc[_]] = _menu.breadCrumbs ::: List(this) + def breadCrumbs: List[Loc[_]] = { + if (_menu != null) { + _menu.breadCrumbs ::: List(this) + } else { + List(this) + } + } def buildKidMenuItems(kids: Seq[Menu]): List[MenuItem] = { kids.toList.flatMap(_.loc.buildItem(Nil, false, false)) ::: supplementalKidMenuItems @@ -513,7 +528,6 @@ object Loc { init() } - /** * Algebraic data type for parameters that modify handling of a Loc * in a SiteMap diff --git a/web/webkit/src/test/scala/net/liftweb/sitemap/LocSpec.scala b/web/webkit/src/test/scala/net/liftweb/sitemap/LocSpec.scala index 0654d8515e..e1294f70fc 100644 --- a/web/webkit/src/test/scala/net/liftweb/sitemap/LocSpec.scala +++ b/web/webkit/src/test/scala/net/liftweb/sitemap/LocSpec.scala @@ -91,6 +91,15 @@ object LocSpec extends Specification { } } } + + "not throw Exceptions on param methods before SiteMap assignment" in { + val testMenu = Menu.param[Param]("Test", "Test", s => Empty, p => "bacon") / "foo" / "bar" >> Loc.MatchWithoutCurrentValue + val testLoc = testMenu.toLoc + + testLoc.allParams must not(throwA[Exception]) + testLoc.currentValue must not(throwA[Exception]) + testLoc.siteMap must not(throwA[Exception]) + testLoc.breadCrumbs must not(throwA[Exception]) + } } } - From ae1d2785e496336beb0c748551258ca8e6f3c98b Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 2 Jun 2018 10:24:40 -0400 Subject: [PATCH 1733/1949] Add the java-version file for folks using jenv --- .java-version | 1 + 1 file changed, 1 insertion(+) create mode 100644 .java-version diff --git a/.java-version b/.java-version new file mode 100644 index 0000000000..6259340971 --- /dev/null +++ b/.java-version @@ -0,0 +1 @@ +1.8 From a23c97febe7a5de2a0d3b64b65deb6fca687639f Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 2 Jun 2018 10:28:11 -0400 Subject: [PATCH 1734/1949] Drop Lift 3.0 series to security fixes only --- SUPPORT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SUPPORT.md b/SUPPORT.md index 9f6f1d6281..7da6ddbb73 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -34,7 +34,7 @@ As of January 27th, 2018 the current support status looks like this: |< 2.5 | | | Not supported | |2.5 |June 2013 | 2.5.4 (Jan 2016) | Security fixes (until Lift 4.0) | |2.6 |January 2015 | 2.6.3 (Jan 2016) | Security fixes (until Lift 5.0) | -|3.0 |November 2016 | 3.0.2 (Sep 2016) | Minor fixes (until May 2018) | +|3.0 |November 2016 | 3.0.2 (Sep 2017) | Security fixes (until Lift 6.0) | |3.1 |July 2017 | 3.1.1 (Sep 2017) | Minor fixes (until Jan 2019) | |3.2 |January 2018 | 3.2.0 (Jan 2018) | Minor fixes (until July 2019) | From 81ad1df8704c8600e89aef2af1341cdbeb8d168a Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 2 Jun 2018 10:28:32 -0400 Subject: [PATCH 1735/1949] Note Lift 3.3 planned release date --- SUPPORT.md | 1 + 1 file changed, 1 insertion(+) diff --git a/SUPPORT.md b/SUPPORT.md index 7da6ddbb73..1376fbf550 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -37,6 +37,7 @@ As of January 27th, 2018 the current support status looks like this: |3.0 |November 2016 | 3.0.2 (Sep 2017) | Security fixes (until Lift 6.0) | |3.1 |July 2017 | 3.1.1 (Sep 2017) | Minor fixes (until Jan 2019) | |3.2 |January 2018 | 3.2.0 (Jan 2018) | Minor fixes (until July 2019) | +|3.3 |July 2018 (est) | Pre-release | Active development | Per this support policy please ensure you're using a currently supported version of Lift before: From 9aca917e0964f0012ce6167ad5f00ac5a7d65890 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 2 Jun 2018 10:36:12 -0400 Subject: [PATCH 1736/1949] Correct as-of date in support policy --- SUPPORT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SUPPORT.md b/SUPPORT.md index 1376fbf550..7faec210dc 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -27,7 +27,7 @@ of Lift: * Major releases aren't on a regular schedule, but we will announce and update this policy with actual dates when major releases become planned. -As of January 27th, 2018 the current support status looks like this: +As of June 2nd, 2018 the current support status looks like this: |Version |Initial Release | Last Release | Current Support Status | |---------|-----------------|------------------|---------------------------------| From 486ed429dabf4930fbf66e0fefaaf5d8a1ecf1bb Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sun, 3 Jun 2018 09:24:15 -0400 Subject: [PATCH 1737/1949] Update API doc link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dbcda11381..9106a3fb2b 100644 --- a/README.md +++ b/README.md @@ -196,7 +196,7 @@ The Lift wiki is hosted on Assembla and can be found at [http://www.assembla.com ### ScalaDocs -The ScalaDocs for each release of Lift, in additional to the actual JARs, are available on the Liftweb.net site. You can access the source code–based documentation for releases via the site's homepage or by navigating directly to the URL for the specific release. For instance, the Lift 3.0 release can be accessed at [http://liftweb.net/api/31/api/](http://liftweb.net/api/31/api/). +The ScalaDocs for each release of Lift, in additional to the actual JARs, are available on the Liftweb.net site. You can access the source code–based documentation for releases via the site's homepage or by navigating directly to the URL for the specific release. For instance, the Lift 3.2 release can be accessed at [http://liftweb.net/api/32/api/](http://liftweb.net/api/32/api/). ## License From 0d79f9a123f48d3e468b2ab33cd95efaf4c410b7 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sun, 3 Jun 2018 09:25:43 -0400 Subject: [PATCH 1738/1949] Bump Scala 2.12 release to 2.12.6 --- build.sbt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index 27e6cf985c..22a26be1bd 100644 --- a/build.sbt +++ b/build.sbt @@ -7,8 +7,8 @@ homepage in ThisBuild := Some(url("http://www.liftweb.net")) licenses in ThisBuild += ("Apache License, Version 2.0", url("http://www.apache.org/licenses/LICENSE-2.0.txt")) startYear in ThisBuild := Some(2006) organizationName in ThisBuild := "WorldWide Conferencing, LLC" -scalaVersion in ThisBuild := "2.12.4" -crossScalaVersions in ThisBuild := Seq("2.12.4", "2.11.11") +scalaVersion in ThisBuild := "2.12.6" +crossScalaVersions in ThisBuild := Seq("2.12.6", "2.11.11") libraryDependencies in ThisBuild ++= Seq(specs2, specs2Matchers, specs2Mock, scalacheck, scalatest) From d60e85543356e2b4aee97cd38ecfcd22888aee12 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sun, 3 Jun 2018 09:31:38 -0400 Subject: [PATCH 1739/1949] Commons-codec -> 1.11, commons-fileupload -> 1.3.3 --- project/Dependencies.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 33470a68fc..a329db070f 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -29,8 +29,8 @@ object Dependencies { // Compile scope: // Scope available in all classpath, transitive by default. - lazy val commons_codec = "commons-codec" % "commons-codec" % "1.10" - lazy val commons_fileupload = "commons-fileupload" % "commons-fileupload" % "1.3.1" + lazy val commons_codec = "commons-codec" % "commons-codec" % "1.11" + lazy val commons_fileupload = "commons-fileupload" % "commons-fileupload" % "1.3.3" lazy val commons_httpclient = "commons-httpclient" % "commons-httpclient" % "3.1" lazy val javamail = "javax.mail" % "mail" % "1.4.7" lazy val jbcrypt = "org.mindrot" % "jbcrypt" % "0.4" From 5daa8f4323450d8d49e524fcf88a37f86130d05c Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sun, 3 Jun 2018 09:32:08 -0400 Subject: [PATCH 1740/1949] Bump joda-time to 2.10 --- project/Dependencies.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index a329db070f..04ef819ed1 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -34,7 +34,7 @@ object Dependencies { lazy val commons_httpclient = "commons-httpclient" % "commons-httpclient" % "3.1" lazy val javamail = "javax.mail" % "mail" % "1.4.7" lazy val jbcrypt = "org.mindrot" % "jbcrypt" % "0.4" - lazy val joda_time = "joda-time" % "joda-time" % "2.9.2" + lazy val joda_time = "joda-time" % "joda-time" % "2.10" lazy val joda_convert = "org.joda" % "joda-convert" % "1.8.1" lazy val htmlparser = "nu.validator.htmlparser" % "htmlparser" % "1.4" lazy val mongo_java_driver = "org.mongodb" % "mongodb-driver" % "3.6.3" From 24727e3f3cb1aaa02a9824ec7e90886d173311d9 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sun, 3 Jun 2018 09:33:14 -0400 Subject: [PATCH 1741/1949] Bump joda-convert to 2.0.2 --- project/Dependencies.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 04ef819ed1..bbbfb046bc 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -35,7 +35,7 @@ object Dependencies { lazy val javamail = "javax.mail" % "mail" % "1.4.7" lazy val jbcrypt = "org.mindrot" % "jbcrypt" % "0.4" lazy val joda_time = "joda-time" % "joda-time" % "2.10" - lazy val joda_convert = "org.joda" % "joda-convert" % "1.8.1" + lazy val joda_convert = "org.joda" % "joda-convert" % "2.0.2" lazy val htmlparser = "nu.validator.htmlparser" % "htmlparser" % "1.4" lazy val mongo_java_driver = "org.mongodb" % "mongodb-driver" % "3.6.3" lazy val mongo_java_driver_async = "org.mongodb" % "mongodb-driver-async" % "3.6.3" From d2f8044d836df4b66fb11e048de79bff03129a47 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sun, 3 Jun 2018 09:37:26 -0400 Subject: [PATCH 1742/1949] Bump htmlparser to 1.4.11 --- project/Dependencies.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index bbbfb046bc..beea0f84e6 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -36,7 +36,7 @@ object Dependencies { lazy val jbcrypt = "org.mindrot" % "jbcrypt" % "0.4" lazy val joda_time = "joda-time" % "joda-time" % "2.10" lazy val joda_convert = "org.joda" % "joda-convert" % "2.0.2" - lazy val htmlparser = "nu.validator.htmlparser" % "htmlparser" % "1.4" + lazy val htmlparser = "nu.validator" % "htmlparser" % "1.4.11" lazy val mongo_java_driver = "org.mongodb" % "mongodb-driver" % "3.6.3" lazy val mongo_java_driver_async = "org.mongodb" % "mongodb-driver-async" % "3.6.3" lazy val paranamer = "com.thoughtworks.paranamer" % "paranamer" % "2.8" From cd2a9ca96eeba4413c76b9bf5b12a8ff04d27b2f Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sun, 3 Jun 2018 09:39:20 -0400 Subject: [PATCH 1743/1949] Bump mongodb drivers to 3.7.1 --- project/Dependencies.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index beea0f84e6..094f88e93e 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -37,8 +37,8 @@ object Dependencies { lazy val joda_time = "joda-time" % "joda-time" % "2.10" lazy val joda_convert = "org.joda" % "joda-convert" % "2.0.2" lazy val htmlparser = "nu.validator" % "htmlparser" % "1.4.11" - lazy val mongo_java_driver = "org.mongodb" % "mongodb-driver" % "3.6.3" - lazy val mongo_java_driver_async = "org.mongodb" % "mongodb-driver-async" % "3.6.3" + lazy val mongo_java_driver = "org.mongodb" % "mongodb-driver" % "3.7.1" + lazy val mongo_java_driver_async = "org.mongodb" % "mongodb-driver-async" % "3.7.1" lazy val paranamer = "com.thoughtworks.paranamer" % "paranamer" % "2.8" lazy val scalajpa = "org.scala-libs" % "scalajpa" % "1.5" cross CVMappingAll lazy val scalap: ModuleMap = "org.scala-lang" % "scalap" % _ From f83501b3a773c97575a9ec79a8c23b09e2616671 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sun, 3 Jun 2018 09:40:48 -0400 Subject: [PATCH 1744/1949] Bump scalaz-core to 7.2.24 --- project/Dependencies.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 094f88e93e..8b03b39c0f 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -43,7 +43,7 @@ object Dependencies { lazy val scalajpa = "org.scala-libs" % "scalajpa" % "1.5" cross CVMappingAll lazy val scalap: ModuleMap = "org.scala-lang" % "scalap" % _ lazy val scala_compiler: ModuleMap = "org.scala-lang" % "scala-compiler" % _ - lazy val scalaz7_core = "org.scalaz" % "scalaz-core" % "7.2.7" cross CVMappingAll + lazy val scalaz7_core = "org.scalaz" % "scalaz-core" % "7.2.24" cross CVMappingAll lazy val squeryl = "org.squeryl" % "squeryl" % "0.9.5-7" cross CVMappingAll lazy val slf4j_api = "org.slf4j" % "slf4j-api" % slf4jVersion lazy val scala_xml = "org.scala-lang.modules" %% "scala-xml" % "1.0.5" From dabcb11e53c5f4a3d01b042d74d7fe9bf43d3ed0 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sun, 3 Jun 2018 09:43:06 -0400 Subject: [PATCH 1745/1949] Bump slf4j version to 1.7.25 --- project/Dependencies.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 8b03b39c0f..eacc64ce76 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -25,7 +25,7 @@ object Dependencies { lazy val CVMappingAll = crossMapped("2.11.7" -> "2.11") - lazy val slf4jVersion = "1.7.2" + lazy val slf4jVersion = "1.7.25" // Compile scope: // Scope available in all classpath, transitive by default. From df0070eae2ef8c17aef7d719e866ff9fdfaead48 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sun, 3 Jun 2018 09:45:09 -0400 Subject: [PATCH 1746/1949] Bump scala-xml to 1.0.6 This is in line with the version expected by scalac 2.12.6 --- project/Dependencies.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index eacc64ce76..c69fcd872c 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -46,7 +46,7 @@ object Dependencies { lazy val scalaz7_core = "org.scalaz" % "scalaz-core" % "7.2.24" cross CVMappingAll lazy val squeryl = "org.squeryl" % "squeryl" % "0.9.5-7" cross CVMappingAll lazy val slf4j_api = "org.slf4j" % "slf4j-api" % slf4jVersion - lazy val scala_xml = "org.scala-lang.modules" %% "scala-xml" % "1.0.5" + lazy val scala_xml = "org.scala-lang.modules" %% "scala-xml" % "1.0.6" lazy val rhino = "org.mozilla" % "rhino" % "1.7.7.1" lazy val scala_parser = "org.scala-lang.modules" %% "scala-parser-combinators" % "1.0.4" lazy val xerces = "xerces" % "xercesImpl" % "2.11.0" From 8ccfb5f346a4a0bdc593bd9272ca0cd6867ee2df Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sun, 3 Jun 2018 09:46:50 -0400 Subject: [PATCH 1747/1949] Bump rhino to 1.7.10 --- project/Dependencies.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index c69fcd872c..a22cf2998c 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -47,7 +47,7 @@ object Dependencies { lazy val squeryl = "org.squeryl" % "squeryl" % "0.9.5-7" cross CVMappingAll lazy val slf4j_api = "org.slf4j" % "slf4j-api" % slf4jVersion lazy val scala_xml = "org.scala-lang.modules" %% "scala-xml" % "1.0.6" - lazy val rhino = "org.mozilla" % "rhino" % "1.7.7.1" + lazy val rhino = "org.mozilla" % "rhino" % "1.7.10" lazy val scala_parser = "org.scala-lang.modules" %% "scala-parser-combinators" % "1.0.4" lazy val xerces = "xerces" % "xercesImpl" % "2.11.0" From 923f3033a86cb77c08f8afe26918a7dda75db146 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sun, 3 Jun 2018 09:48:06 -0400 Subject: [PATCH 1748/1949] Bump parser combinators to 1.1.0 --- project/Dependencies.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index a22cf2998c..491526b289 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -48,7 +48,7 @@ object Dependencies { lazy val slf4j_api = "org.slf4j" % "slf4j-api" % slf4jVersion lazy val scala_xml = "org.scala-lang.modules" %% "scala-xml" % "1.0.6" lazy val rhino = "org.mozilla" % "rhino" % "1.7.10" - lazy val scala_parser = "org.scala-lang.modules" %% "scala-parser-combinators" % "1.0.4" + lazy val scala_parser = "org.scala-lang.modules" %% "scala-parser-combinators" % "1.1.0" lazy val xerces = "xerces" % "xercesImpl" % "2.11.0" // Aliases From 5c256763119e07f1c4366e160a85e81038ceaf73 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sun, 3 Jun 2018 09:51:59 -0400 Subject: [PATCH 1749/1949] Group provided deps --- project/Dependencies.scala | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 491526b289..e4581bc974 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -58,11 +58,15 @@ object Dependencies { // Provided scope: // Scope provided by container, available only in compile and test classpath, non-transitive by default. - lazy val logback = "ch.qos.logback" % "logback-classic" % "1.2.3" % "provided" - lazy val log4j = "log4j" % "log4j" % "1.2.17" % "provided" - lazy val slf4j_log4j12 = "org.slf4j" % "slf4j-log4j12" % slf4jVersion % "provided" - lazy val persistence_api = "javax.persistence" % "persistence-api" % "1.0.2" % "provided" + lazy val logback = "ch.qos.logback" % "logback-classic" % "1.2.3" % "provided" + lazy val log4j = "log4j" % "log4j" % "1.2.17" % "provided" + lazy val slf4j_log4j12 = "org.slf4j" % "slf4j-log4j12" % slf4jVersion % "provided" + lazy val persistence_api = "javax.persistence" % "persistence-api" % "1.0.2" % "provided" lazy val servlet_api = "javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided" + lazy val jquery = "org.webjars.bower" % "jquery" % "1.11.3" % "provided" + lazy val jasmineCore = "org.webjars.bower" % "jasmine-core" % "2.4.1" % "provided" + lazy val jasmineAjax = "org.webjars.bower" % "jasmine-ajax" % "3.2.0" % "provided" + // Runtime scope: @@ -87,8 +91,4 @@ object Dependencies { lazy val specs2Mock = "org.specs2" %% "specs2-mock" % "3.8.6" % "test" lazy val scalatest = "org.scalatest" %% "scalatest" % "3.0.1" % "test" lazy val junit = "junit" % "junit" % "4.8.2" % "test" - - lazy val jquery = "org.webjars.bower" % "jquery" % "1.11.3" % "provided" - lazy val jasmineCore = "org.webjars.bower" % "jasmine-core" % "2.4.1" % "provided" - lazy val jasmineAjax = "org.webjars.bower" % "jasmine-ajax" % "3.2.0" % "provided" } From 56c4a4e2ca0f7ba43df1fd857c1ea9b772667ca5 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Tue, 12 Jun 2018 20:27:05 -0400 Subject: [PATCH 1750/1949] Bump joda-convert to 2.1 --- project/Dependencies.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index e4581bc974..1d357ee614 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -35,7 +35,7 @@ object Dependencies { lazy val javamail = "javax.mail" % "mail" % "1.4.7" lazy val jbcrypt = "org.mindrot" % "jbcrypt" % "0.4" lazy val joda_time = "joda-time" % "joda-time" % "2.10" - lazy val joda_convert = "org.joda" % "joda-convert" % "2.0.2" + lazy val joda_convert = "org.joda" % "joda-convert" % "2.1" lazy val htmlparser = "nu.validator" % "htmlparser" % "1.4.11" lazy val mongo_java_driver = "org.mongodb" % "mongodb-driver" % "3.7.1" lazy val mongo_java_driver_async = "org.mongodb" % "mongodb-driver-async" % "3.7.1" From 54c3fee682f08a141d6b6ffc530957057dd657ee Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 16 Jun 2018 13:07:58 -0400 Subject: [PATCH 1751/1949] Revert htmlparser version bump --- project/Dependencies.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 1d357ee614..54922c9e66 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -36,7 +36,7 @@ object Dependencies { lazy val jbcrypt = "org.mindrot" % "jbcrypt" % "0.4" lazy val joda_time = "joda-time" % "joda-time" % "2.10" lazy val joda_convert = "org.joda" % "joda-convert" % "2.1" - lazy val htmlparser = "nu.validator" % "htmlparser" % "1.4.11" + lazy val htmlparser = "nu.validator.htmlparser" % "htmlparser" % "1.4" lazy val mongo_java_driver = "org.mongodb" % "mongodb-driver" % "3.7.1" lazy val mongo_java_driver_async = "org.mongodb" % "mongodb-driver-async" % "3.7.1" lazy val paranamer = "com.thoughtworks.paranamer" % "paranamer" % "2.8" From c1e31cabd7821b3213b676f364770f1ab2024016 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sat, 16 Jun 2018 20:39:42 -0400 Subject: [PATCH 1752/1949] Return parser combinators to their previous version Looks like there were some subtle behavior changes we'll need to account for here. --- project/Dependencies.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 54922c9e66..c6d5e99bad 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -48,7 +48,7 @@ object Dependencies { lazy val slf4j_api = "org.slf4j" % "slf4j-api" % slf4jVersion lazy val scala_xml = "org.scala-lang.modules" %% "scala-xml" % "1.0.6" lazy val rhino = "org.mozilla" % "rhino" % "1.7.10" - lazy val scala_parser = "org.scala-lang.modules" %% "scala-parser-combinators" % "1.1.0" + lazy val scala_parser = "org.scala-lang.modules" %% "scala-parser-combinators" % "1.0.4" lazy val xerces = "xerces" % "xercesImpl" % "2.11.0" // Aliases From 69bac9567931bb01cd6569589da3e8cc1f4d308b Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sun, 17 Jun 2018 11:07:20 -0400 Subject: [PATCH 1753/1949] Bump Scala 2.12 version in CI --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f914c536e7..a570021080 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ sudo: false scala: - 2.11.11 - - 2.12.4 + - 2.12.6 cache: directories: From ac4b07eb00de2364e4fcb23dc392fe89e8e2ae02 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sun, 17 Jun 2018 11:11:39 -0400 Subject: [PATCH 1754/1949] Build release-branch-* branches in Travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index a570021080..1f00258e6b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,6 +24,7 @@ script: ./travis.sh branches: only: - master + - /^release-branch-.*$/ node_js: - "4.1" From 83c446fc088ee97ec513ddc3ccc70d1701d3257d Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sun, 17 Jun 2018 11:15:05 -0400 Subject: [PATCH 1755/1949] Bump version to 3.3.0-RC1 for release. --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 22a26be1bd..edf2537273 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ import Dependencies._ import LiftSbtHelpers._ organization in ThisBuild := "net.liftweb" -version in ThisBuild := "3.3.0-SNAPSHOT" +version in ThisBuild := "3.3.0-RC1" homepage in ThisBuild := Some(url("http://www.liftweb.net")) licenses in ThisBuild += ("Apache License, Version 2.0", url("http://www.apache.org/licenses/LICENSE-2.0.txt")) startYear in ThisBuild := Some(2006) From f8be890e12408eae6219b1a4c0ab8a9659af35c6 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Wed, 11 Jul 2018 08:28:25 -0400 Subject: [PATCH 1756/1949] Bump version to 3.3.0 for final release --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index edf2537273..aa358f844c 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ import Dependencies._ import LiftSbtHelpers._ organization in ThisBuild := "net.liftweb" -version in ThisBuild := "3.3.0-RC1" +version in ThisBuild := "3.3.0" homepage in ThisBuild := Some(url("http://www.liftweb.net")) licenses in ThisBuild += ("Apache License, Version 2.0", url("http://www.apache.org/licenses/LICENSE-2.0.txt")) startYear in ThisBuild := Some(2006) From b0620169a5c10fe81008e633a0cd3c000c4536bd Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Wed, 11 Jul 2018 08:29:33 -0400 Subject: [PATCH 1757/1949] Bump version to 3.4.0-SNAPSHOT for forward development --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index aa358f844c..d3940ace90 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ import Dependencies._ import LiftSbtHelpers._ organization in ThisBuild := "net.liftweb" -version in ThisBuild := "3.3.0" +version in ThisBuild := "3.4.0-SNAPSHOT" homepage in ThisBuild := Some(url("http://www.liftweb.net")) licenses in ThisBuild += ("Apache License, Version 2.0", url("http://www.apache.org/licenses/LICENSE-2.0.txt")) startYear in ThisBuild := Some(2006) From b90d19ba487012e069d22978c574ca2920488904 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Wed, 11 Jul 2018 08:43:46 -0400 Subject: [PATCH 1758/1949] Update support status for 3.3 release [ci skip] --- SUPPORT.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/SUPPORT.md b/SUPPORT.md index 7faec210dc..b97916404a 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -27,7 +27,7 @@ of Lift: * Major releases aren't on a regular schedule, but we will announce and update this policy with actual dates when major releases become planned. -As of June 2nd, 2018 the current support status looks like this: +As of July 11th, 2018 the current support status looks like this: |Version |Initial Release | Last Release | Current Support Status | |---------|-----------------|------------------|---------------------------------| @@ -37,7 +37,8 @@ As of June 2nd, 2018 the current support status looks like this: |3.0 |November 2016 | 3.0.2 (Sep 2017) | Security fixes (until Lift 6.0) | |3.1 |July 2017 | 3.1.1 (Sep 2017) | Minor fixes (until Jan 2019) | |3.2 |January 2018 | 3.2.0 (Jan 2018) | Minor fixes (until July 2019) | -|3.3 |July 2018 (est) | Pre-release | Active development | +|3.3 |July 2018 | 3.3.0 (Jul 2018) | Minor fixes (until Jan 2020) | +|3.4 |Jan 2019 (est) | N/A | Active development | Per this support policy please ensure you're using a currently supported version of Lift before: From 9ea0d71339b69e0df3ee35e8e83d87fa5b07eb0d Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Wed, 11 Jul 2018 08:47:16 -0400 Subject: [PATCH 1759/1949] Update version numbers in readme [ci skip] --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9106a3fb2b..9a5b689d26 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ Logback if you don't already have another SLF4J logging library in place. For ex ```scala libraryDependencies ++= { - val liftVersion = "3.2.0" + val liftVersion = "3.3.0" Seq( "net.liftweb" %% "lift-webkit" % liftVersion % "compile", "ch.qos.logback" % "logback-classic" % "1.2.3" @@ -98,7 +98,7 @@ Add Lift to your `pom.xml` like so: net.liftweb lift-webkit_${scala.version} - 3.2.0 + 3.3.0 Where `${scala.version}` is `2.11` or `2.12`. Individual patch releases of the Scala compiler From 5a00d4a00d884db954124355889e436d60942eb1 Mon Sep 17 00:00:00 2001 From: Caleb Kirksey Date: Tue, 24 Jul 2018 19:44:49 -0400 Subject: [PATCH 1760/1949] fix markdown error on readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9a5b689d26..40fcd6e792 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,7 @@ We will accept issues and pull requests into the Lift codebase if the pull reque * The request adheres to our [contributing guidelines][contribfile], including having been discussed on the Mailing List if its from a non-committer. +[supfile]: https://github.com/lift/framework/blob/master/SUPPORT.md [contribfile]: https://github.com/lift/framework/blob/master/CONTRIBUTING.md [sigfile]: https://github.com/lift/framework/blob/master/contributors.md From d26ac96beadd6d29b0249fe2ecfaa01e87fa55ba Mon Sep 17 00:00:00 2001 From: Vitalii Lagutin Date: Tue, 28 Aug 2018 12:48:32 +0300 Subject: [PATCH 1761/1949] Tests for tryo method and fix for it. --- .../main/scala/net/liftweb/common/Tryo.scala | 8 +++-- .../scala/net/liftweb/common/BoxSpec.scala | 35 +++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Tryo.scala b/core/common/src/main/scala/net/liftweb/common/Tryo.scala index 212ce6e7de..d3ecfc487f 100644 --- a/core/common/src/main/scala/net/liftweb/common/Tryo.scala +++ b/core/common/src/main/scala/net/liftweb/common/Tryo.scala @@ -21,8 +21,12 @@ trait Tryo { try { Full(f) } catch { - case c if ignore.exists(_.isAssignableFrom(c.getClass)) => onError.foreach(_(c)); Empty - case c if (ignore == null || ignore.isEmpty) => onError.foreach(_(c)); Failure(c.getMessage, Full(c), Empty) + case c: Throwable => + onError.foreach(_(c)) + if (ignore == null || !ignore.exists(_.isAssignableFrom(c.getClass))) + Failure(c.getMessage, Full(c), Empty) + else + Empty } } diff --git a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala index a920aa4a7c..b5ce4b8910 100644 --- a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala +++ b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala @@ -515,6 +515,41 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { } } + "A Box tryo method" should { + "return Full" in { + Box.tryo(1) must_== Full(1) + } + + "return Failure(_, Full(NPE), _) in case of NPE" in { + val obj: Object = null + + Box.tryo(obj.toString) must beLike { + case Failure(_, Full(ex), _) => ex.getClass must_== classOf[NullPointerException] + } + } + + "return Empty in case of NPE and ignore list with NPE" in { + val ignore: List[Class[_]] = List(classOf[NullPointerException]) + + Box.tryo(ignore)(throw new NullPointerException) must_== Empty + } + + "return Failure(_, Full(NPE), _) in case of non empty ignore list without NPE" in { + val ignore: List[Class[_]] = List(classOf[IllegalArgumentException]) + + Box.tryo(ignore)(throw new NullPointerException) must beLike { + case Failure(_, Full(ex), _) => ex.getClass must_== classOf[NullPointerException] + } + } + + "not throw NPE in case of nullable ignore list" in { + val ignore: List[Class[_]] = null + + Box.tryo(ignore)(throw new IllegalArgumentException) must beLike { + case Failure(_, Full(ex), _) => ex.getClass must_== classOf[IllegalArgumentException] + } + } + } } From 706a98b63d5ac269e0443a05324ca85eae6fc25e Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sun, 18 Nov 2018 10:18:11 -0500 Subject: [PATCH 1762/1949] Bump to Scala 2.12.7 --- .travis.yml | 2 +- build.sbt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1f00258e6b..2db35a7b8a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ sudo: false scala: - 2.11.11 - - 2.12.6 + - 2.12.7 cache: directories: diff --git a/build.sbt b/build.sbt index d3940ace90..5110862233 100644 --- a/build.sbt +++ b/build.sbt @@ -7,8 +7,8 @@ homepage in ThisBuild := Some(url("http://www.liftweb.net")) licenses in ThisBuild += ("Apache License, Version 2.0", url("http://www.apache.org/licenses/LICENSE-2.0.txt")) startYear in ThisBuild := Some(2006) organizationName in ThisBuild := "WorldWide Conferencing, LLC" -scalaVersion in ThisBuild := "2.12.6" -crossScalaVersions in ThisBuild := Seq("2.12.6", "2.11.11") +scalaVersion in ThisBuild := "2.12.7" +crossScalaVersions in ThisBuild := Seq("2.12.7", "2.11.11") libraryDependencies in ThisBuild ++= Seq(specs2, specs2Matchers, specs2Mock, scalacheck, scalatest) From b606a4209e4a8ff589b97a3c086eef542a26bcb0 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sun, 18 Nov 2018 10:18:58 -0500 Subject: [PATCH 1763/1949] Remove all the extra space from the travis file --- .travis.yml | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2db35a7b8a..8acdc4a783 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,39 +1,28 @@ language: scala - dist: trusty - sudo: false - scala: - 2.11.11 - 2.12.7 - cache: directories: - '$HOME/node_modules' - $HOME/.ivy2 - services: - mongodb - jdk: - oraclejdk8 - script: ./travis.sh - branches: only: - master - /^release-branch-.*$/ - node_js: - "4.1" - before_script: - "cd web/webkit" - "npm install" - "cd -" - notifications: webhooks: urls: From 8286841c976b785e1b5c1ce2475e1e2f06e657e7 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Sun, 18 Nov 2018 10:20:28 -0500 Subject: [PATCH 1764/1949] Add a Scala 2.12 / jdk11 build to the matrix --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index 8acdc4a783..604e9ca590 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,10 @@ services: - mongodb jdk: - oraclejdk8 +matrix: + include: + - scala: 2.12.7 + jdk: oraclejdk11 script: ./travis.sh branches: only: From b1d7cdb0aec5820897eb7ab4adf66d268b3ac7e2 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Mon, 19 Nov 2018 10:16:35 -0500 Subject: [PATCH 1765/1949] sbt 1.2.6 boots, albeit without our fun logo --- build.sbt | 15 ++++++++------- project/Dependencies.scala | 10 +++------- project/LiftSbtHelpers.scala | 3 --- project/build.properties | 3 +-- project/plugins.sbt | 8 ++------ project/project/Plugin.scala | 7 ------- 6 files changed, 14 insertions(+), 32 deletions(-) delete mode 100644 project/project/Plugin.scala diff --git a/build.sbt b/build.sbt index d3940ace90..dd8ea63770 100644 --- a/build.sbt +++ b/build.sbt @@ -27,7 +27,7 @@ pomExtra in ThisBuild := Developers.toXml credentials in ThisBuild += Credentials(BuildPaths.getGlobalSettingsDirectory(state.value, BuildPaths.getGlobalBase(state.value)) / ".credentials") initialize := { - printLogo(name.value, version.value, scalaVersion.value) + //printLogo(name.value, version.value, scalaVersion.value) } resolvers in ThisBuild ++= Seq( @@ -46,9 +46,11 @@ lazy val framework = case _ => Seq() }) //workaround for scala/scala-dev#249 - .settings(aggregatedSetting(sources in(Compile, doc)), - aggregatedSetting(dependencyClasspath in(Compile, doc)), - publishArtifact := false) + /*.settings( + (sources / aggregate) in (Compile, doc), + aggregatedSetting(dependencyClasspath in(Compile, doc)), + publishArtifact := false + )*/ // Core Projects // ------------- @@ -155,8 +157,8 @@ lazy val webkit = commons_fileupload, rhino, servlet_api, - specs2.copy(configurations = Some("provided")), - specs2Matchers.copy(configurations = Some("provided")), + specs2, + specs2Matchers, jetty6, jwebunit, mockito_all, @@ -205,7 +207,6 @@ lazy val webkit = } //workaround for scala/scala-dev#249 } ) - .settings(yuiCompressor.Plugin.yuiSettings: _*) .enablePlugins(SbtWeb) // Persistence Projects diff --git a/project/Dependencies.scala b/project/Dependencies.scala index c6d5e99bad..129877d5fd 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -16,15 +16,11 @@ import sbt._ import Keys._ -import net.liftweb.sbt.LiftBuildPlugin.{crossMapped, defaultOrMapped} - object Dependencies { type ModuleMap = String => ModuleID - lazy val CVMappingAll = crossMapped("2.11.7" -> "2.11") - lazy val slf4jVersion = "1.7.25" // Compile scope: @@ -40,11 +36,11 @@ object Dependencies { lazy val mongo_java_driver = "org.mongodb" % "mongodb-driver" % "3.7.1" lazy val mongo_java_driver_async = "org.mongodb" % "mongodb-driver-async" % "3.7.1" lazy val paranamer = "com.thoughtworks.paranamer" % "paranamer" % "2.8" - lazy val scalajpa = "org.scala-libs" % "scalajpa" % "1.5" cross CVMappingAll + lazy val scalajpa = "org.scala-libs" % "scalajpa" % "1.5" lazy val scalap: ModuleMap = "org.scala-lang" % "scalap" % _ lazy val scala_compiler: ModuleMap = "org.scala-lang" % "scala-compiler" % _ - lazy val scalaz7_core = "org.scalaz" % "scalaz-core" % "7.2.24" cross CVMappingAll - lazy val squeryl = "org.squeryl" % "squeryl" % "0.9.5-7" cross CVMappingAll + lazy val scalaz7_core = "org.scalaz" % "scalaz-core" % "7.2.24" + lazy val squeryl = "org.squeryl" % "squeryl" % "0.9.5-7" lazy val slf4j_api = "org.slf4j" % "slf4j-api" % slf4jVersion lazy val scala_xml = "org.scala-lang.modules" %% "scala-xml" % "1.0.6" lazy val rhino = "org.mozilla" % "rhino" % "1.7.10" diff --git a/project/LiftSbtHelpers.scala b/project/LiftSbtHelpers.scala index dfb30d310d..7bb448cafb 100644 --- a/project/LiftSbtHelpers.scala +++ b/project/LiftSbtHelpers.scala @@ -17,8 +17,6 @@ import sbt._ import Keys._ -import net.liftweb.sbt.LiftBuildPlugin._ - object LiftSbtHelpers { def coreProject = liftProject("core") _ def webProject = liftProject("web") _ @@ -39,7 +37,6 @@ object LiftSbtHelpers { def liftProject(id: String, base: File): Project = { Project(id, base) - .settings(liftBuildSettings: _*) .settings(scalacOptions ++= List("-feature", "-language:implicitConversions")) .settings( autoAPIMappings := true, diff --git a/project/build.properties b/project/build.properties index 53b2a25b58..7c58a83abf 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1,2 +1 @@ -# Deprecate using build.properties, use -Dsbt.version=... in launcher arg instead -sbt.version=0.13.16 +sbt.version=1.2.6 diff --git a/project/plugins.sbt b/project/plugins.sbt index e14c78357c..b36a8f876b 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,9 +1,5 @@ DefaultOptions.addPluginResolvers - -resolvers += "sbt-idea-repo" at "http://mpeltonen.github.com/maven/" - -addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.6.0") - resolvers += Resolver.typesafeRepo("releases") -addSbtPlugin("com.typesafe.sbt" % "sbt-web" % "1.2.2") +addSbtPlugin("com.typesafe.sbt" % "sbt-web" % "1.4.3") +addSbtPlugin("org.jetbrains" % "sbt-idea-plugin" % "2.1.3") diff --git a/project/project/Plugin.scala b/project/project/Plugin.scala deleted file mode 100644 index ecd25ad4f3..0000000000 --- a/project/project/Plugin.scala +++ /dev/null @@ -1,7 +0,0 @@ -import sbt._ - -object PluginDef extends Build { - lazy val root = Project("plugins", file(".")) dependsOn(buildPlugin, yuiCompressorPlugin) - lazy val buildPlugin = uri("git://github.com/lift/sbt-lift-build.git#724fb133beac77bbd06d3fb8ea086a1c88ee2a7d") - lazy val yuiCompressorPlugin = uri("git://github.com/indrajitr/sbt-yui-compressor.git#89304ec0c988183d1f1a889e665e0269fe513031") -} From a06fbd317d969075058facb6aab1611921a5cdd8 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Mon, 19 Nov 2018 10:21:26 -0500 Subject: [PATCH 1766/1949] Get the LiftBuildPlugin properly included again --- build.sbt | 2 +- project/plugins.sbt | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index dd8ea63770..9712d29e5d 100644 --- a/build.sbt +++ b/build.sbt @@ -27,7 +27,7 @@ pomExtra in ThisBuild := Developers.toXml credentials in ThisBuild += Credentials(BuildPaths.getGlobalSettingsDirectory(state.value, BuildPaths.getGlobalBase(state.value)) / ".credentials") initialize := { - //printLogo(name.value, version.value, scalaVersion.value) + printLogo(name.value, version.value, scalaVersion.value) } resolvers in ThisBuild ++= Seq( diff --git a/project/plugins.sbt b/project/plugins.sbt index b36a8f876b..251954239d 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -3,3 +3,8 @@ resolvers += Resolver.typesafeRepo("releases") addSbtPlugin("com.typesafe.sbt" % "sbt-web" % "1.4.3") addSbtPlugin("org.jetbrains" % "sbt-idea-plugin" % "2.1.3") + +lazy val buildPlugin = RootProject(uri("git://github.com/lift/sbt-lift-build.git#f9c52bda7b43a98b9f8805c654c713d99db0a58f")) +lazy val yuiCompressorPlugin = RootProject(uri("git://github.com/indrajitr/sbt-yui-compressor.git#89304ec0c988183d1f1a889e665e0269fe513031")) + +lazy val root = (project in file(".")).dependsOn(buildPlugin /*, yuiCompressorPlugin*/) From ac3069ab963b9433d96b692fa22489234eb7c237 Mon Sep 17 00:00:00 2001 From: Matt Farmer Date: Mon, 19 Nov 2018 20:39:12 -0500 Subject: [PATCH 1767/1949] Remove the yui compressor plugin --- project/plugins.sbt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 251954239d..c1226156e6 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -5,6 +5,4 @@ addSbtPlugin("com.typesafe.sbt" % "sbt-web" % "1.4.3") addSbtPlugin("org.jetbrains" % "sbt-idea-plugin" % "2.1.3") lazy val buildPlugin = RootProject(uri("git://github.com/lift/sbt-lift-build.git#f9c52bda7b43a98b9f8805c654c713d99db0a58f")) -lazy val yuiCompressorPlugin = RootProject(uri("git://github.com/indrajitr/sbt-yui-compressor.git#89304ec0c988183d1f1a889e665e0269fe513031")) - -lazy val root = (project in file(".")).dependsOn(buildPlugin /*, yuiCompressorPlugin*/) +lazy val root = (project in file(".")).dependsOn(buildPlugin) From c8ad4dad49c644f6bea1d54fc96a48cfc49bdeed Mon Sep 17 00:00:00 2001 From: Ruben Laguna Date: Fri, 18 Jan 2019 17:22:05 +0100 Subject: [PATCH 1768/1949] Update with link to example --- core/json/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/json/README.md b/core/json/README.md index 4dacd58c5b..6873733bee 100644 --- a/core/json/README.md +++ b/core/json/README.md @@ -273,7 +273,7 @@ Querying JSON ------------ JSON values can be extracted using for-comprehensions. -Please see more examples in src/test/scala/net/liftweb/json/JsonQueryExamples.scala +Please see more examples in [src/test/scala/net/liftweb/json/JsonQueryExamples.scala](./src/test/scala/net/liftweb/json/JsonQueryExamples.scala) scala> import net.liftweb.json._ scala> val json = parse(""" From afe1ba84a1f7f10cbf82b070a27656bb33da565f Mon Sep 17 00:00:00 2001 From: Ruben Laguna Date: Fri, 18 Jan 2019 17:28:05 +0100 Subject: [PATCH 1769/1949] Add links to examples --- core/json/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/json/README.md b/core/json/README.md index 6873733bee..6311e29b20 100644 --- a/core/json/README.md +++ b/core/json/README.md @@ -217,7 +217,7 @@ Merging & Diffing ----------------- Two JSONs can be merged and diffed with each other. -Please see more examples in src/test/scala/net/liftweb/json/MergeExamples.scala and src/test/scala/net/liftweb/json/DiffExamples.scala +Please see more examples in [MergeExamples.scala](./src/test/scala/net/liftweb/json/MergeExamples.scala) and [DiffExamples.scala](src/test/scala/net/liftweb/json/DiffExamples.scala) scala> import net.liftweb.json._ @@ -273,7 +273,7 @@ Querying JSON ------------ JSON values can be extracted using for-comprehensions. -Please see more examples in [src/test/scala/net/liftweb/json/JsonQueryExamples.scala](./src/test/scala/net/liftweb/json/JsonQueryExamples.scala) +Please see more examples in [JsonQueryExamples.scala](./src/test/scala/net/liftweb/json/JsonQueryExamples.scala) scala> import net.liftweb.json._ scala> val json = parse(""" @@ -416,7 +416,7 @@ Extracting values Case classes can be used to extract values from parsed JSON. Non-existing values can be extracted into scala.Option and strings can be automatically converted into java.util.Dates. -Please see more examples in src/test/scala/net/liftweb/json/ExtractionExamplesSpec.scala +Please see more examples in [ExtractionExamplesSpec.scala](./src/test/scala/net/liftweb/json/ExtractionExamplesSpec.scala) scala> import net.liftweb.json._ scala> implicit val formats = DefaultFormats // Brings in default date formats etc. @@ -513,7 +513,7 @@ Serialization ============= Case classes can be serialized and deserialized. -Please see other examples in src/test/scala/net/liftweb/json/SerializationExamples.scala +Please see other examples in [SerializationExamples.scala](./src/test/scala/net/liftweb/json/SerializationExamples.scala) scala> import net.liftweb.json._ scala> import net.liftweb.json.Serialization.{read, write} @@ -659,7 +659,7 @@ XML support =========== JSON structure can be converted to XML node and vice versa. -Please see more examples in src/test/scala/net/liftweb/json/XmlExamples.scala +Please see more examples in [XmlExamples.scala](./src/test/scala/net/liftweb/json/XmlExamples.scala) scala> import net.liftweb.json.Xml.{toJson, toXml} scala> val xml = From d15d47297126fbb955b1e1e05c781a55a02459c5 Mon Sep 17 00:00:00 2001 From: Chris Hagan Date: Sat, 26 Jan 2019 12:07:14 +1100 Subject: [PATCH 1770/1949] Update README Detailed different sbt launch options --- README.md | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 40fcd6e792..e8aae518ab 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,18 @@ In order to run the server, navigate to the application folder and run the `sbt` By default, the server should run on http://localhost:8080. +The above command will do what you probably want; the application will recompile and restart whenever you change HTML, resources or Scala code. + +If your efforts are primarily dedicated to the frontend, you may find that it's not efficient to recompile and restart the application every time you change CSS or HTML. + +In this case, a good alternative command is + + jetty:quickstart + +The difference between *start* and *quickstart* is that start serves assets from your target directory where the exploded WAR is, and quickstart serves from the src directory where you're editing the files. + +Note that there is not a leading tilde *~* on the quickstart command. This is so that compile is not triggered when resources change. Your changed resources will be served directly. Note that in this mode Scala changes must be manually compiled. + ### With sbt (Existing project) If you're using Lift in an existing sbt project you'll need to: @@ -85,11 +97,7 @@ libraryDependencies ++= { #### Running the Server -In order to run the server, navigate to the application folder and run the `sbt` command. In the SBT prompt, run: - - ~jetty:start - -By default, the server should run on http://localhost:8080. +The same run process as above applies to this project configuration. ### With Maven From 9dc4a80bcafe2c0052643884ac7c7a69f26a8093 Mon Sep 17 00:00:00 2001 From: Bartek Kania Date: Tue, 2 Apr 2019 08:47:30 +0200 Subject: [PATCH 1771/1949] Upgrade nu.validator html parser Liftweb has a slightly ancient nu.validator version. Specifically it breaks vuetify data-table since it removes tags inside