|
Lift Framework example source code file (LiftRules.scala)
This example Lift Framework source code file (LiftRules.scala) is included in the DevDaily.com
"Java Source Code
Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.
The Lift Framework LiftRules.scala source code
/*
* 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 common._
import util._
import util.Helpers._
import sitemap._
import http.js.JSArtifacts
import http.js.jquery._
import http.provider._
import js._
import JE._
import JsCmds._
import auth._
import scala.xml._
import java.util.{Locale, TimeZone, ResourceBundle, Date}
import java.io.{InputStream, ByteArrayOutputStream, BufferedReader, StringReader}
import java.util.concurrent.{ConcurrentHashMap => CHash}
import scala.reflect.Manifest
import java.util.concurrent.atomic.AtomicInteger
class LiftRulesJBridge {
def liftRules: LiftRules = LiftRules
}
sealed trait LiftRulesMocker {
def realInstance: LiftRules
}
object LiftRulesMocker {
implicit def toLiftRules(in: LiftRulesMocker): LiftRules = in.realInstance
/**
* In Dev and Test mode, there's an option to stuff another LiftRules
* instance in here and use that one for mocking
*/
object devTestLiftRulesInstance extends ThreadGlobal[LiftRules]
/**
* This function, in Test and Dev mode will vend the instance of LiftRules.
* If there is an instance set in devTestLiftRulesInstance, that instance
* will be used, otherwise the global instance in LiftRules.prodInstance
* will be used.
*/
@volatile var calcLiftRulesInstance: () => LiftRules =
() => devTestLiftRulesInstance.box.openOr( LiftRules.prodInstance)
}
/**
* The data structure that contains information to determine if the
* request should be treated as a stateful or stateless request
*/
final case class StatelessReqTest(path: List[String], httpReq: HTTPRequest)
/**
* The Lift configuration singleton
*/
object LiftRules extends LiftRulesMocker {
lazy val prodInstance: LiftRules = new LiftRules()
private[this] val devOrTest = Props.devMode || Props.testMode
/**
* Get the real instance of LiftRules
*/
def realInstance: LiftRules = if (devOrTest) {
LiftRulesMocker.calcLiftRulesInstance()
} else prodInstance
type DispatchPF = PartialFunction[Req, () => Box[LiftResponse]];
/**
* The test between the path of a request and whether that path
* should result in stateless servicing of that path
*/
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
*/
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
* handled by lift rather than the default handler
*/
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 falure information when a snippet can not be executed.
*/
case class SnippetFailure(page: String, typeName: Box[String], failure: SnippetFailures.Value)
object SnippetFailures extends Enumeration {
val NoTypeDefined = Value(1, "No Type Defined")
val ClassNotFound = Value(2, "Class Not Found")
val StatefulDispatchNotMatched = Value(3, "Stateful Snippet: Dispatch Not Matched")
val MethodNotFound = Value(4, "Method Not Found")
val NoNameSpecified = Value(5, "No Snippet Name Specified")
val InstantiationException = Value(6, "Exception During Snippet Instantiation")
val DispatchSnippetNotMatched = Value(7, "Dispatch Snippet: Dispatch Not Matched")
val StateInStateless = Value(8, "Access to Lift's statefull features from Stateless mode")
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")
}
}
/**
* LiftRules is the global object that holds all of Lift's configuration.
*/
class LiftRules() extends Factory with FormVendor with LazyLoggable {
import LiftRules._
private var _doneBoot = false
/**
* Does the LiftRules instance think it's done booting?
*/
def doneBoot = _doneBoot
def noticesContainerId = "lift__noticesContainer__"
private val pageResourceId = Helpers.nextFuncName
/**
* If you want to make the Lift inactivity timeout shorter than
* the container inactivity timeout, set the inactivity timeout here
*/
val sessionInactivityTimeout = new FactoryMaker[Box[Long]](Empty){}
/**
* The function that converts a scala.text.Document to
* a String (used for JsonAST.JValue to text convertion.
* By default, use Printer.pretty for dev mode and
* Printer.compact for other modes
*/
val jsonOutputConverter = new FactoryMaker[scala.text.Document => String]({
import json.Printer
if (Props.devMode) Printer.pretty _ else Printer.compact _}){}
/**
* Set the default fadeout mechanism for Lift notices. Thus you provide a function that take a NoticeType.Value
* and decide the duration after which the fade out will start and the actual fadeout time. This is applicable
* for general notices (not associated with id-s) regardless if they are set for the page rendering, ajax
* response or Comet response.
*/
val noticesAutoFadeOut = new FactoryMaker[(NoticeType.Value) => Box[(TimeSpan, TimeSpan)]]((notice : NoticeType.Value) => Empty){}
/**
* 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
* 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.
*/
val noticesEffects = new FactoryMaker[(Box[NoticeType.Value], String) => Box[JsCmd]]((notice: Box[NoticeType.Value], id: String) => Empty){}
/**
* Holds user functions that willbe executed very early in the request processing. The functions'
* result will be ignored.
*/
val early = RulesSeq[(HTTPRequest) => Any]
/**
* Holds user functions that are executed before sending the response to client. The functions'
* result will be ignored.
*/
val beforeSend = RulesSeq[(BasicResponse, HTTPResponse, List[(String, String)], Box[Req]) => Any]
/**
* 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
* 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.
*/
val httpAuthProtectedResource = RulesSeq[HttpAuthProtectedResourcePF]
/**
* The HTTP authentication mechanism that ift will perform. See <i>LiftRules.protectedResource
*/
@volatile var authentication: HttpAuthentication = NoAuthentication
/**
* A function that takes the HTTPSession and the contextPath as parameters
* and returns a LiftSession reference. This can be used in cases subclassing
* LiftSession is necessary.
*/
@volatile var sessionCreator: (HTTPSession, String) => LiftSession = {
case (httpSession, contextPath) => new LiftSession(contextPath, httpSession.sessionId, Full(httpSession))
}
/**
* A method that returns a function to create migratory sessions. If you want migratory sessions for your
* application, <code>LiftRules.sessionCreator = LiftRules.sessionCreatorForMigratorySessions
*/
def sessionCreatorForMigratorySessions: (HTTPSession, String) => LiftSession = {
case (httpSession, contextPath) => new LiftSession(contextPath, httpSession.sessionId, Full(httpSession)) with MigratorySession
}
@volatile var enableContainerSessions = true
@volatile var getLiftSession: (Req) => LiftSession = (req) => _getLiftSession(req)
/**
* Attached 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
* implementation could user per resource MD5 sequences thus
* "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
*
*/
@volatile var attachResourceId: (String) => String = (name) => {
name + (if (name contains ("?")) "&" else "?") + pageResourceId + "=_"
}
/**
* Returns a LiftSession instance.
*/
private def _getLiftSession(req: Req): LiftSession = {
val wp = req.path.wholePath
val cometSessionId =
if (wp.length >= 3 && wp.head == LiftRules.cometPath)
Full(wp(2))
else
Empty
val ret = SessionMaster.getSession(req, cometSessionId) match {
case Full(ret) =>
ret.fixSessionTime()
ret
case _ =>
val ret = LiftSession(req)
ret.fixSessionTime()
SessionMaster.addSession(ret, req,
req.request.userAgent,
SessionMaster.getIpFromReq(req))
ret
}
makeCometBreakoutDecision(ret, req)
ret
}
/**
* A function that takes appropriate action in breaking out of any
* existing comet requests based on the request, browser type, etc.
*/
@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)
// get the maximum requests given the browser type
val max = maxConcurrentRequests.vend(req) - 2 // this request and any open comet requests
// dump the oldest requests
which.drop(max).foreach {
case (actor, req) => actor ! BreakOut()
}
}
/**
* The path to handle served resources
*/
@volatile var resourceServerPath = "classpath"
/**
* Holds the JS library specific UI artifacts. By efault it uses JQuery's artifacts
*/
@volatile var jsArtifacts: JSArtifacts = JQuery13Artifacts
/**
* Use this PartialFunction to to automatically add static URL parameters
* to any URL reference from the markup of Ajax request.
*/
val urlDecorate = RulesSeq[URLDecoratorPF]
/**
* Should the JSESSIONID be encoded in the URL if cookies are
* not supported
*/
@volatile var encodeJSessionIdInUrl_? = false
/**
* Partial function to allow you to build a CometActor from code rather than via reflection
*/
val cometCreation = RulesSeq[CometCreationPF]
private def noComet(ignore: CometCreationInfo): Box[LiftCometActor] = Empty
/**
* A factory that will vend comet creators
*/
val cometCreationFactory: FactoryMaker[CometCreationInfo => Box[LiftCometActor]] =
new FactoryMaker(() => noComet _) {}
/**
* Should codes that represent entities be converted to XML
* entities when rendered?
*/
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")
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
* SessionVars, updating function tables, etc.) the developer will
* receive a notice and the operation will not complete.
*/
val statelessReqTest = RulesSeq[StatelessReqTestPF]
val statelessSession: FactoryMaker[Req => LiftSession with StatelessSession] =
new FactoryMaker((req: Req) => new LiftSession(req.contextPath,
Helpers.nextFuncName,
Empty) with
StatelessSession) {}
/**
* Holds user functions that are executed after the response was sent to client. The functions' result
* will be ignored.
*/
val afterSend = RulesSeq[(BasicResponse, HTTPResponse, List[(String, String)], Box[Req]) => Any]
/**
* Calculate the Comet Server (by default, the server that
* the request was made on, but can do the multi-server thing
* as well)
*/
@volatile var cometServer: () => String = () => S.contextPath
/**
* The maximum concurrent requests. If this number of
* requests are being serviced for a given session, messages
* will be sent to all Comet requests to terminate
*/
val maxConcurrentRequests: FactoryMaker[Req => Int] = new FactoryMaker((x: Req) => x match {
case r if r.isFirefox35_+ || r.isIE8 || r.isIE9 || r.isChrome3_+ || r.isOpera9 || r.isSafari3_+ => 5
case _ => 2
}) {}
/**
* A partial function that determines content type based on an incoming
* Req and Accept header
*/
@volatile var determineContentType: PartialFunction[(Box[Req], Box[String]), String] = {
case (_, Full(accept)) if this.useXhtmlMimeType && accept.toLowerCase.contains("application/xhtml+xml") =>
"application/xhtml+xml; charset=utf-8"
case _ => "text/html; charset=utf-8"
}
lazy val liftVersion: String = {
val cn = """\.""".r.replaceAllIn(LiftRules.getClass.getName, "/")
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")
str <- tryo(new String(readWholeStream(newUrl.openConnection.getInputStream), "UTF-8"))
ma <- """Implementation-Version: (.*)""".r.findFirstMatchIn(str)
} yield ma.group(1)
ret openOr "Unknown Lift Version"
}
lazy val liftBuildDate: Date = {
val cn = """\.""".r.replaceAllIn(LiftRules.getClass.getName, "/")
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")
str <- tryo(new String(readWholeStream(newUrl.openConnection.getInputStream), "UTF-8"))
ma <- """Build-Time: (.*)""".r.findFirstMatchIn(str)
asLong <- asLong(ma.group(1))
} yield new Date(asLong)
ret openOr new Date(0L)
}
/**
* Hooks to be run when LiftServlet.destroy is called.
*/
val unloadHooks = RulesSeq[() => Unit]
/**
* For each unload hook registered, run them during destroy()
*/
private[http] def runUnloadHooks() {
unloadHooks.toList.foreach{f =>
tryo{f()}
}
}
/**
* 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 {
case _ if S.skipDocType => Empty
case _ if S.getDocType._1 => S.getDocType._2
case _ => Full(DocType.xhtmlTransitional)
}){}
/**
* The maximum allowed size of a complete mime multi-part POST. Default 8MB
*/
@volatile var maxMimeSize: Long = 8 * 1024 * 1024
/**
* Should pages that are not found be passed along the request processing chain to the
* next handler outside Lift?
*/
@volatile var passNotFoundToChain = false
/**
* The maximum allowed size of a single file in a mime multi-part POST.
* Default 7MB
*/
@volatile var maxMimeFileSize: Long = 7 * 1024 * 1024
/**
* The function referenced here is called if there's a localization lookup failure
*/
@volatile var localizationLookupFailureNotice: Box[(String, Locale) => Unit] = Empty
/**
* Set to false if you want to have 404's handled the same way in dev and production mode
*/
@volatile var displayHelpfulSiteMapMessages_? = true
/**
* The default location to send people if SiteMap access control fails. The path is
* expressed a a List[String]
*/
@volatile var siteMapFailRedirectLocation: List[String] = List()
private[http] def notFoundOrIgnore(requestState: Req, session: Box[LiftSession]): Box[LiftResponse] = {
if (passNotFoundToChain) Empty
else session match {
case Full(session) => Full(session.checkRedirect(requestState.createNotFound))
case _ => Full(requestState.createNotFound)
}
}
/**
* Allows user adding additional Lift tags (the tags must be prefixed by lift namespace such as <lift:xxxx/>).
* Each LiftTagPF function will be called with the folowing parameters:
* <pre>
* - Element label,
* - The Element itselft,
* - The attrbutes
* - The child nodes
* - The page name
* </pre>
*/
val liftTagProcessing = RulesSeq[LiftTagPF]
/**
* If you don't want lift to send the application/xhtml+xml mime type to those browsers
* that understand it, then set this to { @code false }
*/
@volatile var useXhtmlMimeType: Boolean = true
private def _stringToXml(s: String): NodeSeq = Text(s)
/**
* A function that defines how a String should be converted to XML
* for the localization stuff. By default, Text(s) is returned,
* but you can change this to attempt to parse the XML in the String and
* return the NodeSeq.
*/
@volatile var localizeStringToXml: String => NodeSeq = _stringToXml _
/**
* The base name of the resource bundle
*/
@volatile var resourceNames: List[String] = List("lift")
/**
* This function is called to convert the current set of Notices into
* a JsCmd that will be executed on the client to display the Notices.
*
* @see net.liftweb.builtin.snippet.Msgs
*/
@volatile var noticesToJsCmd: () => JsCmd = () => {
import builtin.snippet.{Msg,Msgs,MsgErrorMeta,MsgNoticeMeta,MsgWarningMeta}
// A "wrapper" that simply returns the javascript
val passJs = (in : JsCmd) => in
// Delegate to Msgs for fadeout and effects
def noticesFadeOut(noticeType: NoticeType.Value): JsCmd =
Msgs.noticesFadeOut(noticeType, Noop, passJs)
def groupEffects(noticeType: NoticeType.Value): JsCmd =
Msgs.effects(Full(noticeType), noticeType.id, Noop, passJs)
def idEffects(id : String): JsCmd =
Msgs.effects(Empty, id, Noop, passJs)
// Compute the global notices first
val groupMessages = Msgs.renderNotices() match {
case NodeSeq.Empty => JsCmds.Noop
case xml => LiftRules.jsArtifacts.setHtml(LiftRules.noticesContainerId, xml) &
noticesFadeOut(NoticeType.Notice) &
noticesFadeOut(NoticeType.Warning) &
noticesFadeOut(NoticeType.Error) &
groupEffects(NoticeType.Notice) &
groupEffects(NoticeType.Warning) &
groupEffects(NoticeType.Error)
}
// We need to determine the full set of IDs that need messages rendered.
// TODO: Change to use "distinct" when 2.7.7 support is dropped
val idSet = (S.idMessages((S.errors)) ++
S.idMessages((S.warnings)) ++
S.idMessages((S.notices))).map(_._1).distinct
// Merge each Id's messages and effects into the JsCmd chain
idSet.foldLeft(groupMessages) {
(chain,id) => chain &
LiftRules.jsArtifacts.setHtml(id, Msg.renderIdMsgs(id)) &
idEffects(id)
}
}
/**
* The base name of the resource bundle of the lift core code
*/
@volatile var liftCoreResourceName = "i18n.lift-core"
/**
* Where to send the user if there's no comet session
*/
@volatile var noCometSessionPage = "/"
/**
* Put a function that will calculate the request timeout based on the
* incoming request.
*/
@volatile var calcRequestTimeout: Box[Req => Int] = Empty
/**
* If you want the standard (non-AJAX) request timeout to be something other than
* 10 seconds, put the value here
*/
@volatile var stdRequestTimeout: Box[Int] = Empty
/**
* If you want the AJAX request timeout to be something other than 120 seconds, put the value here
*/
@volatile var cometRequestTimeout: Box[Int] = Empty
/**
* If a Comet request fails timeout for this period of time. Default value is 10 seconds
*/
@volatile var cometFailureRetryTimeout: Long = 10 seconds
/**
* The dispatcher that takes a Snippet and converts it to a
* DispatchSnippet instance
*/
val snippetDispatch = RulesSeq[SnippetDispatchPF]
/**
* Function that generates variants on snippet names to search for, given the name from the template.
* The default implementation just returns name :: Nil (e.g. no change).
* The names are searched in order.
* See also searchSnippetsWithRequestPath for an implementation.
*/
@volatile var snippetNamesToSearch: FactoryMaker[String => List[String]] =
new FactoryMaker(() => (name: String) => name :: Nil) {}
/**
* 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:
* LiftRules.snippetNamesToSearch.default.set(() => LiftRules.searchSnippetsWithRequestPath)
* LiftRules.addToPackages("com.mycompany.myapp")
* LiftRules.addToPackages("com.mycompany.mylib")
* The tag <lift:MySnippet> in template foo/bar/baz.html would search for the snippet in the following locations:
* - com.mycompany.myapp.snippet.foo.bar.MySnippet
* - com.mycompany.myapp.snippet.MySnippet
* - com.mycompany.mylib.snippet.foo.bar.MySnippet
* - com.mycompany.mylib.snippet.MySnippet
* - and then the Lift builtin snippet packages
*/
def searchSnippetsWithRequestPath(name: String): List[String] =
S.request.map(_.path.partPath.dropRight(1)) match {
case Full(xs) if !xs.isEmpty => (xs.mkString(".") + "." + name) :: name :: Nil
case _ => name :: Nil
}
/**
* Change this variable to set view dispatching
*/
val viewDispatch = RulesSeq[ViewDispatchPF]
private[http] def snippet(name: String): Box[DispatchSnippet] = NamedPF.applyBox(name, snippetDispatch.toList)
/**
* If the request times out (or returns a non-Response) you can
* intercept the response here and create your own response
*/
@volatile var requestTimedOut: Box[(Req, Any) => Box[LiftResponse]] = Empty
/**
* A function that takes the current HTTP request and returns the current
*/
@volatile var timeZoneCalculator: Box[HTTPRequest] => TimeZone = defaultTimeZoneCalculator _
def defaultTimeZoneCalculator(request: Box[HTTPRequest]): TimeZone = TimeZone.getDefault
/**
* How many times do we retry an Ajax command before calling it a failure?
*/
@volatile var ajaxRetryCount: Box[Int] = Empty
/**
* The JavaScript to execute at the begining of an
* Ajax request (for example, showing the spinning working thingy)
*/
@volatile var ajaxStart: Box[() => JsCmd] = Empty
import FuncJBridge._
/**
* Set the Ajax end JavaScript function. The
* Java calable alternative to assigning the var ajaxStart
*/
def setAjaxStart(f: Func0[JsCmd]): Unit = {
ajaxStart = Full(f: () => JsCmd)
}
/**
* The function that calculates if the response should be rendered in
* IE6/7 compatibility mode
*/
@volatile var calcIEMode: () => Boolean =
() => (for (r <- S.request) yield r.isIE6 || r.isIE7 ||
r.isIE8) openOr true
/**
* The JavaScript to execute at the end of an
* Ajax request (for example, removing the spinning working thingy)
*/
@volatile var ajaxEnd: Box[() => JsCmd] = Empty
/**
* Set the Ajax end JavaScript function. The
* Java calable alternative to assigning the var ajaxEnd
*/
def setAjaxEnd(f: Func0[JsCmd]): Unit = {
ajaxEnd = Full(f: () => JsCmd)
}
/**
* An XML header is inserted at the very beginning of returned XHTML pages.
* This function defines the cases in which such a header is inserted. The
* function takes a NodeResponse (the LiftResponse that is converting the
* XML to a stream of bytes), the Node (root node of the XML), and
* a Box containing the content type.
*/
@volatile var calculateXmlHeader: (NodeResponse, Node, Box[String]) => String = {
case _ if S.skipXmlHeader => ""
case (_, up: Unparsed, _) => ""
case (_, _, Empty) | (_, _, Failure(_, _, _)) =>
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
case (_, _, Full(s)) if (s.toLowerCase.startsWith("text/html")) =>
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
case (_, _, Full(s)) if (s.toLowerCase.startsWith("text/xml") ||
s.toLowerCase.startsWith("text/xhtml") ||
s.toLowerCase.startsWith("application/xml") ||
s.toLowerCase.startsWith("application/xhtml+xml")) =>
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
case _ => ""
}
/**
* The default action to take when the JavaScript action fails
*/
@volatile var ajaxDefaultFailure: Box[() => JsCmd] =
Full(() => JsCmds.Alert(S.??("ajax.error")))
/**
* A function that takes the current HTTP request and returns the current
*/
@volatile var localeCalculator: Box[HTTPRequest] => Locale = defaultLocaleCalculator _
def defaultLocaleCalculator(request: Box[HTTPRequest]) =
request.flatMap(_.locale).openOr(Locale.getDefault())
val resourceBundleFactories = RulesSeq[ResourceBundleFactoryPF]
/**
* Given the current location (based on the Req.path.partPath),
* what are the resource bundles in the templates for the current
* page.
*
* @see DefaultRoutines.resourceForCurrentLoc()
*/
val resourceForCurrentLoc: FactoryMaker[() => List[ResourceBundle]] =
new FactoryMaker(() => () => DefaultRoutines.resourceForCurrentReq()) {}
private var _sitemap: Box[SiteMap] = Empty
private var sitemapFunc: Box[() => SiteMap] = Empty
private object sitemapRequestVar extends TransientRequestVar(resolveSitemap())
/**
* Set the sitemap to a function that will be run to generate the sitemap.
*
* This allows for changing the SiteMap when in development mode and having
* the function re-run for each request.<br/>
*
* This is **NOT** a mechanism for dynamic SiteMap. This is a mechanism
* **ONLY** for allowing you to change the SiteMap during development.
* There will be significant performance penalties (serializing the
* service of requests... only one at a time) for changing the SiteMap.
*/
def setSiteMapFunc(smf: () => SiteMap) {
sitemapFunc = Full(smf)
if (!Props.devMode) {
resolveSitemap()
}
}
/**
* Define the sitemap.
*/
def setSiteMap(sm: SiteMap) {
this.setSiteMapFunc(() => sm)
}
private def runAsSafe[T](f: => T): T = synchronized {
val old = _doneBoot
try {
_doneBoot = false
f
} finally {
_doneBoot = old
}
}
private case class PerRequestPF[A, B](f: PartialFunction[A, B]) extends PartialFunction[A, B] {
def isDefinedAt(a: A) = f.isDefinedAt(a)
def apply(a: A) = f(a)
}
private def resolveSitemap(): Box[SiteMap] = {
this.synchronized {
runAsSafe {
sitemapFunc.flatMap {
smf =>
LiftRules.statefulRewrite.remove {
case PerRequestPF(_) => true
case _ => false
}
val sm = smf()
_sitemap = Full(sm)
for (menu <- sm.menus;
val loc = menu.loc;
rewrite <- loc.rewritePF) LiftRules.statefulRewrite.append(PerRequestPF(rewrite))
_sitemap
}
}
}
}
/**
* Return the sitemap if set in Boot. If the current runMode is development
* mode, the sitemap may be recomputed on each page load.
*/
def siteMap: Box[SiteMap] = if (Props.devMode) {
this.synchronized {
sitemapRequestVar.is
}
} else _sitemap
/**
* 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))
*/
val htmlProperties: FactoryMaker[Req => HtmlProperties] =
new FactoryMaker(() => (r: Req) => (new OldHtmlProperties(r.userAgent): HtmlProperties)) {}
/**
* How long should we wait for all the lazy snippets to render
*/
val lazySnippetTimeout: FactoryMaker[TimeSpan] = new FactoryMaker(() => 30 seconds) {}
/**
* Does the current context support parallel snippet execution
*/
val allowParallelSnippets: FactoryMaker[Boolean] = new FactoryMaker(() => false) {}
/**
* Update the function here that calculates particular paths to
* exclused from context path rewriting
*/
val excludePathFromContextPathRewriting: FactoryMaker[String => Boolean] =
new FactoryMaker(() => ((s: String) => false)) {}
/**
* If a deferred snippet has a failure during render,
* what should we display?
*/
val deferredSnippetFailure: FactoryMaker[Failure => NodeSeq] =
new FactoryMaker(() => {
failure: Failure => {
if (Props.devMode)
<div style="border: red solid 2px">A lift:parallel snippet failed to render.Message:{failure.msg}{failure.exception match {
case Full(e) =>
<pre>{e.getStackTrace.map(_.toString).mkString("\n")}
case _ => NodeSeq.Empty
}}<i>note: this error is displayed in the browser because
your application is running in "development" mode.If you
set the system property run.mode=production, this error will not
be displayed, but there will be errors in the output logs.
</i>
</div>
else NodeSeq.Empty
}
}) {}
/**
* If a deferred snippet has a failure during render,
* what should we display?
*/
val deferredSnippetTimeout: FactoryMaker[NodeSeq] =
new FactoryMaker(() => {
if (Props.devMode)
<div style="border: red solid 2px">
A deferred snippet timed out during render.
<i>note: this error is displayed in the browser because
your application is running in "development" mode. If you
set the system property run.mode=production, this error will not
be displayed, but there will be errors in the output logs.
</i>
</div>
else NodeSeq.Empty
}) {}
/**
* Should comments be stripped from the served XHTML
*/
val stripComments: FactoryMaker[Boolean] =
new FactoryMaker(() => {
if (Props.devMode)
false
else true
}) {}
private[http] val reqCnt = new AtomicInteger(0)
@volatile private[http] var ending = false
private[http] def bootFinished() {
_doneBoot = true
}
/**
* Holds user's DispatchPF functions that will be executed in a stateless context. This means that
* S object is not availble yet.
*/
val statelessDispatchTable = RulesSeq[DispatchPF]
private[http] def dispatchTable(req: HTTPRequest): List[DispatchPF] = {
req match {
case null => dispatch.toList
case _ => SessionMaster.getSession(req, Empty) match {
case Full(s) => S.initIfUninitted(s) {
S.highLevelSessionDispatchList.map(_.dispatch) :::
dispatch.toList
}
case _ => dispatch.toList
}
}
}
/**
* Contains the Ajax URI path used by Lift to process Ajax requests.
*/
@volatile var ajaxPath = "ajax_request"
/**
* Contains the Comet URI path used by Lift to process Comet requests.
*/
@volatile var cometPath = "comet_request"
/**
* Computes the Comet path by adding additional tokens on top of cometPath
*/
@volatile var calcCometPath: String => JsExp = prefix => {
Str(prefix + "/" + cometPath + "/") +
JsRaw("Math.floor(Math.random() * 100000000000)") +
Str(S.session.map(session => S.encodeURL("/" + session.uniqueId)) openOr "xx") + Str("/") + JsRaw("lift_page")
}
/**
* If there is an alternative way of calculating the context path
* (by default returning Empty)
*
* If this function returns an Empty, the contextPath provided by the container will be used.
*
*/
@volatile var calculateContextPath: () => Box[String] = () => Empty
@volatile private var _context: HTTPContext = _
/**
* Should an exception be thrown on out of scope Session and RequestVar
* access. By default, no.
*/
@volatile var throwOnOutOfScopeVarAccess: Boolean = false
/**
* 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
*/
@volatile var devModeFailureResponseCodeOverride: Box[Int] = Empty
/**
* Returns the HTTPContext
*/
def context: HTTPContext = synchronized {_context}
/**
* Sets the HTTPContext
*/
def setContext(in: HTTPContext): Unit = synchronized {
if (in ne _context) {
_context = in
}
}
private var otherPackages: List[String] = Nil
/**
* Used by Lift to construct full pacakge names fromthe packages provided to addToPackages function
*/
def buildPackage(end: String) = otherPackages.map(_ + "." + end)
/**
* Tells Lift where to find Snippets,Views, Comet Actors and Lift ORM Model object
*/
def addToPackages(what: String) {
if (doneBoot) throw new IllegalStateException("Cannot modify after boot.");
otherPackages = what :: otherPackages
}
/**
* 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.");
otherPackages = what.getName :: otherPackages
}
private val defaultFinder = getClass.getResource _
private def resourceFinder(name: String): java.net.URL = if (null eq _context) null else _context.resource(name)
/**
* Obtain the resource URL by name
*/
@volatile var getResource: String => Box[java.net.URL] = defaultGetResource _
/**
* Obtain the resource URL by name
*/
def defaultGetResource(name: String): Box[java.net.URL] =
for{
rf <- (Box !! resourceFinder(name)) or (Box !! defaultFinder(name))
} yield rf
/**
* Open a resource by name and process its contents using the supplied function.
*/
def doWithResource[T](name: String)(f: InputStream => T): Box[T] =
getResource(name) map { _.openStream } map { is => try { f(is) } finally { is.close } }
/**
* Obtain the resource as an array of bytes by name
*/
def loadResource(name: String): Box[Array[Byte]] = doWithResource(name) { stream =>
val buffer = new Array[Byte](2048)
val out = new ByteArrayOutputStream
def reader {
val len = stream.read(buffer)
if (len < 0) return
else if (len > 0) out.write(buffer, 0, len)
reader
}
reader
out.toByteArray
}
/**
* Obtain the resource as an XML by name. If you're using this to load a template, consider using
* the Template object instead.
*
* @see Template
*/
def loadResourceAsXml(name: String): Box[NodeSeq] = loadResourceAsString(name).flatMap(s => PCDataXmlParser(s))
/**
* Obtain the resource as a String by name
*/
def loadResourceAsString(name: String): Box[String] = loadResource(name).map(s => new String(s, "UTF-8"))
/**
* Get the partial function that defines if a request should be handled by
* the application (rather than the default container handler)
*/
val liftRequest = RulesSeq[LiftRequestPF]
/**
* Holds the user's DispatchPF functions that will be executed in stateful context
*/
val dispatch = RulesSeq[DispatchPF]
/**
* Holds the user's rewrite functions that can alter the URI parts and query parameters. This rewrite
* is performed very early in the HTTP request cycle and may not include any state. This rewrite is meant
* to rewrite requests for statelessDispatch. <br/>
* Note also that rewrites should not have side effects except
* to memoize database query results. No side effects means that you should not change SessionVars
* in a rewrite.
*/
val statelessRewrite = RulesSeq[RewritePF]
/**
* Use statelessRewrite or statefuleRewrite
*/
@deprecated("Use statelessRewrite or statefuleRewrite")
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
* information is available. <br/>
* Note also that rewrites should not have side effects except
* to memoize database query results. No side effects means that you should not change SessionVars
* in a rewrite. <br/>
* In general, rewrites should be considered low level access. Rather than using a rewrite to extract
* parameters out of a URL, you'll be much better off using SiteMap generally and Menu.param and Menu.params
* specifically for extracting parameters from URLs.
*/
val statefulRewrite = RulesSeq[RewritePF]
/**
* Holds the user's snippet functions that will be executed by lift given a certain path.
*/
val snippets = RulesSeq[SnippetPF]
/**
* Execute certain functions early in a Stateful Request
*/
val earlyInStateful = RulesSeq[Box[Req] => Unit]
/**
* Execute certain functions early in a Stateful Request
*/
val earlyInStateless = RulesSeq[Box[Req] => Unit]
private var _configureLogging: () => Unit = _
/**
* Holds the function that configures logging. Must be set before any loggers are created
*/
def configureLogging: () => Unit = _configureLogging
/**
* Holds the function that configures logging. Must be set before any loggers are created
*/
def configureLogging_=(newConfigurer: () => Unit): Unit = {
_configureLogging = newConfigurer
Logger.setup = Full(newConfigurer)
}
configureLogging = net.liftweb.util.LoggingAutoConfigurer()
private val _cometLogger: FatLazy[Logger] = FatLazy({
val ret = Logger("comet_trace")
ret
})
/**
* Holds the CometLogger that will be used to log comet activity
*/
def cometLogger: Logger = _cometLogger.get
/**
* Holds the CometLogger that will be used to log comet activity
*/
def cometLogger_=(newLogger: Logger): Unit = _cometLogger.set(newLogger)
/**
* Takes a Node, headers, cookies, and a session and turns it into an XhtmlResponse.
*/
private def cvt(ns: Node, headers: List[(String, String)], cookies: List[HTTPCookie], req: Req, code:Int) =
convertResponse({
val ret = XhtmlResponse(ns,
/*LiftRules.docType.vend(req)*/S.htmlProperties.docType,
headers, cookies, code,
S.ieMode)
ret._includeXmlVersion = !S.skipDocType
ret
}, headers, cookies, req)
@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",
"Pragma" -> "no-cache" )
}
/**
* Runs responseTransformers
*/
def performTransform(in: LiftResponse): LiftResponse = responseTransformers.toList.foldLeft(in) {
case (in, pf: PartialFunction[_, _]) =>
if (pf.isDefinedAt(in)) pf(in) else in
case (in, f) => f(in)
}
/**
* Holds the user's transformer functions allowing the user to modify a LiftResponse before sending it to client.
*/
val responseTransformers = RulesSeq[LiftResponse => LiftResponse]
/**
* convertResponse is a PartialFunction that reduces a given Tuple4 into a
* LiftResponse that can then be sent to the browser.
*/
var convertResponse: PartialFunction[(Any, List[(String, String)], List[HTTPCookie], Req), LiftResponse] = {
case (r: LiftResponse, _, _, _) => r
case (ns: Group, headers, cookies, req) => cvt(ns, headers, cookies, req, 200)
case (ns: Node, headers, cookies, req) => cvt(ns, headers, cookies, req, 200)
case (ns: NodeSeq, headers, cookies, req) => cvt(Group(ns), headers, cookies, req, 200)
case ((ns: NodeSeq, code: Int), headers, cookies, req) => cvt(Group(ns), headers, cookies, req, code)
case (SafeNodeSeq(n), headers, cookies, req) => cvt(Group(n), headers, cookies, req, 200)
case (Full(o), headers, cookies, req) => convertResponse((o, headers, cookies, req))
case (Some(o), headers, cookies, req) => convertResponse((o, headers, cookies, req))
case (bad, _, _, req) => req.createNotFound
}
/**
* Set a snippet failure handler here. The class and method for the snippet are passed in
*/
val snippetFailedFunc = RulesSeq[SnippetFailure => Unit].prepend(logSnippetFailure _)
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
/**
* The sequence of partial functions (pattern matching) for handling converting an exception to something to
* 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
* a default implementation is already appended.
*
*/
@volatile var exceptionHandler = RulesSeq[ExceptionHandlerPF].append {
case (Props.RunModes.Development, r, e) =>
logger.error("Exception being returned to browser when processing " + r.uri.toString + ": " + showException(e))
XhtmlResponse((<html> Exception occured while processing {r.uri} {showException(e)} |