Skip to content

Commit

Permalink
Merge branch 'master' into http-pipelining
Browse files Browse the repository at this point in the history
  • Loading branch information
huntc committed Apr 12, 2013
2 parents 710b724 + dd671f4 commit d280228
Show file tree
Hide file tree
Showing 73 changed files with 638 additions and 289 deletions.
2 changes: 1 addition & 1 deletion documentation/manual/Home.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
## Hacking Play

1. [[Building Play from source | BuildingFromSource]]
1. [[CI server at Cloudbees | CIServer]]
1. [[3rd Party Tooling | ThirdPartyTools]]
1. [[Repositories | Repositories]]
1. [[Issue tracker | Issues]]
1. [[Contributor guidelines | Guidelines]]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ $ heroku open

## Connecting to a database

Heroku provides a number of relational an NoSQL databases through [Heroku Add-ons](http://addons.heroku.com). Play applications on Heroku are automatically provisioned a [Heroku Postgres](https://addons.heroku.com/heroku-postgresql) database. To configure your Play 2 application to use the Heroku Postgres database, first add the PostgreSQL JDBC driver to your application dependencies (`project/Build.scala`):
Heroku provides a number of relational and NoSQL databases through [Heroku Add-ons](http://addons.heroku.com). Play applications on Heroku are automatically provisioned a [Heroku Postgres](https://addons.heroku.com/heroku-postgresql) database. To configure your Play 2 application to use the Heroku Postgres database, first add the PostgreSQL JDBC driver to your application dependencies (`project/Build.scala`):

```scala
"postgresql" % "postgresql" % "9.1-901-1.jdbc4"
Expand Down
7 changes: 0 additions & 7 deletions documentation/manual/hacking/CIServer.md

This file was deleted.

15 changes: 15 additions & 0 deletions documentation/manual/hacking/ThirdPartyTools.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
A big THANK YOU! to these sponsors for their support of open source projects.

# Continuous Integration

[[images/cloudbees.png]]

Our continuous integration runs on [[Cloudbees|http://www.cloudbees.com/]]. We not only run CI on major release and master branches, but we also perform github pull request validation using CloudBees functionality.

[[https://playframework2.ci.cloudbees.com/]]

# Profiling

[[images/yourkit.png]]

We are using [[YourKit|http://www.yourkit.com/overview/index.jsp]] for profiling our Java and Scala code. YourKit really helps us keep Play's resource usage to the minimum that you'd expect.
2 changes: 1 addition & 1 deletion documentation/manual/hacking/_Sidebar.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
### Hacking Play

- [[Building Play from source | BuildingFromSource]]
- [[CI server at Cloudbees | CIServer]]
- [[3rd Party Tools | ThirdPartyTools]]
- [[Repositories | Repositories]]
- [[Issues tracker | Issues]]
- [[Contributor guidelines | Guidelines]]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added documentation/manual/hacking/images/yourkit.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Adding support for a custom format to the template engine

The built-in template engine supports common template formats (HTML, XML, etc.) but you can easily add support for your own formats, if needed. This page summarizes the steps to follow to support a custom format.

## Overview of the the templating process

The template engine builds its result by appending static and dynamic content parts of a template. Consider for instance the following template:

```
foo @bar baz
```

It consists in two static parts (`foo ` and ` baz`) around one dynamic part (`bar`). The template engine concatenates these parts together to build its result. Actually, in order to prevent cross-site scripting attacks, the value of `bar` can be escaped before being concatenated to the rest of the result. This escaping process is specific to each format: e.g. in the case of HTML you want to transform “<” into “&amp;lt;”.

How does the template engine know which format correspond to a template file? It looks at its extension: e.g. if it ends with `.scala.html` it associates the HTML format to the file.

In summary, to support your own template format you need to perform the following steps:

* Implement the text integration process for the format ;
* Associate a file extension to the format.

## Implement a format

Implement the `play.templates.Format<A>` interface that has the methods `A raw(String text)` and `A escape(String text)` that will be used to integrate static and dynamic template parts, respectively.

The type parameter `A` of the format defines the result type of the template rendering, e.g. `Html` for a HTML template. This type must be a subtype of the `play.templates.Appendable<A>` trait that defines how to concatenates parts together.

For convenience, Play provides a `play.api.templates.BufferedContent<A>` abstract class that implements `play.templates.Appendable<A>` using a `StringBuilder` to build its result and that implements the `play.mvc.Content` interface so Play knows how to serialize it as an HTTP response body.

In short, you need to write to classes: one defining the result (implementing `play.templates.Appendable<A>`) and one defining the text integration process (implementing `play.templates.Format<A>`). For instance, here is how the HTML format could be defined:

```java
public class Html extends BufferedContent<Html> {
public Html(StringBuilder buffer) {
super(buffer);
}
String contentType() {
return "text/html";
}
}

public class HtmlFormat implements Format<Html> {
Html raw(String text: String) { … }
Html escape(String text) { … }
public static final HtmlFormat instance = new HtmlFormat(); // The build process needs a static reference to the format (see the next section)
}
```

## Associate a file extension to the format

The templates are compiled into a `.scala` files by the build process just before compiling the whole application sources. The `sbt.PlayKeys.templatesTypes` key is a sbt setting of type `Map[String, String]` defining the mapping between file extensions and template formats. For instance, if you want Play to use your onw HTML format implementation you have to write the following in your build file to associate the `.scala.html` files to your custom `my.HtmlFormat` format:

```scala
templatesTypes += ("html" -> "my.HtmlFormat.instance")
```

Note that the right side of the arrow contains the fully qualified name of a static value of type `play.templates.Format<?>`.

> **Next:** [[HTTP form submission and validation | JavaForms]]
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ Again, there’s nothing special here. You can just call any other template you
```

## moreScripts and moreStyles equivalents

> **Next:** [[HTTP form submission and validation | ScalaForms]]
To define old moreScripts or moreStyles variables equivalents (like on Play! 1.x) on a Scala template, you can define a variable in the main template like this :

```html
Expand Down Expand Up @@ -183,4 +183,4 @@ And on an extended template that not need an extra script, just like this :
}
```

> **Next:** [[HTTP form submission and validation | JavaForms]]
> **Next:** [[Custom formats | JavaCustomTemplateFormat]]
1 change: 1 addition & 0 deletions documentation/manual/javaGuide/main/templates/_Sidebar.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

- [[Templates syntax | JavaTemplates]]
- [[Common use cases | JavaTemplateUseCases]]
- [[Custom formats | JavaCustomTemplateFormat]]

### Main concepts

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Adding support for a custom format to the template engine

The built-in template engine supports common template formats (HTML, XML, etc.) but you can easily add support for your own formats, if needed. This page summarizes the steps to follow to support a custom format.

## Overview of the the templating process

The template engine builds its result by appending static and dynamic content parts of a template. Consider for instance the following template:

```
foo @bar baz
```

It consists in two static parts (`foo ` and ` baz`) around one dynamic part (`bar`). The template engine concatenates these parts together to build its result. Actually, in order to prevent cross-site scripting attacks, the value of `bar` can be escaped before being concatenated to the rest of the result. This escaping process is specific to each format: e.g. in the case of HTML you want to transform “<” into “&amp;lt;”.

How does the template engine know which format correspond to a template file? It looks at its extension: e.g. if it ends with `.scala.html` it associates the HTML format to the file.

Finally, you usually want your template files to be used as the body of your HTTP responses, so you have to define how to make a Play result from a template rendering result.

In summary, to support your own template format you need to perform the following steps:

* Implement the text integration process for the format ;
* Associate a file extension to the format ;
* Eventually tell Play how to send the result of a template rendering as an HTTP response body.

## Implement a format

Implement the `play.templates.Format[A]` trait that has the methods `raw(text: String): A` and `escape(text: String): A` that will be used to integrate static and dynamic template parts, respectively.

The type parameter `A` of the format defines the result type of the template rendering, e.g. `Html` for a HTML template. This type must be a subtype of the `play.templates.Appendable[A]` trait that defines how to concatenates parts together.

For convenience, Play provides a `play.api.templates.BufferedContent[A]` abstract class that implements `play.templates.Appendable[A]` using a `StringBuilder` to build its result and that implements the `play.api.mvc.Content` trait so Play knows how to serialize it as an HTTP response body (see the last section of this page for details).

In short, you need to write to classes: one defining the result (implementing `play.templates.Appendable[A]`) and one defining the text integration process (implementing `play.templates.Format[A]`). For instance, here is how the HTML format is defined:

```scala
// The `Html` result type. We extend `BufferedContent[Html]` rather than just `Appendable[Html]` so
// Play knows how to make an HTTP result from a `Html` value
class Html(buffer: StringBuilder) extends BufferedContent[Html](buffer) {
val contentType = MimeTypes.HTML
}

object HtmlFormat extends Format[Html] {
def raw(text: String): Html =
def escape(text: String): Html =
}
```

## Associate a file extension to the format

The templates are compiled into a `.scala` files by the build process just before compiling the whole application sources. The `sbt.PlayKeys.templatesTypes` key is a sbt setting of type `Map[String, String]` defining the mapping between file extensions and template formats. For instance, if HTML was not supported out of the box by Play, you would have to write the following in your build file to associate the `.scala.html` files to the `play.api.templates.HtmlFormat` format:

```scala
templatesTypes += ("html" -> "play.api.templates.HtmlFormat")
```

Note that the right side of the arrow contains the fully qualified name of a value of type `play.templates.Format[_]`.

## Tell Play how to make an HTTP result from a template result type

Play can write an HTTP response body for any value of type `A` for which it exists an implicit `play.api.http.Writeable[A]` value. So all you need is to define such a value for your template result type. For instance, here is how to define such a value for HTTP:

```scala
implicit def writableHttp(implicit codec: Codec): Writeable[Http] =
Writeable[Http](result => codec.encode(result.body), Some(ContentTypes.HTTP))
```

> **Note:** if your template result type extends `play.api.templates.BufferedContent` you only need to define an
> implicit `play.api.http.ContentTypeOf` value:
> ```scala
> implicit def contentTypeHttp(implicit codec: Codec): ContentTypeOf[Http] =
> ContentTypeOf[Http](Some(ContentTypes.HTTP))
> ```
> **Next:** [[HTTP form submission and validation | ScalaForms]]
Original file line number Diff line number Diff line change
Expand Up @@ -181,4 +181,4 @@ And on an extended template that not need an extra script, just like this :

}
```
> **Next:** [[HTTP form submission and validation | ScalaForms]]
> **Next:** [[Custom format | ScalaCustomTemplateFormat]]
1 change: 1 addition & 0 deletions documentation/manual/scalaGuide/main/templates/_Sidebar.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

- [[Scala templates syntax | ScalaTemplates]]
- [[Common use cases | ScalaTemplateUseCases]]
- [[Custom format | ScalaCustomTemplateFormat]]

### Main concepts

Expand Down
34 changes: 15 additions & 19 deletions documentation/manual/scalaGuide/main/ws/ScalaOpenID.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ Step 1 may be omitted if all your users are using the same OpenID provider (for

The OpenID API has two important functions:

* `OpenID.redirectURL` calculates the URL where you should redirect the user. It involves fetching the user's OpenID page, this is why it returns a `Promise[String]` rather than a `String`. If the OpenID is invalid, the returned `Promise` will be a `Thrown`.
* `OpenID.verifiedId` needs an implicit `Request`, and inspects it to establish the user information, including his verified OpenID. It will do a call to the OpenID server to check the authenticity of the information, this is why it returns a `Promise[UserInfo]` rather than just `UserInfo`. If the information is not correct or if the server check is false (for example if the redirect URL has been forged), the returned `Promise` will be a `Thrown`.
* `OpenID.redirectURL` calculates the URL where you should redirect the user. It involves fetching the user's OpenID page asynchronously, this is why it returns a `Future[String]`. If the OpenID is invalid, the returned `Future` will fail.
* `OpenID.verifiedId` needs an implicit `Request` and inspects it to establish the user information, including his verified OpenID. It will do a call to the OpenID server asynchronously to check the authenticity of the information, this is why a `Future[UserInfo]` is returned. If the information is not correct or if the server check is false (for example if the redirect URL has been forged), the returned `Future` will fail.

In any case, when the `Promise` you get is a `Thrown`, you should look at the `Throwable` and redirect back the user to the login page with relevant information.
If the `Future` fails, you can define a fallback, which redirects back the user to the login page or return a `BadRequest`.

Here is an example of usage (from a controller):

Expand All @@ -34,27 +34,23 @@ def loginPost = Action { implicit request =>
error => {
Logger.info("bad request " + error.toString)
BadRequest(error.toString)
},
{
case (openid) => AsyncResult(OpenID.redirectURL(openid, routes.Application.openIDCallback.absoluteURL())
.extend( _.value match {
case Redeemed(url) => Redirect(url)
case Thrown(t) => Redirect(routes.Application.login)
}))
}, {
case (openid) => AsyncResult {
val url = OpenID.redirectURL(openid,
routes.Application.openIDCallback.absoluteURL())
url.map(a => Redirect(a)).
fallbackTo(Future(Redirect(routes.Application.login)))
}
}
)
}

def openIDCallback = Action { implicit request =>
AsyncResult(
OpenID.verifiedId.extend( _.value match {
case Redeemed(info) => Ok(info.id + "\n" + info.attributes)
case Thrown(t) => {
// Here you should look at the error, and give feedback to the user
Redirect(routes.Application.login)
}
})
)
OpenID.verifiedId.map((info: UserInfo) =>
Ok(info.id + "\n" + info.attributes)).
fallbackTo(Future(Forbidden))
)
}
```

Expand All @@ -76,4 +72,4 @@ OpenID.redirectURL(

Attributes will then be available in the `UserInfo` provided by the OpenID server.

> **Next:** [[OAuth | ScalaOAuth]]
> **Next:** [[OAuth | ScalaOAuth]]
2 changes: 1 addition & 1 deletion framework/project/Build.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ object BuildSettings {
val buildScalaVersion = propOr("scala.version", "2.10.0")
// TODO - Try to compute this from SBT...
val buildScalaVersionForSbt = propOr("play.sbt.scala.version", "2.9.2")
val buildSbtVersion = propOr("play.sbt.version", "0.12.2")
val buildSbtVersion = propOr("play.sbt.version", "0.12.3")
val buildSbtMajorVersion = "0.12"
val buildSbtVersionBinaryCompatible = "0.12"

Expand Down
13 changes: 8 additions & 5 deletions framework/project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@ object Dependencies {
val specsBuild = "org.specs2" %% "specs2" % "1.14"
val scalaIoFileBuild = "com.github.scala-incubator.io" %% "scala-io-file" % "0.4.2"
val scalaIoFileSbt = "com.github.scala-incubator.io" %% "scala-io-file" % "0.4.1" exclude ("javax.transaction", "jta")
val guava = "com.google.guava" % "guava" % "13.0.1"


val jdbcDeps = Seq(
("com.jolbox" % "bonecp" % "0.7.1.RELEASE" notTransitive ())
.exclude("com.google.guava", "guava")
.exclude("org.slf4j", "slf4j-api"),
"com.jolbox" % "bonecp" % "0.7.1.RELEASE" exclude ("com.google.guava", "guava"),

// bonecp needs it, but due to guavas stupid version numbering of older versions ("r08"), we need to explicitly
// declare a dependency on the newer version so that ivy can know which one to include
guava,

"com.h2database" % "h2" % "1.3.168",

Expand Down Expand Up @@ -50,7 +53,7 @@ object Dependencies {
.exclude("com.google.guava", "guava")
.exclude("javassist", "javassist"),

"com.google.guava" % "guava" % "13.0.1",
guava,

"com.google.code.findbugs" % "jsr305" % "2.0.1",

Expand Down Expand Up @@ -168,7 +171,7 @@ object Dependencies {
specsBuild % "test")

val testDependencies = Seq(
"junit" % "junit-dep" % "4.10",
"junit" % "junit" % "4.11",
specsBuild,
"com.novocode" % "junit-interface" % "0.10-M2",
("com.google.guava" % "guava" % "10.0.1" notTransitive()),
Expand Down
7 changes: 4 additions & 3 deletions framework/project/Tasks.scala
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,8 @@ object Tasks {
(file("src/anorm/src/main/scala") ** "*.scala").get ++
(file("src/play-filters-helpers/src/main/scala") ** "*.scala").get ++
(file("src/play-jdbc/src/main/scala") ** "*.scala").get ++
(file("src/play/target/scala-" + sbv + "/src_managed/main/views/html/helper") ** "*.scala").get
(file("src/play/target/scala-" + sbv + "/src_managed/main/views/html/helper") ** "*.scala").get ++
(file("src/templates/src/main/scala") ** "*.scala").get
val options = Seq("-sourcepath", base.getAbsolutePath, "-doc-source-url", "https://github.com/playframework/Play20/tree/" + BuildSettings.buildVersion + "/framework€{FILE_PATH}.scala")
new Scaladoc(10, cs.scalac)("Play " + BuildSettings.buildVersion + " Scala API", sourceFiles, classpath.map(_.data) ++ allJars, file("../documentation/api/scala"), options, s.log)

Expand Down Expand Up @@ -192,9 +193,9 @@ object Tasks {

(sourceDirectory ** "*.scala.html").get.foreach {
template =>
val compile = compiler.getDeclaredMethod("compile", classOf[java.io.File], classOf[java.io.File], classOf[java.io.File], classOf[String], classOf[String], classOf[String])
val compile = compiler.getDeclaredMethod("compile", classOf[java.io.File], classOf[java.io.File], classOf[java.io.File], classOf[String], classOf[String])
try {
compile.invoke(null, template, sourceDirectory, generatedDir, "play.api.templates.Html", "play.api.templates.HtmlFormat", "import play.api.templates._\nimport play.api.templates.PlayMagic._")
compile.invoke(null, template, sourceDirectory, generatedDir, "play.api.templates.HtmlFormat", "import play.api.templates._\nimport play.api.templates.PlayMagic._")
} catch {
case e: java.lang.reflect.InvocationTargetException => {
streams.log.error("Compilation failed for %s".format(template))
Expand Down
2 changes: 1 addition & 1 deletion framework/project/build.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sbt.version=0.12.2
sbt.version=0.12.3
2 changes: 1 addition & 1 deletion framework/skeletons/java-skel/project/build.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sbt.version=0.12.2
sbt.version=0.12.3
2 changes: 1 addition & 1 deletion framework/skeletons/scala-skel/project/build.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sbt.version=0.12.2
sbt.version=0.12.3
6 changes: 5 additions & 1 deletion framework/src/console/src/main/scala/Console.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ object Console {
|| __/|_|\____|\__ (_)
||_| |__/
|
|""".stripMargin) + ("play! " + play.core.PlayVersion.current + " (using Java " + System.getProperty("java.version") + " and Scala " + play.core.PlayVersion.scalaVersion + "), http://www.playframework.com")
|""".stripMargin) +
("play! " + play.core.PlayVersion.current +
" built with Scala " + play.core.PlayVersion.scalaVersion +
" (running Java " + System.getProperty("java.version") + ")," +
" http://www.playframework.com")

// -- Commands

Expand Down
Loading

0 comments on commit d280228

Please sign in to comment.