LOGO SPEAKER‘S COMPANY
Scala for SlingBuilding RESTful Web Applications with Scala for Sling
http://people.apache.org/~mduerig/scala4sling/
Michael Dürig
Day Software AG
10080
2
AGENDA
> Introduction
> What is Apache Sling?
> What is Scala?
> Scala for Sling
> Summary and questions
3
Introduction
> Michael Dürig
– Developer for Day Software
– http://michid.wordpress.com/
> Michael Marth
– Technology Evangelist for Day Software
– http://dev.day.com/
4
Overview
> What to expect
– Proof of concept
– Experimental code
> What not to expect
– Product showcase, tutorial
– Live coding (demo code available from
http://people.apache.org/~mduerig/scala4sling/)
> Prerequisites
– Basic understanding of Java content repositories (JCR)
– Prior exposure to Scala a plus
5
AGENDA
> Introduction
> What is Apache Sling?
> What is Scala?
> Scala for Sling
> Summary and questions
6
Sling builds on JCR
> Web application framework for JCR
– JCR (JSR-170/JSR-283): Apache Jackrabbit
– OSGi-based: Apache Felix
– http://incubator.apache.org/sling/
> Scriptable application layer for JCR
– JSR-223: Scripting for the Java platform
> REST over JCR
– Content resolution for mapping request URLs to JCR nodes
– Servlet resolution for mapping JCR nodes to request handlers (i.e. scripts)
7
URL decomposition
GET /forum/scala4sling.thread.html
8
URL decomposition
GET /forum/scala4sling.thread.html
Repository
9
URL decomposition
GET /forum/scala4sling.thread.html
RepositoryRepository path
10
URL decomposition
GET /forum/scala4sling.thread.html
RepositoryRepository path
Application selection
11
URL decomposition
GET /forum/scala4sling.thread.html
RepositoryRepository path Script selection
Application selection
12
AGENDA
> Introduction
> What is Apache Sling?
> What is Scala?
> Scala for Sling
> Summary and questions
13
Scala builds on the JVM
> Multi-paradigm language for the JVM
– Conceived by Martin Odersky and his group (EPFL, Lausanne)
– Fully interoperable with Java
– IDE plugins for Eclipse and IntelliJ IDEA
– http://www.scala-lang.org/
> Concise, elegant, and type safe
– Touch and feel of a genuine scripting language
– Smoothly integrates object oriented and functional features
– Great for creating DSLs
> Values
> Type parameters
14
Type inference: the scripting touch
val x = 42 // x has type Int
val s = x.toString // s has type String
val q = s.substring(s) // type mismatch; found String, required Int
class Pair[S, T](s: S, t: T)
def makePair[S, T](s: S, t: T) = new Pair(s, t)
val p = makePair(42.0, "Scala") // p has type Pair[Double, String]
> Values
> Type parameters
15
Type inference: the scripting touch
val x = 42 // x has type Int
val s = x.toString // s has type String
val q = s.substring(s) // type mismatch; found String, required Int
class Pair[S, T](s: S, t: T)
def makePair[S, T](s: S, t: T) = new Pair(s, t)
val p = makePair(42.0, "Scala") // p has type Pair[Double, String]
class Pair<S, T> {
public final S s;
public final T t;
public Pair(S s, T t) {
super();
this.s = s;
this.t = t;
}
}
public <S, T> Pair<S, T> makePair(S s, T t) {
return new Pair<S, T>(s, t);
}
public final Pair<Double, String> p = makePair(42.0, "Scala");
16
XML <pre>literals</pre>
> HTML? Scala!val title = "Hello Jazoon 09"
println {
<html>
<body>
<h1>{ title }</h1>
{
(for (c <- title) yield c)
.mkString(" ")
}
</body>
</html>
}
17
XML <pre>literals</pre>
> HTML? Scala!val title = "Hello Jazoon 09"
println {
<html>
<body>
<h1>{ title }</h1>
{
(for (c <- title) yield c)
.mkString(" ")
}
</body>
</html>
}
println {
Elem(null, "html", Null, TopScope,
Elem(null, "body", Null, TopScope,
Elem(null, "h1", Null, TopScope,
Text(title)
),
Text(
(for (c <- title) yield c)
.mkString(" ")
)
)
)
}
18
XML <pre>literals</pre>
> HTML? Scala!val title = "Hello Jazoon 09"
println {
<html>
<body>
<h1>{ title }</h1>
{
(for (c <- title) yield c)
.mkString(" ")
}
</body>
</html>
}
<html>
<body>
<h1>Hello Jazoon 09</h1>
H e l l o J a z o o n 0 9
</body>
</html>
19
Implicits: pimp my library
> Implicit conversion
> Déjà vu?
– Similar to extension methods in C#
– Similar to conversion constructors in C++
– Equivalent to type classes in Haskell
implicit def translate(s: String) = new {
def toGerman = s match {
case "Example" => "Beispiel"
// ...
case _ => throw new Exception("No translation for " + s)
}
}
val german = "Example".toGerman
20
Objects are functions are objects…
> Object as function
> Function as object
object Twice {
def apply(n: Int) = 2*n
}
println(Twice(21)) // prints 42
def twice(n: Int) = 2*n
val doubler = twice(_)
println(doubler(21)) // prints 42
21
AGENDA
> Introduction
> What is Apache Sling?
> What is Scala?
> Scala for Sling
> Summary and questions
22
Demo application: Forum
package forum {
object html {
import html_Bindings._
// ...
println {
<html>
<body>
Welcome to the { currentNode("name") } forum
— { Calendar.getInstance.getTime }
{ ThreadNewForm.render }
{ ThreadOverview.render(currentNode) }
</body>
</html>
}
}
23
Forum: html.scala
package forum {
object html {
import html_Bindings._
// ...
println {
<html>
<body>
Welcome to the { currentNode("name") } forum
— { Calendar.getInstance.getTime }
{ ThreadNewForm.render }
{ ThreadOverview.render(currentNode) }
</body>
</html>
}
}
GET /forum.html
24
Forum: html.scala
package forum {
object html {
import html_Bindings._
// ...
println {
<html>
<body>
Welcome to the { currentNode("name") } forum
— { Calendar.getInstance.getTime }
{ ThreadNewForm.render }
{ ThreadOverview.render(currentNode) }
</body>
</html>
}
}
Put Sling variables into scope
(currentNode, request, response, etc.)
25
Forum: html.scala
package forum {
object html {
import html_Bindings._
// ...
println {
<html>
<body>
Welcome to the { currentNode("name") } forum
— { Calendar.getInstance.getTime }
{ ThreadNewForm.render }
{ ThreadOverview.render(currentNode) }
</body>
</html>
}
}
/**
* Print out an object followed by a new line character.
* @param x the object to print.
*/
def println(x: Any): Unit = out.println(x)
26
Forum: html.scala
package forum {
object html {
import html_Bindings._
// ...
println {
<html>
<body>
Welcome to the { currentNode("name") } forum
— { Calendar.getInstance.getTime }
{ ThreadNewForm.render }
{ ThreadOverview.render(currentNode) }
</body>
</html>
}
}
/**
* Print out an object followed by a new line character.
* @param x the object to print.
*/
def println(x: Any): Unit = out.println(x)
27
Forum: html.scala
package forum {
object html {
import html_Bindings._
// ...
println {
<html>
<body>
Welcome to the { currentNode("name") } forum
— { Calendar.getInstance.getTime }
{ ThreadNewForm.render }
{ ThreadOverview.render(currentNode) }
</body>
</html>
}
}
28
Forum: html.scala
implicit def rich(node: Node) = new {
def apply(property: String) = node.getProperty(property).getString
...
}
object ThreadOverview {
// imports omitted
def render(node: Node) =
emptyUnless(node.hasNodes) {
<h1>threads</h1>
<ul>{ node.nodes map toListItem }</ul>
}
private def toListItem(node: Node) = {
<li>
{ node("subject") }
(<a href={ node.path + ".thread.html"}>show thread</a>)
</li>
}
}
29
Forum: thread overview
object ThreadOverview {
// imports omitted
def render(node: Node) =
emptyUnless(node.hasNodes) {
<h1>threads</h1>
<ul>{ node.nodes map toListItem }</ul>
}
private def toListItem(node: Node) = {
<li>
{ node("subject") }
(<a href={ node.path + ".thread.html"}>show thread</a>)
</li>
}
}
30
Forum: thread overview
def emptyUnless(condition: Boolean)(block: => NodeSeq) =
if (condition) block
else Empty
object ThreadNewForm {
def render = {
<h1>start a new thread</h1>
<form action="/content/forum/*" method="POST"
enctype="multipart/form-data">
subject <input name="subject" type="text" />
<textarea name="body"></textarea>
logo <input name="logo“ type="file" />
<input type="submit" value="save" />
<input name=":redirect" value="/content/forum.html" type="hidden" />
</form>
}
}
31
Forum: form data
object ThreadNewForm {
def render = {
<h1>start a new thread</h1>
<form action="/content/forum/*" method="POST"
enctype="multipart/form-data">
subject <input name="subject" type="text" />
<textarea name="body"></textarea>
logo <input name="logo“ type="file" />
<input type="submit" value="save" />
<input name=":redirect" value="/content/forum.html" type="hidden" />
</form>
}
}
32
Forum: form data
object ThreadNewForm {
def render = {
<h1>start a new thread</h1>
<form action="/content/forum/*" method="POST"
enctype="multipart/form-data">
subject <input name="subject" type="text" />
<textarea name="body"></textarea>
logo <input name="logo“ type="file" />
<input type="submit" value="save" />
<input name=":redirect" value="/content/forum.html" type="hidden" />
</form>
}
}
33
Forum: form data
POST should add new child node to /content/forum/
object ThreadNewForm {
def render = {
<h1>start a new thread</h1>
<form action="/content/forum/*" method="POST"
enctype="multipart/form-data">
subject <input name="subject" type="text" />
<textarea name="body"></textarea>
logo <input name="logo“ type="file" />
<input type="submit" value="save" />
<input name=":redirect" value="/content/forum.html" type="hidden" />
</form>
}
}
34
Forum: form data
POST should add new child node to /content/forum/
... with properties subject and body of
type String,
object ThreadNewForm {
def render = {
<h1>start a new thread</h1>
<form action="/content/forum/*" method="POST"
enctype="multipart/form-data">
subject <input name="subject" type="text" />
<textarea name="body"></textarea>
logo <input name="logo“ type="file" />
<input type="submit" value="save" />
<input name=":redirect" value="/content/forum.html" type="hidden" />
</form>
}
}
35
Forum: form data
POST should add new child node to /content/forum/
... with properties subject and body of
type String,
... and a child node logo of type nt:file.
object ThreadNewForm {
def render = {
<h1>start a new thread</h1>
<form action="/content/forum/*" method="POST"
enctype="multipart/form-data">
subject <input name="subject" type="text" />
<textarea name="body"></textarea>
logo <input name="logo“ type="file" />
<input type="submit" value="save" />
<input name=":redirect" value="/content/forum.html" type="hidden" />
</form>
}
}
36
Forum: form data
Redirect to forum.html on success
37
Extracting form fields
> Sling post servlet
– No action required: used by default
– Creates nodes and properties for form fields
– Tweaked with hidden form fields
– Sensible defaults
> Custom POST request handler
– Write a script POST.scala
– Process form fields in code
– Full control over request processing
38
Forum: custom POST request handlerpackage forum {
object POST {
import POST_Bindings._
// ...
val node = addNodes(session.root, newPath)
node.setProperty("body", request("body"))
node.setProperty(“subject", request(“subject"))
if (request("logo") != "") {
val logoNode = node.addNode("logo", "nt:file")
val contentNode = logoNode.addNode("jcr:content", "nt:resource")
val logo = request.getRequestParameter("logo")
contentNode.setProperty("jcr:lastModified", Calendar.getInstance)
contentNode.setProperty("jcr:mimeType", logo.getContentType)
contentNode.setProperty("jcr:data", logo.getInputStream)
}
session.save
response.sendRedirect(request(":redirect"))
}
}
39
Forum: custom POST request handlerpackage forum {
object POST {
import POST_Bindings._
// ...
val node = addNodes(session.root, newPath)
node.setProperty("body", request("body"))
node.setProperty(“subject", request(“subject"))
if (request("logo") != "") {
val logoNode = node.addNode("logo", "nt:file")
val contentNode = logoNode.addNode("jcr:content", "nt:resource")
val logo = request.getRequestParameter("logo")
contentNode.setProperty("jcr:lastModified", Calendar.getInstance)
contentNode.setProperty("jcr:mimeType", logo.getContentType)
contentNode.setProperty("jcr:data", logo.getInputStream)
}
session.save
response.sendRedirect(request(":redirect"))
}
}
POST /forum.html
40
Forum: custom POST request handlerpackage forum {
object POST {
import POST_Bindings._
// ...
val node = addNodes(session.root, newPath)
node.setProperty("body", request("body"))
node.setProperty(“subject", request(“subject"))
if (request("logo") != "") {
val logoNode = node.addNode("logo", "nt:file")
val contentNode = logoNode.addNode("jcr:content", "nt:resource")
val logo = request.getRequestParameter("logo")
contentNode.setProperty("jcr:lastModified", Calendar.getInstance)
contentNode.setProperty("jcr:mimeType", logo.getContentType)
contentNode.setProperty("jcr:data", logo.getInputStream)
}
session.save
response.sendRedirect(request(":redirect"))
}
}
Add node for this post
41
Forum: custom POST request handlerpackage forum {
object POST {
import POST_Bindings._
// ...
val node = addNodes(session.root, newPath)
node.setProperty("body", request("body"))
node.setProperty(“subject", request(“subject"))
if (request("logo") != "") {
val logoNode = node.addNode("logo", "nt:file")
val contentNode = logoNode.addNode("jcr:content", "nt:resource")
val logo = request.getRequestParameter("logo")
contentNode.setProperty("jcr:lastModified", Calendar.getInstance)
contentNode.setProperty("jcr:mimeType", logo.getContentType)
contentNode.setProperty("jcr:data", logo.getInputStream)
}
session.save
response.sendRedirect(request(":redirect"))
}
}
Set properties for body and subject
42
Forum: custom POST request handlerpackage forum {
object POST {
import POST_Bindings._
// ...
val node = addNodes(session.root, newPath)
node.setProperty("body", request("body"))
node.setProperty(“subject", request(“subject"))
if (request("logo") != "") {
val logoNode = node.addNode("logo", "nt:file")
val contentNode = logoNode.addNode("jcr:content", "nt:resource")
val logo = request.getRequestParameter("logo")
contentNode.setProperty("jcr:lastModified", Calendar.getInstance)
contentNode.setProperty("jcr:mimeType", logo.getContentType)
contentNode.setProperty("jcr:data", logo.getInputStream)
}
session.save
response.sendRedirect(request(":redirect"))
}
}
If the request contains a logo
43
Forum: custom POST request handlerpackage forum {
object POST {
import POST_Bindings._
// ...
val node = addNodes(session.root, newPath)
node.setProperty("body", request("body"))
node.setProperty(“subject", request(“subject"))
if (request("logo") != "") {
val logoNode = node.addNode("logo", "nt:file")
val contentNode = logoNode.addNode("jcr:content", "nt:resource")
val logo = request.getRequestParameter("logo")
contentNode.setProperty("jcr:lastModified", Calendar.getInstance)
contentNode.setProperty("jcr:mimeType", logo.getContentType)
contentNode.setProperty("jcr:data", logo.getInputStream)
}
session.save
response.sendRedirect(request(":redirect"))
}
}
If the request contains a logo
... add a child node logo of type nt:file
44
Forum: custom POST request handlerpackage forum {
object POST {
import POST_Bindings._
// ...
val node = addNodes(session.root, newPath)
node.setProperty("body", request("body"))
node.setProperty(“subject", request(“subject"))
if (request("logo") != "") {
val logoNode = node.addNode("logo", "nt:file")
val contentNode = logoNode.addNode("jcr:content", "nt:resource")
val logo = request.getRequestParameter("logo")
contentNode.setProperty("jcr:lastModified", Calendar.getInstance)
contentNode.setProperty("jcr:mimeType", logo.getContentType)
contentNode.setProperty("jcr:data", logo.getInputStream)
}
session.save
response.sendRedirect(request(":redirect"))
}
}
If the request contains a logo
... add a child node logo of type nt:file
... and set properties jcr:lastModified,
jcr:mimeType and jcr:data
45
Forum: custom POST request handlerpackage forum {
object POST {
import POST_Bindings._
// ...
val node = addNodes(session.root, newPath)
node.setProperty("body", request("body"))
node.setProperty(“subject", request(“subject"))
if (request("logo") != "") {
val logoNode = node.addNode("logo", "nt:file")
val contentNode = logoNode.addNode("jcr:content", "nt:resource")
val logo = request.getRequestParameter("logo")
contentNode.setProperty("jcr:lastModified", Calendar.getInstance)
contentNode.setProperty("jcr:mimeType", logo.getContentType)
contentNode.setProperty("jcr:data", logo.getInputStream)
}
session.save
response.sendRedirect(request(":redirect"))
}
}
Save changes and send redirect
object html_Bindings extends MockBindings {
override def currentNode = new MockNode
with MockItem
override def request = new MockSlingHttpServletRequest
with MockHttpServletRequest
with MockServletRequest
}
object Test extends Application {
forum.html
}
46
Forum: unit testing
object html_Bindings extends MockBindings {
override def currentNode = new MockNode
with MockItem
override def request = new MockSlingHttpServletRequest
with MockHttpServletRequest
with MockServletRequest
}
object Test extends Application {
forum.html
}
47
Forum: unit testing
object html_Bindings extends MockBindings {
override def currentNode = new MockNode
with MockItem
override def request = new MockSlingHttpServletRequest
with MockHttpServletRequest
with MockServletRequest
}
object Test extends Application {
forum.html
}
48
Forum: unit testing
object html_Bindings extends MockBindings {
override def currentNode = new MockNode
with MockItem
override def request = new MockSlingHttpServletRequest
with MockHttpServletRequest
with MockServletRequest
}
object Test extends Application {
forum.html
}
49
Forum: unit testing<html>
<head>
<link href="/apps/forum/static/blue.css" rel="stylesheet"></link>
</head>
<body>
<div id="Header">
Welcome to the forum
— Wed Jun 17 17:12:48 CEST 2009
</div>
<div id="Menu">
<p>search all threads:
<form action="/content/forum.search.html"
enctype="multipart/form-data" method="GET">
<input value="" type="text" size="10" name="query"></input>
<input value="search" type="submit"></input>
</form>
</p>
</div>
<div id="Content">
<h1>start a new thread</h1>
<span id="inp">
<form action="/content/forum/*" enctype="multipart/form-data" method="POST">
<p>subject</p>
<p><input type="text" name="subject"></input></p>
<p><textarea name="body"></textarea></p>
<p>logo</p>
<p><input type="file" name="logo"></input></p>
<p><input value="save" type="submit"></input></p>
<input value="/content/forum.html" type="hidden" name=":redirect"></input>
</form>
</span>
</div>
</body>
</html>
50
AGENDA
> Introduction
> What is Apache Sling?
> What is Scala?
> Scala for Sling
> Summary and questions
51
Conclusion
> Advantages
– Scala!
– No language boundary
– Tool support (i.e. IDE, ScalaDoc,
safe refactoring, unit testing)
> Disadvantages
– IDE support shaky, improves
quickly though
– Not much refactoring support as
of today
52
Conclusion
> Advantages
– Scala!
– No language boundary
– Tool support (i.e. IDE, ScalaDoc,
safe refactoring, unit testing)
> Disadvantages
– IDE support shaky, improves
quickly though
– Not much refactoring support as
of today
53
Conclusion
> Advantages
– Scala!
– No language boundary
– Tool support (i.e. IDE, ScalaDoc,
safe refactoring, unit testing)
> Disadvantages
– IDE support shaky, improves
quickly though
– Not much refactoring support as
of today
<p>Welcome to { currentNode("name") } forum</p>
— { Calendar.getInstance.getTime }
{ ThreadNewForm.render }
{ ThreadOverview.render(currentNode) }
54
Conclusion
> Advantages
– Scala!
– No language boundary
– Tool support (i.e. IDE, ScalaDoc,
safe refactoring, unit testing)
> Disadvantages
– IDE support shaky, improves
quickly though
– Not much refactoring support as
of today
55
Conclusion
> Advantages
– Scala!
– No language boundary
– Tool support (i.e. IDE, ScalaDoc,
safe refactoring, unit testing)
> Disadvantages
– IDE support shaky, improves
quickly though
– Not much refactoring support as
of today
56
Conclusion
> Advantages
– Scala!
– No language boundary
– Tool support (i.e. IDE, ScalaDoc,
safe refactoring, unit testing)
> Disadvantages
– IDE support shaky, improves
quickly though
– Not much refactoring support as
of today
object html_Bindings {
def currentNode = new MockNode
...
}
object Test extends Application {
forum.html
}
57
Conclusion
> Advantages
– Scala!
– No language boundary
– Tool support (i.e. IDE, ScalaDoc,
safe refactoring, unit testing)
> Disadvantages
– IDE support shaky, improves
quickly though
– Not much refactoring support as
of today
58
Summary
> Apache Sling
– Great for building RESTful web applications
– Pluggable scripting support (JSR-223)
> Scala
– Great for scripting
– Supports «on the fly» templates through XML literals
– Easy unit testing
LOGO SPEAKER‘S COMPANY
Michael Dürig [email protected]
Michael Marth [email protected]
Day Software AG http://www.day.com/
References:
• Scala for Sling: http://people.apache.org/~mduerig/scala4sling/
• The Scala programming language: http://www.scala-lang.org/
• Apache Sling: http://incubator.apache.org/sling/
Top Related