|
Lift Framework example source code file (S.scala)
The Lift Framework S.scala source code/* * 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 http import java.util.{Locale, TimeZone, ResourceBundle} import collection.mutable.{HashMap, ListBuffer} import xml._ import common._ import actor.LAFuture import util._ import Helpers._ import js._ import builtin.snippet._ import provider._ import http.rest.RestContinuation class SJBridge { def s = S } /** * An object representing the current state of the HTTP request and response. * It uses the DynamicVariable construct such that each thread has its own * local session info without passing a huge state construct around. The S object * is initialized by LiftSession on request startup. * * @see LiftSession * @see LiftFilter */ object S extends S { /** * RewriteHolder holds a partial function that re-writes an incoming request. It is * used for per-session rewrites, as opposed to global rewrites, which are handled * by the LiftRules.rewrite RulesSeq. This case class exists so that RewritePFs may * be manipulated by name. See S.addSessionRewriter for example usage. * * @see # sessionRewriter * @see # addSessionRewriter * @see # clearSessionRewriter * @see # removeSessionRewriter * @see LiftRules # rewrite */ case class RewriteHolder(name: String, rewrite: LiftRules.RewritePF) /** * DispatchHolder holds a partial function that maps a Req to a LiftResponse. It is * used for per-session dispatch, as opposed to global dispatch, which are handled * by the LiftRules.dispatch RulesSeq. This case class exists so that DispatchPFs may * be manipulated by name. See S.addHighLevelSessionDispatcher for example usage. * * @see LiftResponse * @see LiftRules # dispatch * @see # highLevelSessionDispatchList * @see # addHighLevelSessionDispatcher * @see # removeHighLevelSessionDispatcher * @see # clearHighLevelSessionDispatcher */ case class DispatchHolder(name: String, dispatch: LiftRules.DispatchPF) /** * The CookieHolder class holds information about cookies to be sent during * the session, as well as utility methods for adding and deleting cookies. It * is used internally. * * @see # _responseCookies * @see # _init * @see # addCookie * @see # deleteCookie * @see # receivedCookies * @see # responseCookies * @see # findCookie */ case class CookieHolder(inCookies: List[HTTPCookie], outCookies: List[HTTPCookie]) { def add(in: HTTPCookie) = CookieHolder(inCookies, in :: outCookies.filter(_.name != in.name)) def delete(name: String) = { add(HTTPCookie(name, "").setMaxAge(0)) } def delete(old: HTTPCookie) = add(old.setMaxAge(0).setValue("")) } final case class PFPromoter[A, B](pff: () => PartialFunction[A, B]) object PFPromoter { implicit def fromPF[A, B](pf: PartialFunction[A, B]): PFPromoter[A, B] = new PFPromoter[A, B](() => pf) implicit def fromFunc[A, B](pff: () => PartialFunction[A, B]): PFPromoter[A, B] = new PFPromoter[A, B](pff) } private[http] class ProxyFuncHolder(proxyTo: AFuncHolder, _owner: Box[String]) extends AFuncHolder { def this(proxyTo: AFuncHolder) = this (proxyTo, Empty) def owner: Box[String] = _owner or proxyTo.owner def apply(in: List[String]): Any = proxyTo.apply(in) override def apply(in: FileParamHolder): Any = proxyTo.apply(in) override def supportsFileParams_? : Boolean = proxyTo.supportsFileParams_? override private[http] def lastSeen: Long = proxyTo.lastSeen override private[http] def lastSeen_=(when: Long) = proxyTo.lastSeen = when override def sessionLife = proxyTo.sessionLife } /** * Impersonates a function that will be called when uploading files */ @serializable private final class BinFuncHolder(val func: FileParamHolder => Any, val owner: Box[String]) extends AFuncHolder { def apply(in: List[String]) {logger.info("You attempted to call a 'File Upload' function with a normal parameter. Did you forget to 'enctype' to 'multipart/form-data'?")} override def apply(in: FileParamHolder) = func(in) override def supportsFileParams_? : Boolean = true } object BinFuncHolder { def apply(func: FileParamHolder => Any): AFuncHolder = new BinFuncHolder(func, Empty) def apply(func: FileParamHolder => Any, owner: Box[String]): AFuncHolder = new BinFuncHolder(func, owner) } object SFuncHolder { def apply(func: String => Any): AFuncHolder = new SFuncHolder(func, Empty) def apply(func: String => Any, owner: Box[String]): AFuncHolder = new SFuncHolder(func, owner) } /** * Impersonates a function that is executed on HTTP requests from client. The function * takes a String as the only parameter and returns an Any. */ @serializable private final class SFuncHolder(val func: String => Any, val owner: Box[String]) extends AFuncHolder { def this(func: String => Any) = this (func, Empty) def apply(in: List[String]): Any = in.headOption.toList.map(func(_)) } object LFuncHolder { def apply(func: List[String] => Any): AFuncHolder = new LFuncHolder(func, Empty) def apply(func: List[String] => Any, owner: Box[String]): AFuncHolder = new LFuncHolder(func, owner) } /** * Impersonates a function that is executed on HTTP requests from client. The function * takes a List[String] as the only parameter and returns an Any. */ @serializable private final class LFuncHolder(val func: List[String] => Any, val owner: Box[String]) extends AFuncHolder { def apply(in: List[String]): Any = func(in) } object NFuncHolder { def apply(func: () => Any): AFuncHolder = new NFuncHolder(func, Empty) def apply(func: () => Any, owner: Box[String]): AFuncHolder = new NFuncHolder(func, owner) } /** * Impersonates a function that is executed on HTTP requests from client. The function * takes zero arguments and returns an Any. */ @serializable private final class NFuncHolder(val func: () => Any, val owner: Box[String]) extends AFuncHolder { def apply(in: List[String]): Any = in.headOption.toList.map(s => func()) } /** * Abstrats a function that is executed on HTTP requests from client. */ @serializable sealed trait AFuncHolder extends Function1[List[String], Any] { def owner: Box[String] def apply(in: List[String]): Any def apply(in: FileParamHolder): Any = { error("Attempt to apply file upload to a non-file upload handler") } def supportsFileParams_? : Boolean = false def duplicate(newOwner: String): AFuncHolder = new ProxyFuncHolder(this, Full(newOwner)) @volatile private[this] var _lastSeen: Long = millis private[http] def lastSeen = _lastSeen private[http] def lastSeen_=(when: Long) = _lastSeen = when def sessionLife: Boolean = _sessionLife private[this] val _sessionLife: Boolean = functionLifespan_? } /** * The companion object that generates AFuncHolders from other functions */ object AFuncHolder { implicit def strToAnyAF(f: String => Any): AFuncHolder = SFuncHolder(f) implicit def unitToAF(f: () => Any): AFuncHolder = NFuncHolder(f) implicit def listStrToAF(f: List[String] => Any): AFuncHolder = LFuncHolder(f) implicit def boolToAF(f: Boolean => Any): AFuncHolder = LFuncHolder(lst => f(lst.foldLeft(false)( (v, str) => v || Helpers.toBoolean(str)))) } } /** * An object representing the current state of the HTTP request and response. * It uses the DynamicVariable construct such that each thread has its own * local session info without passing a huge state construct around. The S object * is initialized by LiftSession on request startup. * * @see LiftSession * @see LiftFilter */ trait S extends HasParams with Loggable { import S._ /* * The current session state is contained in the following val/vars: */ /** * Holds the current Req (request) on a per-thread basis. * @see Req */ private val _request = new ThreadGlobal[Req] /** * Holds the current functions mappings for this session. * * @see # functionMap * @see # addFunctionMap * @see # clearFunctionMap */ private val __functionMap = new ThreadGlobal[Map[String, AFuncHolder]] /** * This is simply a flag so that we know whether or not the state for the S object * has been initialized for our current scope. * * @see # inStatefulScope_ ? * @see # initIfUninitted */ private val inS = (new ThreadGlobal[Boolean]).set(false) /** * The _snippetMap holds mappings from snippet names to snippet functions. These mappings * are valid only in the current request. This val * is typically modified using the mapSnippet method. * * @see # mapSnippet * @see # locateMappedSnippet */ private[http] object _snippetMap extends RequestVar[Map[String, NodeSeq => NodeSeq]](Map()) /** * Holds the attributes that are set on the current snippet tag. Attributes are available * to snippet functions via the S.attr and S.attrs methods. The attributes are held in a * tuple, representing (most recent attributes, full attribute stack). * * @see # attrs * @see # attr */ private val _attrs = new ThreadGlobal[(MetaData,List[(Either[String, (String, String)], String)])] /** * Holds the per-request LiftSession instance. * * @see LiftSession * @see # session */ private val _sessionInfo = new ThreadGlobal[LiftSession] /** * Holds a list of ResourceBundles for this request. * * @see # resourceBundles * @see LiftRules # resourceNames * @see LiftRules # resourceBundleFactories */ private val _resBundle = new ThreadGlobal[List[ResourceBundle]] private[http] object _statefulSnip extends RequestVar[Map[String, DispatchSnippet]](Map()) private val _responseHeaders = new ThreadGlobal[ResponseInfoHolder] private val _responseCookies = new ThreadGlobal[CookieHolder] private val _lifeTime = new ThreadGlobal[Boolean] private val autoCleanUp = new ThreadGlobal[Boolean] private val _oneShot = new ThreadGlobal[Boolean] private val _disableTestFuncNames = new ThreadGlobal[Boolean] private object postFuncs extends TransientRequestVar(new ListBuffer[() => Unit]) /** * During an Ajax call made on a Comet component, make the Req's * params available */ private object paramsForComet extends TransientRequestVar[Map[String, List[String]]](Map()) /** * We can now collect JavaScript to append to the outgoing request, * no matter the format of the outgoing request */ private object _jsToAppend extends TransientRequestVar(new ListBuffer[JsCmd]) /** * We can now collect Elems to put in the head tag */ private object _headTags extends TransientRequestVar(new ListBuffer[Elem]) /** * 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])]) /** * This method returns true if the S object has been initialized for our current scope. If * the S object has not been initialized then functionality on S will not work. */ def inStatefulScope_? : Boolean = inS.value /** * Get a Req representing our current HTTP request. * * @return A Full(Req) if one has been initialized on the calling thread, Empty otherwise. * * @see Req */ def request: Box[Req] = (Box !! _request.value) or CurrentReq.box private[http] object CurrentLocation extends RequestVar[Box[sitemap.Loc[_]]](request.flatMap(_.location)) def location: Box[sitemap.Loc[_]] = CurrentLocation.is /** * @return a List of any Cookies that have been set for this Response. If you want * a specific cookie, use findCookie. * * @see net.liftweb.http.provider.HTTPCookie * @see # findCookie ( String ) * @see # addCookie ( Cookie ) * @see # deleteCookie ( Cookie ) * @see # deleteCookie ( String ) */ def receivedCookies: List[HTTPCookie] = for (rc <- Box.legacyNullTest(_responseCookies.value).toList; c <- rc.inCookies) yield c.clone().asInstanceOf[HTTPCookie] /** * Finds a cookie with the given name that was sent in the request. * * @param name - the name of the cookie to find * * @return Full ( cookie ) if the cookie exists, Empty otherwise * * @see net.liftweb.http.provider.HTTPCookie * @see # receivedCookies * @see # addCookie ( Cookie ) * @see # deleteCookie ( Cookie ) * @see # deleteCookie ( String ) */ def findCookie(name: String): Box[HTTPCookie] = Box.legacyNullTest(_responseCookies.value).flatMap( rc => Box(rc.inCookies.filter(_.name == name)). map(_.clone().asInstanceOf[HTTPCookie])) /** * Get the cookie value for the given cookie */ def cookieValue(name: String): Box[String] = for { cookie <- findCookie(name) value <- cookie.value } yield value def currentCometActor: Box[LiftCometActor] = CurrentCometActor.box /** * @return a List of any Cookies that have been added to the response to be sent * back to the user. If you want the cookies that were sent with the request, see * receivedCookies. * * @see net.liftweb.http.provider.HTTPCookie * @see # receivedCookies */ def responseCookies: List[HTTPCookie] = Box.legacyNullTest(_responseCookies.value). toList.flatMap(_.outCookies) /** * Adds a Cookie to the List[Cookies] that will be sent with the Response. * * If you wish to delete a Cookie as part of the Response, use the deleteCookie * method. * * An example of adding and removing a Cookie is: * * <pre name="code" class="scala" > * import net.liftweb.http.provider.HTTPCookie * * class MySnippet { * final val cookieName = "Fred" * * def cookieDemo (xhtml : NodeSeq) : NodeSeq = { * var cookieVal = S.findCookie(cookieName).map(_.getvalue) openOr "" * * def setCookie() { * val cookie = HTTPCookie(cookieName, cookieVal).setMaxAge(3600) // 3600 seconds, or one hour * S.addCookie(cookie) * } * * bind("cookie", xhtml, * "value" -> SHtml.text(cookieVal, cookieVal = _), * "add" -> SHtml.submit("Add", setCookie) * "remove" -> SHtml.link(S.uri, () => S.deleteCookie(cookieName), "Delete Cookie") * ) * } * } * </pre> * * @see net.liftweb.http.provider.HTTPCookie * @see # deleteCookie ( Cookie ) * @see # deleteCookie ( String ) * @see # responseCookies */ def addCookie(cookie: HTTPCookie) { Box.legacyNullTest(_responseCookies.value).foreach(rc => _responseCookies.set(rc.add(cookie)) ) } /** * Deletes the cookie from the user's browser. * * @param cookie the Cookie to delete * * @see net.liftweb.http.provider.HTTPCookie * @see # addCookie ( Cookie ) * @see # deleteCookie ( String ) */ def deleteCookie(cookie: HTTPCookie) { Box.legacyNullTest(_responseCookies.value).foreach(rc => _responseCookies.set(rc.delete(cookie)) ) } /** * Deletes the cookie from the user's browser. * * @param name the name of the cookie to delete * * @see net.liftweb.http.provider.HTTPCookie * @see # addCookie ( Cookie ) * @see # deleteCookie ( Cookie ) */ def deleteCookie(name: String) { Box.legacyNullTest(_responseCookies.value).foreach(rc => _responseCookies.set(rc.delete(name)) ) } /** * Find a template based on the snippet attribute "template" */ // TODO: Is this used anywhere? - DCB def templateFromTemplateAttr: Box[NodeSeq] = for (templateName <- attr("template") ?~ "Template Attribute missing"; val tmplList = templateName.roboSplit("/"); template <- Templates(tmplList) ?~ "couldn't find template") yield template /** * Returns the Locale for this request based on the LiftRules.localeCalculator * method. * * @see LiftRules.localeCalculator ( HTTPRequest ) * @see java.util.Locale */ def locale: Locale = LiftRules.localeCalculator(containerRequest) /** * Return the current timezone based on the LiftRules.timeZoneCalculator * method. * * @see LiftRules.timeZoneCalculator ( HTTPRequest ) * @see java.util.TimeZone */ def timeZone: TimeZone = LiftRules.timeZoneCalculator(containerRequest) /** * @return <code>true if this response should be rendered in * IE6/IE7 compatibility mode. * * @see LiftSession.ieMode * @see LiftRules.calcIEMode * @see Req.isIE6 * @see Req.isIE7 * @see Req.isIE8 * @see Req.isIE */ def ieMode: Boolean = session.map(_.ieMode.is) openOr false // LiftRules.calcIEMode() /** * Get the current instance of HtmlProperties */ def htmlProperties: HtmlProperties = { session.map(_.requestHtmlProperties.is) openOr LiftRules.htmlProperties.vend( S.request openOr Req.nil ) } /** * Return a List of the LiftRules.DispatchPF functions that are set for this * session. See addHighLevelSessionDispatcher for an example of how these are * used. * * @see LiftRules.DispatchPF * @see # addHighLevelSessionDispatcher ( String, LiftRules.DispatchPF ) * @see # removeHighLevelSessionDispatcher ( String ) * @see # clearHighLevelSessionDispatcher */ def highLevelSessionDispatcher: List[LiftRules.DispatchPF] = highLevelSessionDispatchList.map(_.dispatch) /** * Return the list of DispatchHolders set for this session. * * @see DispatchHolder */ def highLevelSessionDispatchList: List[DispatchHolder] = session map (_.highLevelSessionDispatcher.toList.map(t => DispatchHolder(t._1, t._2))) openOr Nil /** * Adds a dispatch function for the current session, as opposed to a global * dispatch through LiftRules.dispatch. An example would be if we wanted a user * to be able to download a document only when logged in. First, we define * a dispatch function to handle the download, specific to a given user: * * <pre name="code" class="scala" > * def getDocument(userId : Long)() : Box[LiftResponse] = { ... } * </pre> * * Then, in the login/logout handling snippets, we could install and remove * the custom dispatch as appropriate: * * <pre name="code" class="scala" > * def login(xhtml : NodeSeq) : NodeSeq = { * def doAuth () { * ... * if (user.loggedIn_?) { * S.addHighLevelSessionDispatcher("docDownload", { * case Req(List("download", "docs"), _, _) => getDocument(user.id) * } ) * } * } * * def logout(xhtml : NodeSeq) : NodeSeq = { * def doLogout () { * ... * S.removeHighLevelSessionDispatcher("docDownload") * // or, if more than one dispatch has been installed, this is simpler * S.clearHighLevelSessionDispatcher * } * } * </pre> * * It's important to note that per-session dispatch takes precedence over * LiftRules.dispatch, so you can override things set there. * * @param name A name for the dispatch. This can be used to remove it later by name. * @param disp The dispatch partial function * * @see LiftRules.DispatchPF * @see LiftRules.dispatch * @see # removeHighLevelSessionDispatcher * @see # clearHighLevelSessionDispatcher */ def addHighLevelSessionDispatcher(name: String, disp: LiftRules.DispatchPF) = session map (_.highLevelSessionDispatcher += (name -> disp)) /** * Removes a custom dispatch function for the current session. See * addHighLevelSessionDispatcher for an example of usage. * * @param name The name of the custom dispatch to be removed. * * @see LiftRules.DispatchPF * @see LiftRules.dispatch * @see # addHighLevelSessionDispatcher * @see # clearHighLevelSessionDispatcher */ def removeHighLevelSessionDispatcher(name: String) = session map (_.highLevelSessionDispatcher -= name) /** * Clears all custom dispatch functions from the current session. See * addHighLevelSessionDispatcher for an example of usage. * * @see LiftRules.DispatchPF * @see LiftRules.dispatch * @see # addHighLevelSessionDispatcher * @see # clearHighLevelSessionDispatcher */ def clearHighLevelSessionDispatcher = session map (_.highLevelSessionDispatcher.clear) /** * Return the list of RewriteHolders set for this session. See addSessionRewriter * for an example of how to use per-session rewrites. * * @see RewriteHolder * @see LiftRules # rewrite */ def sessionRewriter: List[RewriteHolder] = session map (_.sessionRewriter.toList.map(t => RewriteHolder(t._1, t._2))) openOr Nil /** * Adds a per-session rewrite function. This can be used if you only want a particular rewrite * to be valid within a given session. Per-session rewrites take priority over rewrites set in * LiftRules.rewrite, so you can use this mechanism to override global functionality. For example, * you could set up a global rule to make requests for the "account profile" page go back to the home * page by default: * * <pre name="code" class="scala" > * package bootstrap.liftweb * ... imports ... * class Boot { * def boot { * LiftRules.rewrite.append { * case RewriteRequest(ParsePath(List("profile")), _, _, _) => * RewriteResponse(List("index")) * } * } * } * </pre> * * Then, in your login snippet, you could set up a per-session rewrite to the correct template: * * <pre name="code" class="scala" > * def loginSnippet (xhtml : NodeSeq) : NodeSeq = { * ... * def doLogin () { * ... * S.addSessionRewriter("profile", { * case RewriteRequest(ParsePath(List("profile")), _, _, _) => * RewriteResponse(List("viewProfile"), Map("user" -> user.id)) * } * ... * } * ... * } * </pre> * * And in your logout snippet you can remove the rewrite: * * <pre name="code" class="scala" > * def doLogout () { * S.removeSessionRewriter("profile") * // or * S.clearSessionRewriter * } * </pre> * * * @param name A name for the rewrite function so that it can be replaced or deleted later. * @rw The rewrite partial function * * @see LiftRules.rewrite * @see # sessionRewriter * @see # removeSessionRewriter * @see # clearSessionRewriter */ def addSessionRewriter(name: String, rw: LiftRules.RewritePF) = session map (_.sessionRewriter += (name -> rw)) /** * Removes the given per-session rewriter. See addSessionRewriter for an * example of usage. * * @see LiftRules.rewrite * @see # addSessionRewriter * @see # clearSessionRewriter */ def removeSessionRewriter(name: String) = session map (_.sessionRewriter -= name) /** * Put the given Elem in the head tag. The Elems * will be de-dupped so no problems adding the * same style tag multiple times */ def putInHead(elem: Elem): Unit = _headTags.is += elem /** * Get the accumulated Elems for head * * @see putInHead */ def forHead(): List[Elem] = _headTags.is.toList /** * Put the given Elem at the end of the body tag. */ def putAtEndOfBody(elem: Elem): Unit = _tailTags.is += elem /** * Get the accumulated Elems for the end of the body * * @see putAtEndOfBody */ def atEndOfBody(): List[Elem] = _tailTags.is.toList /** * Sometimes it's helpful to accumute JavaScript as part of servicing * a request. For example, you may want to accumulate the JavaScript * as part of an Ajax response or a Comet Rendering or * as part of a regular HTML rendering. Call S.appendJs(jsCmd). * The accumulation of Js will be emitted as part of the response. */ def appendJs(js: JsCmd): Unit = _jsToAppend.is += js /** * Sometimes it's helpful to accumute JavaScript as part of servicing * a request. For example, you may want to accumulate the JavaScript * as part of an Ajax response or a Comet Rendering or * as part of a regular HTML rendering. Call S.appendJs(jsCmd). * The accumulation of Js will be emitted as part of the response. */ def appendJs(js: Seq[JsCmd]): Unit = _jsToAppend.is ++= js /** * Get the accumulated JavaScript * * @see appendJs */ def jsToAppend(): List[JsCmd] = { import js.JsCmds._ (for { sess <- S.session } yield 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)) } } } /** * Clears the per-session rewrite table. See addSessionRewriter for an * example of usage. * * @see LiftRules.rewrite * @see # addSessionRewriter * @see # removeSessionRewriter */ def clearSessionRewriter = session map (_.sessionRewriter.clear) /** * Test the current request to see if it's a POST. This is a thin wrapper * over Req.post_? * * @return <code>true if the request is a POST request, |
* <td>List((Left("testname"), "test"))
* </tr>
* <tr>
* <td><lift:MySnippet anchor:name="test" />
* <td>List((Right(("anchor", "name")), "test"))
* </tr>
* </table>
*
* <p>The prefixedAttrsToMap method provides a convenient way to retrieve only attributes with
* a given prefix. The prefixedAttrsToMetaData method can be used to add attributes onto an XML
* node</p>
*
* @see # prefixedAttrsToMap ( String )
* @see # prefixedAttrsToMap ( String, Map )
* @see # prefixedAttrsToMetaData ( String )
* @see # prefixedAttrsToMetaData ( String, Map )
*/
def attrs: List[(Either[String, (String, String)], String)] = S._attrs.value match {
case null => Nil
case (current,full) => full
}
/**
* Returns the S attributes that are prefixed by 'prefix' parameter as a Map[String, String]
* that will be 'merged' with the 'start' Map
*
* @param prefix the prefix to be matched
* @param start the initial Map
*
* @return Map[String, String]
*
* @see # prefixedAttrsToMap ( String )
* @see # prefixedAttrsToMetaData ( String )
* @see # prefixedAttrsToMetaData ( String, Map )
*
*/
def prefixedAttrsToMap(prefix: String, start: Map[String, String]): Map[String, String] =
attrs.reverse.flatMap {
case (Right((pre, name)), value) if pre == prefix => List((name, value))
case (Left(name), value) if name.startsWith(prefix + ":") => List(name.substring(prefix.length + 1) -> value)
case _ => Nil
}.foldRight(start) {
case ((name, value), at) => at + (name -> value)
}
/**
* Returns the S attributes that are prefixed by 'prefix' parameter as a Map[String, String]
*
* @param prefix the prefix to be matched
*
* @return Map[String, String]
*
* @see # prefixedAttrsToMap ( String, Map )
* @see # prefixedAttrsToMetaData ( String )
* @see # prefixedAttrsToMetaData ( String, Map )
*
*/
def prefixedAttrsToMap(prefix: String): Map[String, String] =
prefixedAttrsToMap(prefix: String, Map.empty)
/**
* <p>Returns the S attributes that are prefixed by 'prefix' parameter as a MetaData.
* The start Map will be 'merged' with the Map resulted after prefix matching and
* the result Map will be converted to a MetaData. The MetaData can be used to add attributes
* back onto XML elements via Scala's '%' method. For example, if we wanted to add
* attributes prefixed with "anchor" to any <a> elements we create, we could
* do something like:</p>
*
* <pre name="code" class="scala" >
* val myLink = (<a href= {...} >...) % S.prefixedAttrsToMetaData("anchor", Map("id" -> "myAnchor"))
* </pre>
*
* @param prefix the prefix to be matched
* @param start the initial Map
*
* @return MetaData representing the combination of current attributes plus the start Map of attributes
*
* @see # prefixedAttrsToMap ( String )
* @see # prefixedAttrsToMap ( String, Map )
* @see # prefixedAttrsToMetaData ( String )
*
*/
def prefixedAttrsToMetaData(prefix: String, start: Map[String, String]): MetaData =
mapToAttrs(prefixedAttrsToMap(prefix, start))
/**
* Similar with prefixedAttrsToMetaData(prefix: String, start: Map[String, String])
* but there is no 'start' Map
*/
def prefixedAttrsToMetaData(prefix: String): MetaData = prefixedAttrsToMetaData(prefix, Map.empty)
/**
* Converts a Map[String, String] into a MetaData instance. This can be used to
* add attributes to an XML element based on a map of attribute->value pairs. See
* prefixedAttrsToMetaData(String,Map) for an example.
*
* @param in The map of attributes
*
* @return MetaData representing the Map of attributes as unprefixed attributes.
*
* @see # prefixedAttrsToMetaData ( String, Map )
*
*/
def mapToAttrs(in: Map[String, String]): MetaData =
in.foldLeft[MetaData](Null) {
case (md, (name, value)) => new UnprefixedAttribute(name, value, md)
}
/**
* Converts the S.attrs to a Map[String, String]. The key of the map depends on whether
* the attribute is prefixed or not. Prefixed attributes have keys of the form
* "prefix:name", while unprefixed attributes have keys of the form "name". If you only want
* attributes for a specific prefix, use prefixedAttrsToMap.
*
* @see # prefixedAttrsToMap ( String )
* @see # prefixedAttrsToMap ( String, Map )
*/
def attrsFlattenToMap: Map[String, String] = Map.empty ++ attrs.flatMap {
case (Left(key), value) => List((key, value))
case (Right((prefix, key)), value) => List((prefix + ":" + key, value))
case _ => Nil
}
/**
* Converts S.attrs attributes to a MetaData object that can be used to add
* attributes to one or more XML elements. Similar to prefixedAttrsToMetaData, except
* that it handles both prefixed and unprefixed attributes. This version of the method will
* use all of the currently set attributes from S.attrs. If you want to filter it, use the
* attrsToMetaData(String => Boolean) version, which allows you to specify a predicate
* function for filtering. For example, if you want all of the current attributes to be
* added to a div tag, you could do:
*
* <pre name="code" class="scala" >
* val myDiv = (<div> {...} ) % S.attrsToMetaData
* </pre>
*
* @return a MetaData instance representing all attributes in S.attrs
*
* @see # attrsToMetaData ( String = > Boolean )
*/
def attrsToMetaData: MetaData = attrsToMetaData(ignore => true)
/**
* Similar to S.attrsToMetaData, but lets you specify a predicate function that filters the
* generated MetaData. For example, if you only wanted the "id" attribute, you could do:
*
* <pre name="code" class="scala" >
* val myDiv = (<div> {...} ) % S.attrsToMetaData(_.equalsIgnoreCase("id"))
* </pre>
*
* @param predicate The predicate function which is executed for each attribute name. If the function
* returns <code>true, then the attribute is included in the MetaData.
*
* @see # attrsToMetaData
*
*/
def attrsToMetaData(predicate: String => Boolean): MetaData = {
attrs.foldLeft[MetaData](Null) {
case (md, (Left(name), value)) if (predicate(name)) => new UnprefixedAttribute(name, value, md)
case (md, (Right((prefix, name)), value)) if (predicate(name)) => new PrefixedAttribute(prefix, name, value, md)
case _ => Null
}
}
/**
* Converts S.attrs attributes to a MetaData object that can be used to add
* attributes to one or more XML elements. Similar to prefixedAttrsToMetaData, except
* that it handles both prefixed and unprefixed attributes. This version of the method will
* use all of the currently set attributes from S.attrs. If you want to filter it, use the
* currentAttrsToMetaData(String => Boolean) version, which allows you to specify a predicate
* function for filtering. For example, if you want all of the current attributes to be
* added to a div tag, you could do:
*
* <pre name="code" class="scala" >
* val myDiv = (<div> {...} ) % S.attrsToMetaData
* </pre>
*
* @return a MetaData instance representing all attributes in S.attrs
*
* @see # attrsToMetaData ( String = > Boolean )
*/
def currentAttrsToMetaData: MetaData = currentAttrsToMetaData(ignore => true)
/**
* Similar to S.attrsToMetaData, but lets you specify a predicate function that filters the
* generated MetaData. For example, if you only wanted the "id" attribute, you could do:
*
* <pre name="code" class="scala" >
* val myDiv = (<div> {...} ) % S.attrsToMetaData(_.equalsIgnoreCase("id"))
* </pre>
*
* @param predicate The predicate function which is executed for each attribute name. If the function
* returns <code>true, then the attribute is included in the MetaData.
*
* @see # attrsToMetaData
*
*/
def currentAttrsToMetaData(predicate: String => Boolean): MetaData = {
currentAttrs.filter(a => predicate(a.key))
}
/**
* Find and process a template. This can be used to load a template from within some other Lift processing,
* such as a snippet or view. If you just want to retrieve the XML contents of a template, use
* Templates.apply.
*
* @param path The path for the template that you want to process
* @param snips any snippet mapping specific to this template run
* @return a Full Box containing the processed template, or a Failure if the template could not be found.
*
* @see TempalateFinder # apply
*/
def runTemplate(path: List[String], snips: (String, NodeSeq => NodeSeq)*): Box[NodeSeq] =
mapSnippetsWith(snips :_*) {
for{
t <- Templates(path) ?~ ("Couldn't find template " + path)
sess <- session ?~ "No current session"
} yield sess.processSurroundAndInclude(path.mkString("/", "/", ""), t)
}
/**
* Evaluate a template for snippets. This can be used to run a template
* from within some other Lift processing,
* such as a snippet or view.
*
* @param template the HTML template to run through the Snippet re-writing process
* @param snips any snippet mapping specific to this template run
* @return a Full Box containing the processed template, or a Failure if the template could not be found.
*/
def eval(template: NodeSeq, snips: (String, NodeSeq => NodeSeq)*): Box[NodeSeq] =
mapSnippetsWith(snips :_*) {
for{
sess <- session ?~ "No current session"
} yield sess.processSurroundAndInclude("HTML Constant", template)
}
/**
* Used to get an attribute by its name. There are several means to getting
* attributes:
*
* <pre name="code" class="scala">
// Get a Box for the attribute:
val myAttr = S.attr("test") openOr "Not found"
// Get an attribute or return a default value:
val myAttr = S.attr("name", "Fred")
// Apply a transform function on the attribute value, or return an Empty:
val pageSize = S.attr("count", _.toInt) openOr 20
// There are also prefixed versions:
val prefixedAttr = S.attr("prefix", "name") openOr "Not found"
* </pre>
*
* Note that this uses the data held in S.attrs, which means that
* it will find attributes through the entire snippet nesting stack.
* For example, given the snippets:
*
* <pre name="code" class="xml">
* <lift:MyStuff.snippetA foo="bar">
* <lift.MyStuff.snippetB>...</lift.MyStuff.snippetB>
* </lift:MyStuff.snippetA>
* </pre>
*
* Calling <code>S.attr("foo") from snippetB will return
* <code>Full("bar").
*/
object attr extends AttrHelper[Box] {
type Info = String
protected def findAttr(key: String): Option[Info] =
attrs.find {
case (Left(v), _) if v == key => true
case _ => false
}.map(_._2)
protected def findAttr(prefix: String, key: String): Option[Info] =
attrs.find {
case (Right((p, n)), _) if (p == prefix && n == key) => true
case _ => false
}.map(_._2)
protected def convert[T](in: Option[T]): Box[T] = Box(in)
/**
* 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)
/**
* 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)
}
/**
* 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")
def setVars[T](attr: MetaData)(f: => T): T = withAttrs(attr)(f)
/**
* A function that will eagerly evaluate a template.
*/
def eagerEval : NodeSeq => NodeSeq = ns => {
S.session match {
case Full(session) => session.processSurroundAndInclude("Eager Eval", ns)
case _ => ns
}
}
/**
* Initialize the current request session if it's not already initialized.
* Generally this is handled by Lift during request processing, but this
* method is available in case you want to use S outside the scope
* of a request (standard HTTP or Comet).
*
* @param session the LiftSession for this request
* @param f A function to execute within the scope of the session
*/
def initIfUninitted[B](session: LiftSession)(f: => B): B = {
if (inS.value) f
else init(Req.nil, session)(f)
}
/**
* Retrieves the attributes from the most recently executed
* snippet element.
*
* For example, given the snippets:
*
* <pre name="code" class="xml">
* <lift:MyStuff.snippetA foo="bar">
* <lift.MyStuff.snippetB>...</lift.MyStuff.snippetB>
* </lift:MyStuff.snippetA>
* </pre>
*
* S.currentAttrs will return <code>Nil.
*
* If you want a particular attribute, the S.currentAttr
* helper object simplifies things considerably.
*/
def currentAttrs: MetaData = _attrs.value match {
case null => Null
case (current, full) => current
}
/**
* Temporarily adds the given attributes to the current set, then executes the given function.
*
* @param attrs The attributes to set temporarily
*/
def withAttrs[T](attrs: MetaData)(f: => T): T = {
val currentStack = _attrs.value._2
val newFrame = attrs.toList.map {
case pa: PrefixedAttribute => (Right(pa.pre, pa.key), pa.value.text)
case m => (Left(m.key), m.value.text)
}
_attrs.doWith((attrs, newFrame ::: currentStack))(f)
}
/**
* Used to get an attribute by its name from the current
* snippet element. There are several means to getting
* attributes:
*
* <pre name="code" class="scala">
// Get a Box for the attribute:
val myAttr = S.currentAttr("test") openOr "Not found"
// Get an attribute or return a default value:
val myAttr = S.currentAttr("name", "Fred")
// Apply a transform function on the attribute value, or return an Empty:
val pageSize = S.currentAttr("count", _.toInt) openOr 20
// There are also prefixed versions:
val prefixedAttr = S.currentAttr("prefix", "name") openOr "Not found"
* </pre>
*
* Note that this uses the data held in S.currentAttrs, which means that
* it will only find attributes from the current snippet element.
* For example, given the snippets:
*
* <pre name="code" class="xml">
* <lift:MyStuff.snippetA foo="bar">
* <lift.MyStuff.snippetB>...</lift.MyStuff.snippetB>
* </lift:MyStuff.snippetA>
* </pre>
*
* Calling <code>S.currentAttr("foo") from snippetB will return
* <code>Empty.
*/
object currentAttr extends AttrHelper[Box] {
type Info = String
protected def findAttr(key: String): Option[Info] =
currentAttrs.toList.find{ _.key == key }.map(_.value.text)
protected def findAttr(prefix: String, key: String): Option[Info] =
currentAttrs.toList.find{ _.prefixedKey == (prefix + ":" + key) }.map(_.value.text)
protected def convert[T](in: Option[T]): Box[T] = Box(in)
/**
* 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)
/**
* 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)
}
/**
* Returns the LiftSession parameter denominated by 'what'.
*
* @see # getSessionAttribute
* @see # set
* @see # setSessionAttribute
* @see # unset
* @see # unsetSessionAttribute
*/
def get(what: String): Box[String] = session.flatMap(_.get[String](what))
/**
* Returns the HttpSession parameter denominated by 'what'
*
* @see # get
* @see # set
* @see # setSessionAttribute
* @see # unset
* @see # unsetSessionAttribute
*
*/
def getSessionAttribute(what: String): Box[String] = containerSession.flatMap(_.attribute(what) match {case s: String => Full(s) case _ => Empty})
/**
* Returns the HttpSession
*/
def containerSession: Box[HTTPSession] = session.flatMap(_.httpSession).or(containerRequest.map(_.session))
/**
* Returns the 'type' S attribute. This corresponds to the current Snippet's name. For example, the snippet:
*
* <pre name="code" class="xml">
<lift:Hello.world />
* </pre>
*
* Will return "Hello.world".
*/
def invokedAs: String = (currentSnippet or attr("type")) openOr ""
/**
* Sets a HttpSession attribute
*
* @see # get
* @see # getSessionAttribute
* @see # set
* @see # unset
* @see # unsetSessionAttribute
*
*/
def setSessionAttribute(name: String, value: String) = containerSession.foreach(_.setAttribute(name, value))
/**
* Sets a LiftSession attribute
*
* @see # get
* @see # getSessionAttribute
* @see # setSessionAttribute
* @see # unset
* @see # unsetSessionAttribute
*
*/
def set(name: String, value: String) = session.foreach(_.set(name, value))
/**
* Removes a HttpSession attribute
*
* @see # get
* @see # getSessionAttribute
* @see # set
* @see # setSessionAttribute
* @see # unset
*
*/
def unsetSessionAttribute(name: String) = containerSession.foreach(_.removeAttribute(name))
/**
* Removes a LiftSession attribute
*
* @see # get
* @see # getSessionAttribute
* @see # set
* @see # setSessionAttribute
* @see # unsetSessionAttribute
*
*/
def unset(name: String) = session.foreach(_.unset(name))
/**
* The current container request
*/
def containerRequest: Box[HTTPRequest] =
request.flatMap(r => Box !! r.request)
/**
* The hostname to which the request was sent. This is taken from the "Host" HTTP header, or if that
* does not exist, the DNS name or IP address of the server.
*/
def hostName: String = request.map(_.hostName) openOr Req.localHostName
/**
* The host and path of the request up to and including the context path. This does
* not include the template path or query string.
*/
def hostAndPath: String = request.map(_.hostAndPath) openOr ""
/**
* Get a map of function name bindings that are used for form and other processing. Using these
* bindings is considered advanced functionality.
*/
def functionMap: Map[String, AFuncHolder] = __functionMap.box.openOr(Map())
private def testFunctionMap[T](f: => T): T =
session match {
case Full(s) if s.stateful_? => f
case _ => throw new StateInStatelessException(
"Accessing function map information outside of a stateful session")
}
/**
* Clears the function map. potentially very destuctive... use at your own risk!
*/
def clearFunctionMap {
if (__functionMap.box.map(_.size).openOr(0) > 0) {
// Issue #1037
testFunctionMap {
__functionMap.box.foreach(ignore => __functionMap.set(Map()))
}
}
}
/**
* The current context path for the deployment.
*/
def contextPath: String = (request.map(_.contextPath) or session.map(_.contextPath)) openOr ""
/**
* Finds a snippet function by name.
*
* @see LiftRules.snippets
*/
def locateSnippet(name: String): Box[NodeSeq => NodeSeq] = {
val snippet = if (name.indexOf(".") != -1) name.roboSplit("\\.") else name.roboSplit(":")
NamedPF.applyBox(snippet, LiftRules.snippets.toList)
}
private object _currentSnippet extends RequestVar[Box[String]](Empty)
private[http] def doSnippet[T](name: String)(f: => T): T = {
val old = _currentSnippet.is
try {
_currentSnippet.set(Full(name))
f
} finally {
_currentSnippet.set(old)
}
}
def currentSnippet: Box[String] = _currentSnippet.is
def locateMappedSnippet(name: String): Box[NodeSeq => NodeSeq] = _snippetMap.is.get(name)
/**
* Associates a name with a snippet function 'func'. This can be used to change a snippet
* mapping on a per-request basis. For example, if we have a page that we want to change
* behavior on based on query parameters, we could use mapSnippet to programmatically determine
* which snippet function to use for a given snippet in the template. Our code would look like:
*
* <pre name="code" class="scala" >
import scala.xml.{ NodeSeq,Text }
class SnipMap {
def topSnippet (xhtml : NodeSeq) : NodeSeq = {
if (S.param("showAll").isDefined) {
S.mapSnippet("listing", listing)
} else {
S.mapSnippet("listing", { ignore => Text("") } )
}
...
}
def listing(xhtml : NodeSeq) : NodeSeq = {
...
}
</pre>
*
* Then, your template would simply look like:
*
* <pre name="code" class="scala" >
<lift:surround with="default" at="content">
...
<p><lift:SnipMap.topSnippet /></p>
<p><lift:listing /></p>
</lift:surround>
* </pre>
*
* Snippets are processed in the order that they're defined in the
* template, so if you want to use this approach make sure that
* the snippet that defines the mapping comes before the snippet that
* is being mapped. Also note that these mappings are per-request, and are
* discarded after the current request is processed.
*
* @param name The name of the snippet that you want to map (the part after "<lift:").
* @param func The snippet function to map to.
*/
def mapSnippet(name: String, func: NodeSeq => NodeSeq) {_snippetMap.set(_snippetMap.is.updated(name, func))}
/**
* The are times when it's helpful to define snippets for a certain
* call stack... snippets that are local purpose. Use doWithSnippets
* to temporarily define snippet mappings for the life of f.
*/
def mapSnippetsWith[T](snips: (String, NodeSeq => NodeSeq)*)(f: => T): T = {
val newMap = _snippetMap.is ++ snips
_snippetMap.doWith(newMap)(f)
}
private def updateFunctionMap(name: String, value: AFuncHolder) {
__functionMap.box match {
case Full(old) => __functionMap.set(old + ((name, value)))
case _ =>
}
}
/**
* Associates a name with a function impersonated by AFuncHolder. These are basically functions
* that are executed when a request contains the 'name' request parameter.
*/
def addFunctionMap(name: String, value: AFuncHolder) = {
testFunctionMap {
(autoCleanUp.box, _oneShot.box) match {
case (Full(true), _) => {
updateFunctionMap(name,
new S.ProxyFuncHolder(value) {
var shot = false
override def apply(in: List[String]): Any = {
synchronized {
if (!shot) {
shot = true
S.session.map(_.removeFunction(name))
value.apply(in)
} else {
js.JsCmds.Noop
}
}
}
override def apply(in: FileParamHolder): Any = {
synchronized {
if (!shot) {
shot = true
S.session.map(_.removeFunction(name))
value.apply(in)
} else {
js.JsCmds.Noop
}
}
}
})
}
case (_, Full(true)) => {
updateFunctionMap(name,
new S.ProxyFuncHolder(value) {
var shot = false
lazy val theFuture: LAFuture[Any] = {
S.session.map(_.removeFunction(name))
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: FileParamHolder): Any = future.get(5000).open_!
})
future
}
def fixShot(): Boolean = synchronized {
val ret = shot
shot = true
ret
}
override def apply(in: List[String]): Any = {
val ns = fixShot()
if (ns) {
theFuture.get(5000).open_!
} else {
val future = theFuture
try {
val ret = value.apply(in)
future.satisfy(ret)
ret
} catch {
case e => future.satisfy(e); throw e
}
}
}
override def apply(in: FileParamHolder): Any = {
val ns = fixShot()
if (ns) {
theFuture.get(5000).open_!
} else {
val future = theFuture
try {
val ret = value.apply(in)
future.satisfy(ret)
ret
} catch {
case e => future.satisfy(e); throw e
}
}
}
})
}
case _ =>
updateFunctionMap(name, value)
}
}
}
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
*/
def encodeURL(url: String) = {
URLRewriter.rewriteFunc map (_(url)) openOr url
}
private[http] object _formGroup extends TransientRequestVar[Box[Int]](Empty)
private object formItemNumber extends TransientRequestVar[Int](0)
private def notLiftOrScala(in: StackTraceElement): Boolean =
in.getClassName match {
case s if s.startsWith("net.liftweb") => false
case s if s.startsWith("scala") => false
case _ => true
}
def disableTestFuncNames_? : Boolean = _disableTestFuncNames.box openOr false
def disableTestFuncNames[T](f: => T): T =
_disableTestFuncNames.doWith(true) {
f
}
def formFuncName: String = if (Props.testMode && !disableTestFuncNames_?) {
val bump: Long = ((_formGroup.is openOr 0) + 1000L) * 100000L
val num: Int = formItemNumber.is
formItemNumber.set(num + 1)
import java.text._
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 {
_formGroup.is match {
case Full(x) => Helpers.nextFuncName(x.toLong * 100000L)
case _ => Helpers.nextFuncName
}
}
def formGroup[T](group: Int)(f: => T): T = {
val x = _formGroup.is
_formGroup.set(Full(group))
try {
f
} finally {
_formGroup.set(x)
}
}
import json.JsonAST._
/**
* Build a handler for incoming JSON commands based on the new Json Parser
*
* @param f - partial function against a returning a JsCmds
*
* @return ( JsonCall, JsCmd )
*/
def createJsonFunc(f: PFPromoter[JValue, JsCmd]): (JsonCall, JsCmd) = createJsonFunc(Empty, Empty, f)
/**
* Build a handler for incoming JSON commands based on the new Json Parser
*
* @param onError -- the JavaScript to execute client-side if the request is not processed by the server
* @param f - partial function against a returning a JsCmds
*
* @return ( JsonCall, JsCmd )
*/
def createJsonFunc(onError: JsCmd, f: PFPromoter[JValue, JsCmd]): (JsonCall, JsCmd) =
createJsonFunc(Empty, Full(onError), f)
/**
* Build a handler for incoming JSON commands based on the new Json Parser. You
* can use the helpful Extractor in net.liftweb.util.JsonCommand
*
* @param name -- the optional name of the command (placed in a comment for testing)
* @param onError -- the JavaScript to execute client-side if the request is not processed by the server
* @param f - partial function against a returning a JsCmds
*
* @return ( JsonCall, JsCmd )
*/
def createJsonFunc(name: Box[String], onError: Box[JsCmd], pfp: PFPromoter[JValue, JsCmd]): (JsonCall, JsCmd) = {
functionLifespan(true) {
val key = formFuncName
import json._
def jsonCallback(in: List[String]): JsCmd = {
val f = pfp.pff()
for {
line <- in
parsed <- JsonParser.parseOpt(line) if f.isDefinedAt(parsed)
} yield f(parsed)}.foldLeft(JsCmds.Noop)(_ & _)
val onErrorFunc: String =
onError.map(f => JsCmds.Run("function onError_" + key + "() {" + f.toJsCmd + """
}
""").toJsCmd) openOr ""
val onErrorParam = onError.map(f => "onError_" + key) openOr "null"
val af: AFuncHolder = jsonCallback _
addFunctionMap(key, af)
(JsonCall(key), JsCmds.Run(name.map(n => onErrorFunc +
"/* JSON Func " + n + " $$ " + key + " */").openOr("") +
"function " + key + "(obj) {liftAjax.lift_ajaxHandler(" +
"'" + key + "='+ encodeURIComponent(" +
LiftRules.jsArtifacts.
jsonStringify(JE.JsRaw("obj")).
toJsCmd + "), null," + onErrorParam + ");}"))
}
}
/**
* Build a handler for incoming JSON commands
*
* @param f - function returning a JsCmds
* @return ( JsonCall, JsCmd )
*/
def buildJsonFunc(f: Any => JsCmd): (JsonCall, JsCmd) = buildJsonFunc(Empty, Empty, f)
def buildJsonFunc(onError: JsCmd, f: Any => JsCmd): (JsonCall, JsCmd) =
buildJsonFunc(Empty, Full(onError), f)
/**
* Build a handler for incoming JSON commands
*
* @param name -- the optional name of the command (placed in a comment for testing)
*
* @param f - function returning a JsCmds
* @return ( JsonCall, JsCmd )
*/
def buildJsonFunc(name: Box[String], onError: Box[JsCmd], f: Any => JsCmd): (JsonCall, JsCmd) = {
functionLifespan(true) {
val key = formFuncName
def checkCmd(in: Any) = in match {
case v2: scala.collection.Map[Any, _] if v2.isDefinedAt("command") =>
// ugly code to avoid type erasure warning
val v = v2.asInstanceOf[scala.collection.Map[String, Any]]
JsonCmd(v("command").toString, v.get("target").
map {
case null => null
case x => x.toString
} getOrElse (null), v.get("params").getOrElse(None), v)
case v => v
}
def jsonCallback(in: List[String]): JsCmd = {
in.headOption.toList.flatMap {
s =>
val parsed = JSONParser.parse(s.trim).toList
val cmds = parsed.map(checkCmd)
val ret = cmds.map(f)
ret
}.foldLeft(JsCmds.Noop)(_ & _)
}
val onErrorFunc: String =
onError.map(f => JsCmds.Run("function onError_" + key + "() {" + f.toJsCmd + """
}
""").toJsCmd) openOr ""
val onErrorParam = onError.map(f => "onError_" + key) openOr "null"
val af: AFuncHolder = jsonCallback _
addFunctionMap(key, af)
(JsonCall(key), JsCmds.Run(name.map(n => onErrorFunc +
"/* JSON Func " + n + " $$ " + key + " */").openOr("") +
"function " + key + "(obj) {liftAjax.lift_ajaxHandler(" +
"'" + key + "='+ encodeURIComponent(" +
LiftRules.jsArtifacts.
jsonStringify(JE.JsRaw("obj")).
toJsCmd + "), null," + onErrorParam + ");}"))
}
}
/**
* Returns the JsCmd that holds the notices markup
*
*/
private[http] def noticesToJsCmd: JsCmd = LiftRules.noticesToJsCmd()
@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)
implicit def stuff2ToUnpref(in: (Symbol, Any)): UnprefixedAttribute = new UnprefixedAttribute(in._1.name, Text(in._2.toString), Null)
/**
* Attaches to this uri and parameter that has function f associated with. When
* this request is submitted to server the function will be executed and then
* it is automatically cleaned up from functions caches.
*/
def mapFuncToURI(uri: String, f: () => Unit): String = {
session map (_ attachRedirectFunc (uri, Box.legacyNullTest(f))) openOr uri
}
/**
* Execute code synchronized to the current session object
*/
def synchronizeForSession[T](f: => T): T = {
session match {
case Full(s) => s.synchronized(f)
case _ => f
}
}
/**
* Maps a function with an random generated and name
*/
def fmapFunc[T](in: AFuncHolder)(f: String => T): T = {
val name = formFuncName
addFunctionMap(name, in)
f(name)
}
/**
* Wrap an AFuncHolder with the current snippet and Loc context so that for Ajax calls, the original snippets,
* RequestVars and Loc (location) are populated
*
* @param f the AFuncHolder that you want to wrap with execution context
*/
def contextFuncBuilder(f: S.AFuncHolder): S.AFuncHolder = S.session match {
case Full(s) => s.contextFuncBuilder(f)
case _ => f
}
def render(xhtml: NodeSeq, httpRequest: HTTPRequest): NodeSeq = {
def doRender(session: LiftSession): NodeSeq =
session.processSurroundAndInclude("external render", xhtml)
if (inS.value) doRender(session.open_!)
else {
val req = Req(httpRequest, LiftRules.statelessRewrite.toList,
LiftRules.statelessTest.toList,
LiftRules.statelessReqTest.toList,
System.nanoTime)
CurrentReq.doWith(req) {
val ses: LiftSession = SessionMaster.getSession(httpRequest,
Empty) match {
case Full(ret) =>
ret.fixSessionTime()
ret
case _ =>
val ret = LiftSession(httpRequest.session, req.contextPath)
ret.fixSessionTime()
SessionMaster.addSession(ret,
req,
httpRequest.userAgent,
SessionMaster.getIpFromReq(req))
ret
}
init(req, ses) {
doRender(ses)
}
}
}
}
/**
* Maps a function with an random generated and name
*/
def jsonFmapFunc[T](in: Any => JsObj)(f: String => T): T = {
val name = formFuncName
addFunctionMap(name, SFuncHolder((s: String) => JSONParser.parse(s).map(in) openOr js.JE.JsObj()))
f(name)
}
/**
* Similar with addFunctionMap but also returns the name.
*
* Use fmapFunc(AFuncHolder)(String => T)
*/
@deprecated("Use fmapFunc(AFuncHolder)(String => T)")
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)")
def mapFunc(name: String, inf: AFuncHolder): String = {
addFunctionMap(name, inf)
name
}
/**
* Returns all the HTTP parameters having 'n' name
*/
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] =
paramsForComet.get.get(n).flatMap(_.headOption) orElse
request.flatMap(r => Box(r.param(n)))
/**
* Set the paramsForComet and run the function
*/
private[http] def doCometParams[T](map: Map[String, List[String]])(f: => T): T = {
paramsForComet.doWith(map)(f)
}
/**
* Sets an ERROR notice as a plain text
*/
def error(n: String) {error(Text(n))}
/**
* Sets an ERROR notice as an XML sequence
*/
def error(n: NodeSeq) {p_notice.is += ((NoticeType.Error, n, Empty))}
/**
* Sets an ERROR notice as an XML sequence and associates it with an id
*/
def error(id: String, n: NodeSeq) {p_notice.is += ((NoticeType.Error, n, Full(id)))}
/**
* Sets an ERROR notice as plain text and associates it with an id
*/
def error(id: String, n: String) {error(id, Text(n))}
/**
* Sets an NOTICE notice as plain text
*/
def notice(n: String) {notice(Text(n))}
/**
* Sets an 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
*/
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
*/
def notice(id: String, n: String) {notice(id, Text(n))}
/**
* Sets an WARNING notice as plain text
*/
def warning(n: String) {warning(Text(n))}
/**
* Sets an 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
*/
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
*/
def warning(id: String, n: String) {warning(id, Text(n))}
/**
* Sets an ERROR notices from a List[FieldError]
*/
def error(vi: List[FieldError]) {p_notice ++= vi.map {i => (NoticeType.Error, i.msg, i.field.uniqueFieldId)}}
private[http] def message(msg: String, notice: NoticeType.Value) {message(Text(msg), notice)}
private[http] def message(msg: NodeSeq, notice: NoticeType.Value) {p_notice += ((notice, msg, Empty))}
/**
* Add a whole list of notices
*/
def appendNotices(list: Seq[(NoticeType.Value, NodeSeq, Box[String])]) {p_notice.is ++= list}
/**
* Returns the current notices
*/
def getNotices: List[(NoticeType.Value, NodeSeq, Box[String])] = p_notice.toList
/**
* Returns the current and "old" notices
*/
def getAllNotices: List[(NoticeType.Value, NodeSeq, Box[String])] = p_notice.toList ++ oldNotices.is
/**
* Returns only ERROR notices
*/
def errors: List[(NodeSeq, Box[String])] = List(oldNotices.is, p_notice.is).flatMap(_.filter(_._1 == NoticeType.Error).map(n => (n._2, n._3)))
/**
* Returns only NOTICE notices
*/
def notices: List[(NodeSeq, Box[String])] = List(oldNotices.is, p_notice.is).flatMap(_.filter(_._1 == NoticeType.Notice).map(n => (n._2, n._3)))
/**
* Returns only WARNING notices
*/
def warnings: List[(NodeSeq, Box[String])] = List(oldNotices.is, p_notice.is).flatMap(_.filter(_._1 == NoticeType.Warning).map(n => (n._2, n._3)))
/**
* Clears up the notices
*/
def clearCurrentNotices {p_notice.is.clear}
/**
* Returns the messages provided by list function that are associated with id
*
* @param id - the lookup id
* @param f - the function that returns the messages
*/
def messagesById(id: String)(f: => List[(NodeSeq, Box[String])]): List[NodeSeq] = f filter (_._2 map (_ equals id) openOr false) map (_._1)
/**
* Returns all messages, associated with any id or not
*
* @param f - the function that returns the messages
*/
def messages(f: => List[(NodeSeq, Box[String])]): List[NodeSeq] = f map (_._1)
/**
* Returns the messages that are not associated with any id
*
* @param f - the function that returns the messages
*/
def noIdMessages(f: => List[(NodeSeq, Box[String])]): List[NodeSeq] = f filter (_._2 isEmpty) map (_._1)
/**
* Returns the messages that are associated with any id.
* Messages associated with the same id will be enlisted.
*
* @param f - the function that returns the messages
*/
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)))
})
res toList
}
implicit def tuple2FieldError(t: (FieldIdentifier, NodeSeq)) = FieldError(t._1, t._2)
/**
* Use this in DispatchPF for processing REST requests asynchronously. Note that
* this must be called in a stateful context, therefore the S state must be a valid one.
*
* @param f - the user function that does the actual computation. This function
* takes one parameter which is the function that must be invoked
* for returning the actual response to the client. Note that f function
* is invoked asynchronously in the context of a different thread.
*
*/
def respondAsync(f: => Box[LiftResponse]): () => Box[LiftResponse] = {
(for (req <- S.request) yield {
RestContinuation.respondAsync(req)(f)
}) openOr (() => Full(EmptyResponse))
}
/**
* If you bind functions (i.e. using SHtml helpers) inside the closure passed to callOnce,
* after your function is invoked, it will be automatically removed from functions cache so
* that it cannot be invoked again.
*/
def callOnce[T](f: => T): T = {
autoCleanUp.doWith(true) {
f
}
}
/**
* All functions created inside the oneShot scope
* will only be called once and their results will be
* cached and served again if the same function is invoked
*/
def oneShot[T](f: => T): T =
_oneShot.doWith(true) {
f
}
}
/**
* Defines the notices types
*/
@serializable
object NoticeType {
sealed abstract class Value(val title : String) {
def lowerCaseTitle = title.toLowerCase
// The element ID to use for notice divs
def id : String = LiftRules.noticesContainerId + "_" + lowerCaseTitle
// The element that will define the title to use in notice messages
def titleTag : String = lowerCaseTitle + "_msg"
def styleTag : String = lowerCaseTitle + "_class"
}
object Notice extends Value("Notice")
object Warning extends Value("Warning")
object Error extends Value("Error")
}
/**
* Used to handles JSON requests
*/
abstract class JsonHandler {
private val name = "_lift_json_" + getClass.getName
private def handlers: (JsonCall, JsCmd) =
S.session.map(s => s.get[Any](name) match {
case Full((x: JsonCall, y: JsCmd)) => (x, y)
case _ =>
val ret: (JsonCall, JsCmd) = S.buildJsonFunc(this.apply)
s.set(name, ret)
ret
}
).openOr((JsonCall(""), JsCmds.Noop))
def call: JsonCall = handlers._1
def jsCmd: JsCmd = handlers._2
def apply(in: Any): JsCmd
}
... this post is sponsored by my books ... | |
#1 New Release! |
FP Best Seller |
Copyright 1998-2024 Alvin Alexander, alvinalexander.com
All Rights Reserved.
A percentage of advertising revenue from
pages under the /java/jwarehouse
URI on this website is
paid back to open source projects.