1. Scala for Sling
Building RESTful Web Applications with Scala for Sling
http://people.apache.org/~mduerig/scala4sling/
Michael Dürig
Day Software AG
10080
LOGO SPEAKER‘S COMPANY
3. 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. 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
13. 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
14. 14
Type inference: the scripting touch
> Values
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
> Type parameters
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]
15. 15
Type inference: the scripting touch
> Values
val x = 42 // x has type Int
val s = x.toString // s has type String
val q = s.substring(s) //
class Pair<S, T> type mismatch; found String, required Int
{
public final S s;
public final T t;
> Type parameters
public Pair(S s, T t) {
super();
class Pair[S, T](s: S, t: =T)
this.s s;
def makePair[S, T](s: S, t: t; = new Pair(s, t)
this.t = T)
}
val p = makePair(42.0, "Scala") // p has type Pair[Double, String]
}
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. 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>
}
18. 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. 19
Implicits: pimp my library
> Implicit conversion
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
> Déjà vu?
– Similar to extension methods in C#
– Similar to conversion constructors in C++
– Equivalent to type classes in Haskell
20. 20
Objects are functions are objects…
> Object as function
object Twice {
def apply(n: Int) = 2*n
}
println(Twice(21)) // prints 42
> Function as object
def twice(n: Int) = 2*n
val doubler = twice(_)
println(doubler(21)) // prints 42
23. 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>
}
}
24. 24
Forum: html.scala
GET /forum.html
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>
}
}
25. 25
Forum: html.scala
package forum {
object html {
Put Sling variables into scope
import html_Bindings._
(currentNode, request, response, etc.)
// ...
println {
<html>
<body>
Welcome to the { currentNode("name") } forum
— { Calendar.getInstance.getTime }
{ ThreadNewForm.render }
{ ThreadOverview.render(currentNode) }
</body>
</html>
}
}
26. 26
Forum: html.scala
package forum {
object html {
/**
import html_Bindings._
// ... * Print out an object followed by a new line character.
* @param x the object to print.
*/
println { def println(x: Any): Unit = out.println(x)
<html>
<body>
Welcome to the { currentNode("name") } forum
— { Calendar.getInstance.getTime }
{ ThreadNewForm.render }
{ ThreadOverview.render(currentNode) }
</body>
</html>
}
}
27. 27
Forum: html.scala
package forum {
object html {
/**
import html_Bindings._
// ... * Print out an object followed by a new line character.
* @param x the object to print.
*/
println { def println(x: Any): Unit = out.println(x)
<html>
<body>
Welcome to the { currentNode("name") } forum
— { Calendar.getInstance.getTime }
{ ThreadNewForm.render }
{ ThreadOverview.render(currentNode) }
</body>
</html>
}
}
28. 28
Forum: html.scala
package forum {
object html {
import html_Bindings._
// implicit def rich(node: Node) = new {
...
def apply(property: String) = node.getProperty(property).getString
...
println {
}
<html>
<body>
Welcome to the { currentNode("name") } forum
— { Calendar.getInstance.getTime }
{ ThreadNewForm.render }
{ ThreadOverview.render(currentNode) }
</body>
</html>
}
}
31. 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. 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. 33
Forum: form data
object ThreadNewForm {
def render = {
POST should add new child node to
/content/forum/
<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. 34
Forum: form data
object ThreadNewForm {
def render = {
POST should add new child node to
/content/forum/
<h1>start a new thread</h1>
<form action="/content/forum/*" with properties subject and body
... method="POST" of
enctype="multipart/form-data">
type String,
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. 35
Forum: form data
object ThreadNewForm {
def render = {
POST should add new child node to
/content/forum/
<h1>start a new thread</h1>
<form action="/content/forum/*" with properties subject and body
... method="POST" of
enctype="multipart/form-data">
type String,
subject <input name="subject" and a child node logo of type nt:file.
... 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. 36
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>
} Redirect to forum.html on success
}
37. 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. 38
Forum: custom POST request handler
package 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. 39
Forum: custom POST request handler
package forum { POST /forum.html
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"))
}
}
40. 40
Forum: custom POST request handler
package forum {
object POST {
import POST_Bindings._
// ... Add node for this post
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"))
}
}
41. 41
Forum: custom POST request handler
package forum {
object POST {
import POST_Bindings._
// ...
Set properties for body and subject
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"))
}
}
42. 42
Forum: custom POST request handler
package forum {
object POST {
import POST_Bindings._
// ...
val node = addNodes(session.root, newPath)
If the request contains a logo
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"))
}
}
43. 43
Forum: custom POST request handler
package forum {
object POST {
import POST_Bindings._
// ...
val node = addNodes(session.root, newPath)
If the request contains a logo
node.setProperty("body", request("body"))
node.setProperty(“subject", request(“subject"))
... add a child node logo of type nt:file
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"))
}
}
44. 44
Forum: custom POST request handler
package forum {
object POST {
import POST_Bindings._
// ...
val node = addNodes(session.root, newPath)
If the request contains a logo
node.setProperty("body", request("body"))
node.setProperty(“subject", request(“subject"))
... add a child node logo of type nt:file
if (request("logo") != "") {
val logoNode = node.addNode("logo", set properties jcr:lastModified,
... and "nt:file")
val contentNode = logoNode.addNode("jcr:content", "nt:resource")
jcr:mimeType and jcr:data
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"))
}
}
45. 45
Forum: custom POST request handler
package 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)
Save changes and send redirect
}
session.save
response.sendRedirect(request(":redirect"))
}
}
46. 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. 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. 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. 49
Forum: unit testing
<html>
<head>
<link href="/apps/forum/static/blue.css" rel="stylesheet"></link>
object html_Bindings extends MockBindings {
</head>
<body>
override def currentNode = new MockNode
<div id="Header">
Welcome to the forum
with MockItem
— Wed Jun 17 17:12:48 CEST 2009
</div>
<div id="Menu">
override def <p>search all threads:
request = new MockSlingHttpServletRequest
<form action="/content/forum.search.html"
with MockHttpServletRequest
enctype="multipart/form-data" method="GET">
<input value="" type="text" size="10" name="query"></input>
with MockServletRequest
<input value="search" type="submit"></input>
} </form>
</p>
</div>
<div id="Content">
object Test extends Application {
<h1>start a new thread</h1>
<span id="inp">
forum.html <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>
51. 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. 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. 53
Conclusion
> Advantages
– Scala!
<p>Welcome to { currentNode("name") } forum</p>
– No language boundary — { Calendar.getInstance.getTime }
{ ThreadNewForm.render }
– Tool support (i.e. IDE, ScalaDoc, { ThreadOverview.render(currentNode) }
safe refactoring, unit testing)
> Disadvantages
– IDE support shaky, improves
quickly though
– Not much refactoring support as
of today
54. 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. 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. 56
Conclusion
> Advantages
– Scala!
– No language boundary
object html_Bindings {
– Tool support (i.e. IDE, ScalaDoc, def currentNode = new MockNode
safe refactoring, unit testing) ...
}
object Test extends Application {
> Disadvantages forum.html
}
– IDE support shaky, improves
quickly though
– Not much refactoring support as
of today
57. 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. 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
59. Michael Dürig michael.duerig@day.com
Michael Marth michael.marth@day.com
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/
LOGO SPEAKER‘S COMPANY