skinny.engine.scalate.ScalateSupport.scala Maven / Gradle / Ivy
package skinny.engine.scalate
import scala.language.reflectiveCalls
import scala.language.implicitConversions
import java.io.{ PrintWriter, StringWriter }
import javax.servlet.http.{ HttpServletRequest, HttpServletResponse }
import javax.servlet.{ FilterConfig, ServletConfig }
import org.fusesource.scalate.layout.DefaultLayoutStrategy
import org.fusesource.scalate.servlet.ServletTemplateEngine
import org.fusesource.scalate.support.TemplateFinder
import org.fusesource.scalate.{ Binding, TemplateEngine }
import skinny.engine.SkinnyEngineBase
import skinny.engine.context.SkinnyEngineContext
import scala.collection.concurrent.{ Map => CMap, TrieMap }
import scala.collection.mutable
object ScalateSupport {
val DefaultLayouts = Seq(
"/WEB-INF/templates/layouts/default",
"/WEB-INF/layouts/default",
"/WEB-INF/scalate/layouts/default"
)
private def setLayoutStrategy(engine: TemplateEngine) = {
val layouts = for {
base <- ScalateSupport.DefaultLayouts
extension <- TemplateEngine.templateTypes
} yield ("%s.%s".format(base, extension))
engine.layoutStrategy = new DefaultLayoutStrategy(engine, layouts: _*)
}
private val TemplateAttributesKey = "skinny.engine.scalate.ScalateSupport.TemplateAttributes"
private val templateEngineInstances: CMap[String, TemplateEngine] = new TrieMap[String, TemplateEngine]
def scalateTemplateEngine(ctx: String, init: => TemplateEngine): TemplateEngine = {
templateEngineInstances.get(ctx).getOrElse {
val engine = init
engine.workingDirectory = new java.io.File(engine.workingDirectory, ctx)
templateEngineInstances.putIfAbsent(ctx, engine).getOrElse(engine)
}
}
}
/**
* ScalateSupport creates and configures a template engine and provides
* helper methods and bindings to integrate with the ServletBase.
*/
trait ScalateSupport extends SkinnyEngineBase {
/**
* The template engine used by the methods in this support class. It
* provides a lower-level interface to Scalate and may be used directly
* to circumvent the conventions imposed by the helpers in this class.
* For instance, paths passed directly to the template engine are not
* run through `findTemplate`.
*/
protected[skinny] var templateEngine: TemplateEngine = _
abstract override def initialize(config: ConfigT) {
super.initialize(config)
templateEngine = createTemplateEngine(config)
}
abstract override def shutdown() {
if (templateEngine != null) templateEngine.compiler.shutdown()
super.shutdown()
}
/**
* Creates the templateEngine from the config. There is little reason to
* override this unless you have created a ServletBase extension outside
* an HttpServlet or Filter.
*/
protected def createTemplateEngine(config: ConfigT): TemplateEngine = {
val ctx = config.getServletContext.getContextPath match {
case "" => "ROOT"
case path => path.substring(1)
}
config match {
case servletConfig: ServletConfig =>
ScalateSupport.scalateTemplateEngine(ctx, new ServletTemplateEngine(servletConfig) with SkinnyEngineTemplateEngine)
case filterConfig: FilterConfig =>
ScalateSupport.scalateTemplateEngine(ctx, new ServletTemplateEngine(filterConfig) with SkinnyEngineTemplateEngine)
case _ =>
// Don't know how to convert your Config to something that
// ServletTemplateEngine can accept, so fall back to a TemplateEngine
ScalateSupport.scalateTemplateEngine(ctx, new TemplateEngine with SkinnyEngineTemplateEngine)
}
}
/**
* A TemplateEngine integrated with SkinnyEngine.
*
* A SkinnyEngineTemplateEngine looks for layouts in `/WEB-INF/templates/layouts` before
* searching in `/WEB-INF/layouts` and `/WEB-INF/scalate/layouts`.
*/
trait SkinnyEngineTemplateEngine {
this: TemplateEngine =>
/**
* Delegates to the ServletBase's isDevelopmentMode flag.
*/
override def isDevelopmentMode = ScalateSupport.this.isDevelopmentMode
ScalateSupport.setLayoutStrategy(this)
templateDirectories = defaultTemplatePath
bindings ::= Binding("context", "_root_." + classOf[SkinnyEngineRenderContext].getName, importMembers = true, isImplicit = true)
importStatements ::= "import skinny.engine.implicits.ServletApiImplicits._"
}
/**
* Creates a render context to be used by default in the template engine.
*
* Returns a SkinnyEngineRenderContext by default in order to bind some other
* framework variables (e.g., multiParams, flash). SkinnyEngineTemplateEngine
* assumes this returns SkinnyEngineRenderContext in its binding of "context".
* If you return something other than a SkinnyEngineRenderContext, you will
* also want to redefine that binding.
*/
protected def createRenderContext(
req: HttpServletRequest,
resp: HttpServletResponse,
out: PrintWriter)(implicit ctx: SkinnyEngineContext): SkinnyEngineRenderContext = {
new SkinnyEngineRenderContext(this, ctx, templateEngine, out, req, resp)
}
implicit def skinnyEngineRenderContext(implicit ctx: SkinnyEngineContext): SkinnyEngineRenderContext = {
new SkinnyEngineRenderContext(
this, ctx, templateEngine, response.getWriter, request, response)
}
/**
* Flag whether the Scalate error page is enabled. If true, uncaught
* exceptions will be caught and rendered by the Scalate error page.
*
* The default is true.
*/
protected def isScalateErrorPageEnabled = true
abstract override def handle(req: HttpServletRequest, res: HttpServletResponse) {
super.handle(req, res)
}
override protected def renderUncaughtException(e: Throwable)(
implicit ctx: SkinnyEngineContext): Unit = {
if (isScalateErrorPageEnabled) renderScalateErrorPage(ctx.request, ctx.response, e)
else super.renderUncaughtException(e)(ctx)
}
// Hack: Have to pass it the request and response, because we're outside the
// scope of the super handler.
private[this] def renderScalateErrorPage(req: HttpServletRequest, resp: HttpServletResponse, e: Throwable) = {
resp.setStatus(500)
resp.setContentType("text/html")
val errorPage = templateEngine.load("/WEB-INF/scalate/errors/500.scaml")
val ctx = createRenderContext(req, resp, resp.getWriter)(context)
ctx.setAttribute("javax.servlet.error.exception", Some(e))
templateEngine.layout(errorPage, ctx)
}
/**
* The default index page when the path is a directory.
*/
protected def defaultIndexName: String = "index"
/**
* The default template format.
*/
protected def defaultTemplateFormat: String = "jade"
/**
* The default path to search for templates. Left as a def so it can be
* read from the servletContext in initialize, but you probably want a
* constant.
*
* Defaults to:
* - `/WEB-INF/templates/views` (recommended)
* - `/WEB-INF/views` (used by previous SkinnyEngine quickstarts)
* - `/WEB-INF/scalate/templates` (used by previous SkinnyEngine quickstarts)
*/
protected def defaultTemplatePath: List[String] =
List(
"/WEB-INF/templates/views",
"/WEB-INF/views",
"/WEB-INF/scalate/templates"
)
/**
* The default path to search for templates. Left as a def so it can be
* read from the servletContext in initialize, but you probably want a
* constant.
*
* Defaults to:
* - `/WEB-INF/templates/views` (recommended)
* - `/WEB-INF/views` (used by previous SkinnyEngine quickstarts)
* - `/WEB-INF/scalate/templates` (used by previous SkinnyEngine quickstarts)
*/
protected def defaultLayoutPath: Option[String] = None
/**
* Convenience method for `layoutTemplateAs("jade")`.
*/
protected def jade(path: String, attributes: (String, Any)*)(
implicit ctx: SkinnyEngineContext, renderCtx: SkinnyEngineRenderContext): String = {
layoutTemplateAs(Set("jade"))(path, attributes: _*)(ctx, renderCtx)
}
/**
* Convenience method for `layoutTemplateAs("scaml")`.
*/
protected def scaml(path: String, attributes: (String, Any)*)(
implicit ctx: SkinnyEngineContext, renderCtx: SkinnyEngineRenderContext): String = {
layoutTemplateAs(Set("scaml"))(path, attributes: _*)(ctx, renderCtx)
}
/**
* Convenience method for `layoutTemplateAs("ssp")`.
*/
protected def ssp(path: String, attributes: (String, Any)*)(
implicit ctx: SkinnyEngineContext, renderCtx: SkinnyEngineRenderContext): String = {
layoutTemplateAs(Set("ssp"))(path, attributes: _*)(ctx, renderCtx)
}
/**
* Convenience method for `layoutTemplateAs("mustache")`.
*/
protected def mustache(path: String, attributes: (String, Any)*)(
implicit ctx: SkinnyEngineContext, renderCtx: SkinnyEngineRenderContext): String = {
layoutTemplateAs(Set("mustache"))(path, attributes: _*)(ctx, renderCtx)
}
/**
* Finds and renders a template with the current layout strategy,
* returning the result.
*
* @param ext The extensions to look for a template.
* @param path The path of the template, passed to `findTemplate`.
* @param attributes Attributes to path to the render context. Disable
* layouts by passing `layout -> ""`.
*/
protected def layoutTemplateAs(ext: Set[String])(path: String, attributes: (String, Any)*)(
implicit ctx: SkinnyEngineContext, renderCtx: SkinnyEngineRenderContext): String = {
val uri = findTemplate(path, ext).getOrElse(path)
val buffer = new StringWriter()
val out = new PrintWriter(buffer)
val context = createRenderContext(ctx.request, ctx.response, out)(ctx)
val attrs = {
templateAttributes(ctx) ++
(defaultLayoutPath map (p => Map("layout" -> p) ++
Map(attributes: _*)) getOrElse Map(attributes: _*))
}
attrs foreach { case (k, v) => context.attributes(k) = v }
templateEngine.layout(uri, context)
buffer.toString
}
/**
* Finds and renders a template with the current layout strategy,
* looking for all known extensions, returning the result.
*
* @param path The path of the template, passed to `findTemplate`.
* @param attributes Attributes to path to the render context. Disable
* layouts by passing `layout -> ""`.
*/
protected def layoutTemplate(path: String, attributes: (String, Any)*)(
implicit ctx: SkinnyEngineContext, renderCtx: SkinnyEngineRenderContext): String = {
layoutTemplateAs(templateEngine.extensions)(path, attributes: _*)(ctx, renderCtx)
}
/**
* Finds a template for a path. Delegates to a TemplateFinder, and if
* that fails, tries again with `/defaultIndexName` appended.
*/
protected def findTemplate(path: String, extensionSet: Set[String] = templateEngine.extensions): Option[String] = {
val finder = new TemplateFinder(templateEngine) {
override lazy val extensions = extensionSet
}
finder.findTemplate(("/" + path).replaceAll("//", "/")) orElse
finder.findTemplate("/%s/%s".format(path, defaultIndexName))
}
/**
* A request-scoped map of attributes to pass to the template. This map
* will be set to any render context created with the `createRenderContext`
* method.
*/
protected def templateAttributes(implicit ctx: SkinnyEngineContext): mutable.Map[String, Any] = {
ctx.request.getOrElseUpdate(ScalateSupport.TemplateAttributesKey, mutable.Map.empty)
.asInstanceOf[mutable.Map[String, Any]]
}
protected def templateAttributes(key: String)(implicit ctx: SkinnyEngineContext): Any = {
templateAttributes(ctx)(key)
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy