Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
base fork: revbingo/play
...
head fork: revbingo/play
  • 18 commits
  • 22 files changed
  • 0 commit comments
  • 5 contributors
View
14 documentation/commands/cmd-dependencies.txt
@@ -12,12 +12,12 @@
~
~ Description:
~ ~~~~~~~~~~~~
-~ Compute resolve and retrieve project dependencies and install them either
-~ in lib/ directory for jar artifacts or in modules/ for Play modules artifacts.
+~ Compute resolve and retrieve project dependencies and install them either in
+~ lib/ directory for jar artifacts or in modules/ for Play modules artifacts.
~
-~ By default the command will not delete unrecognized artifacts. Use the --sync option
-~ to keep both lib/ and modules/ directory synchronized with the dependencies management
-~ system.
+~ By default the command will not delete unrecognized artifacts.
+~ Use the --sync option to keep both lib/ and modules/ directory synchronized
+~ with the dependencies management system.
~
~ Options:
~ ~~~~~~~~
@@ -35,6 +35,10 @@
~ Keep lib/ and modules/ directory synced.
~ Delete unknow dependencies.
~
+~ --forceCopy:
+~ Never create files pointing to the original folder, always copy the folder
+~ instead.
+~
~ --%fwk_id:
~ Use this ID to run the application (override the default framework ID)
~
View
12 documentation/commands/cmd-version.txt
@@ -0,0 +1,12 @@
+~ Name:
+~ ~~~~~
+~ version -- Print the framework version
+~
+~ Synopsis:
+~ ~~~~~~~~~
+~ play version
+~
+~ Description:
+~ ~~~~~~~~~~~~
+~ Prints the version of the Play framework currently being used.
+~
View
15 documentation/files/jquery.tools-1.2.5.toolbox.expose.min.js
@@ -0,0 +1,15 @@
+/*
+
+ jQuery Tools 1.2.5 / Expose - Dim the lights
+
+ NO COPYRIGHTS OR LICENSES. DO WHAT YOU LIKE.
+
+ http://flowplayer.org/tools/toolbox/expose.html
+
+ Since: Mar 2010
+ Date: Wed Sep 22 06:02:10 2010 +0000
+*/
+(function(b){function k(){if(b.browser.msie){var a=b(document).height(),d=b(window).height();return[window.innerWidth||document.documentElement.clientWidth||document.body.clientWidth,a-d<20?d:a]}return[b(document).width(),b(document).height()]}function h(a){if(a)return a.call(b.mask)}b.tools=b.tools||{version:"1.2.5"};var l;l=b.tools.expose={conf:{maskId:"exposeMask",loadSpeed:"slow",closeSpeed:"fast",closeOnClick:true,closeOnEsc:true,zIndex:9998,opacity:0.8,startOpacity:0,color:"#fff",onLoad:null,
+onClose:null}};var c,i,e,g,j;b.mask={load:function(a,d){if(e)return this;if(typeof a=="string")a={color:a};a=a||g;g=a=b.extend(b.extend({},l.conf),a);c=b("#"+a.maskId);if(!c.length){c=b("<div/>").attr("id",a.maskId);b("body").append(c)}var m=k();c.css({position:"absolute",top:0,left:0,width:m[0],height:m[1],display:"none",opacity:a.startOpacity,zIndex:a.zIndex});a.color&&c.css("backgroundColor",a.color);if(h(a.onBeforeLoad)===false)return this;a.closeOnEsc&&b(document).bind("keydown.mask",function(f){f.keyCode==
+27&&b.mask.close(f)});a.closeOnClick&&c.bind("click.mask",function(f){b.mask.close(f)});b(window).bind("resize.mask",function(){b.mask.fit()});if(d&&d.length){j=d.eq(0).css("zIndex");b.each(d,function(){var f=b(this);/relative|absolute|fixed/i.test(f.css("position"))||f.css("position","relative")});i=d.css({zIndex:Math.max(a.zIndex+1,j=="auto"?0:j)})}c.css({display:"block"}).fadeTo(a.loadSpeed,a.opacity,function(){b.mask.fit();h(a.onLoad);e="full"});e=true;return this},close:function(){if(e){if(h(g.onBeforeClose)===
+false)return this;c.fadeOut(g.closeSpeed,function(){h(g.onClose);i&&i.css({zIndex:j});e=false});b(document).unbind("keydown.mask");c.unbind("click.mask");b(window).unbind("resize.mask")}return this},fit:function(){if(e){var a=k();c.css({width:a[0],height:a[1]})}},getMask:function(){return c},isLoaded:function(a){return a?e=="full":e},getConf:function(){return g},getExposed:function(){return i}};b.fn.mask=function(a){b.mask.load(a);return this};b.fn.expose=function(a){b.mask.load(a,this);return this}})(jQuery);
View
37 documentation/manual/guide1.textile
@@ -196,15 +196,21 @@ bc. INFO ~ Connected to jdbc:h2:mem:play
h2. <a>Using a version control system to track changes</a>
-When you work on a project, it’s highly recommended to store your source code in a version control system (VCS). It allows you to revert to a previous version if a change breaks something, work with several people and give access to all the successive versions of the application. Of course you can use any VCS to store your project, but here we will use Bazaar as an example. "Bazaar":http://bazaar-vcs.org/ is a distributed source version control system.
+When you work on a project, it’s highly recommended to store your source code in a version control system (VCS). It allows you to revert to a previous version if a change breaks something, work with several people and give access to all the successive versions of the application.
-Installing Bazaar is beyond the scope of this tutorial but it is very easy on any system. Once you have a working installation of Bazaar, go to the blog directory and init the application versioning by typing:
+When storing a Play application in a VCS, it’s important to exclude the **tmp/**, **modules/**, **lib/**, **test-result/** and **logs/** directories.
-bc. $ bzr init
+h3. <a>Bazaar</a>
-When storing a Play application in a VCS, it’s important to exclude the **tmp/** and **logs/** directories.
+Here we will use Bazaar as an example. "Bazaar":http://bazaar-vcs.org/ is a distributed source version control system.
+
+Installing Bazaar is beyond the scope of this tutorial but it is very easy on any system. Once you have a working installation of Bazaar, go to the blog directory and init the application versioning by typing:
-bc. $ bzr ignore tmp
+bc. $ bzr init
+$ bzr ignore tmp
+$ bzr ignore modules
+$ bzr ignore lib
+$ bzr ignore test-result
$ bzr ignore logs
Now we can commit our first blog engine version:
@@ -212,6 +218,27 @@ Now we can commit our first blog engine version:
bc. $ bzr add
$ bzr commit -m "YABE inital version"
+h3. <a>Git</a>
+
+"Git":http://git-scm.com is another distributed version control system, see its documentation for more information.
+
+Create a git working repository at the application root directory:
+
+bc. $ git init
+
+Create a @.gitignore@ file containing the following content:
+
+bc. /tmp
+/modules
+/lib
+/test-result
+/logs
+
+Add the content of the application and commit it:
+
+bc. $ git add .
+$ git commit -m "YABE initial version"
+
Version 1 is committed and we now have a solid foundation for our project.
p(note). Go to the %(next)"A first iteration of the data model":guide2%.
View
12 documentation/manual/guide10.textile
@@ -4,7 +4,7 @@ We’ve now finished the blog engine we wanted to create in this tutorial. Howev
Of course we’ve already written unit tests in order to test all the yabe model layer functionality. And it’s great as it will ensure that the blog engine’s core functionality is well tested. But a web application is not only about the ‘model’ part. We need to ensure that the web interface works as expected. That means testing the yabe blog engine’s controller layer. But we even need to test the UI itself, as for example, our JavaScript code.
-h2. <a>Testing the controller part</a>
+h2. <a name="controller">Testing the controller part</a>
Play gives you a way to test directly the application’s controller part using JUnit. We call these tests **‘Functional tests’**. This is because we want to test the web application’s complete functionality.
@@ -53,7 +53,7 @@ Well, we could continue to test all the application functionalities this way, bu
These kinds of JUnit based **‘Functional tests’** are still useful, typically to test Web services returning non-HTML responses such as JSON or XML over HTTP.
-h2. <a>Writing Selenium tests</a>
+h2. <a name="selenium">Writing Selenium tests</a>
"Selenium":http://seleniumhq.org is a testing tool specifically for testing web applications. The cool things here is that Selenium allows to run the test suite directly in any existing browser. As it does not use any ‘browser simulator’, you can be sure that you’re testing what your users will use.
@@ -176,11 +176,15 @@ clickAndWait('css=input[type=submit]')
And now run the test again, it should work.
-h2. <a>Measuring code coverage</a>
+h2. <a name="cobertura">Measuring code coverage</a>
Of course we haven’t written all required test cases for the application. But it’s enough for this tutorial. Now in a real-world project, how can we know if we have written enough test cases? We need something called **‘code coverage’**.
-Play comes with a code coverage module based on the "Cobertura":http://cobertura.sourceforge.net/ tool. We need to enable this module only for test mode. So add this line to the **application.conf** file, and restart the application in test mode.
+The ""Cobertura module":http://www.playframework.org/modules/cobertura generates code coverage reports using the "Cobertura":http://cobertura.sourceforge.net/ tool. Install the module using the @install@ command:
+
+bc. play install cobertura-{version}
+
+We need to enable this module only for test mode. So add this line to the @application.conf@ file, and restart the application in test mode.
bc. # Import the cobertura module in test mode
%test.module.cobertura=${play.path}/modules/cobertura
View
54 documentation/manual/i18n.textile
@@ -68,7 +68,11 @@ bc. application.langs=fr,en
This property affects how the dates are rendered in the templates using the ${date.format()}.
It also set the default date format when binding a date parameter.
-h2. <a name="retrieve">Retrieve localized messages</a>
+
+h2(#retrieve). <a>Retrieve localized messages</a>
+
+
+h3(#argument). Message arguments
From the application code, you can retrieve messages defined in message files. From Java, use the **play.i18n.Messages** object.
@@ -76,24 +80,66 @@ bc. public static void hello() {
renderText(Messages.get("hello"));
}
-We support message formatting through the standard Java formatting syntax. You can also define dynamic content in your messages:
+We support message formatting through the standard @java.util.Formatter@ ‘Format string syntax. You can also define dynamic content in your messages:
bc. hello=Hello %s!
-and
+where @%s@ represents a message argument that will be output as a @String@. Message arguments are provided by additional (varargs) arguments to @Messages.get@:
bc. public static void hello(String user) {
renderText(Messages.get("hello", user));
}
+h3(#template). Template output
+
From a template you can use the special **&{…}** syntax to display localized messages:
bc. <h1>&{'hello'}</h1>
-or using dynamic content:
+or using dynamic content in message arguments:
bc. <h1>&{'hello', params.user}</h1>
+
+h3(#arguments). Multiple arguments
+
+You can define multiple message arguments, such as this message which refers to two ‘decimal integer’ arguments:
+
+bc. guess=Please pick a number between %d and %d
+
+which you display by specifying the message arguments in the right order:
+
+bc. <p>&{'guess', low, high}</p>
+
+
+h3(#indices). Argument indices
+
+You can also specify the message argument explicitly, to use a different order. For example, suppose a message in English has two parameters:
+
+bc. guess.characteristic=Guess %s’s %s.
+
+with message output like:
+
+bc. <p>&{'guess.characteristic', person.name, 'age'}</p>
+
+The French localisation has the two message in the opposite order, so in the French localisation we specify the argument indices:
+
+bc. guess.characteristic=Devinez %2$s de %1$s.
+
+where @%2$s@ outputs the **second** argument as a decimal integer.
+
+Finally, we want to localise the characteristic name ‘age’ as well, so we would change the output to use the message key @person.age@, and change the message definitions to:
+
+bc. guess.characteristic=Guess %s’s &{%s}.
+person.age = age
+
+and
+
+bc. guess.characteristic=Devinez &{%2$s} de %1$s.
+person.age = l’age
+
+where @&{%s}@ is itself a message look-up, with the argument value as the message key.
+
p(note). **Continuing the discussion**
Next: %(next)"Cache":cache%.
View
46 documentation/manual/ide.textile
@@ -5,7 +5,9 @@ Working with Play is easy. You don’t even need a sophisticated IDE as Play com
However, using a modern Java IDE provides cool productivity features like auto-completion, on-the-fly compilation, assisted refactoring and debugging. Play supports the "NetBeans":http://www.netbeans.org, "IntelliJ IDEA":http://www.jetbrains.com/idea/index.html and "Eclipse":http://www.eclipse.org platforms.
-h2(#eclipse). <a>Generate configuration files for Eclipse</a>
+h2(#eclipse). <a>Eclipse</a>
+
+h3. Generate configuration
Play provides a command to simplify Eclipse configuration. To transform a Play application into a working Eclipse project, use the **eclipsify** command:
@@ -19,13 +21,18 @@ The **eclipsify** command generates several launchers for the application. The m
If you make any important changes to your application, such as changing the classpath, use **eclipsify** again to regenerate the configuration files.
-Additionally, an Eclipse plugin is available in your Play distribution, in the **support/eclipse/** directory. To install it, simply copy the JAR file you will find to your Eclipse installation’s **dropins** folder.
-
p(note). **Do not commit Eclipse configuration files when you work in a team!**
The generated configuration files contain absolute references to your framework installation. These are specific to your own installation. When you work in a team, each developer must keep his Eclipse configuration files private.
-h2(#netbeans). <a>Generate configuration files for NetBeans</a>
+h3. Play Eclipse plug-in
+
+Additionally, Play comes with an Eclipse plug-in that provides editors for HTML view templates, @application.conf@ and the @routes@ file.
+
+To install, copy the JAR file from @$PLAY_HOME/support/eclipse@ to @$ECLIPSE_HOME/dropins@.
+
+
+h2(#netbeans). <a>NetBeans</a>
Play provides a command to simplify NetBeans configuration. To transform an existing application to a valid NetBeans project, use the **netbeansify** command:
@@ -39,17 +46,29 @@ Use the standard *Run* button to start the application. When the application is
If you make any important change to your application such as changing the classpath, use **netbeansify** again to regenerate the configuration files.
-p(note). **Do not commit the nbproject/ directory when you work in a team!**
+p(note). **Do not commit the @nbproject/@ directory when you work in a team!**
The generated configuration files contains absolute references to your framework installation. These are specific to your own installation. When you work in a team on the same application, each developer must keep his NetBeans configuration files private.
-h2(#intellij). <a>Generate configuration files for IntelliJ IDEA</a>
+h2(#intellij). <a>IntelliJ IDEA</a>
-Play provides a command to simplify IntelliJ IDEA configuration. To transform an existing application to a valid IntelliJ IDEA module/project, use the **idealize** command:
+Play provides a command to simplify IntelliJ IDEA configuration. To transform an existing application to a valid IntelliJ IDEA module, use the **idealize** command:
bc. # play idealize myApp
-Then you can just import the application in IntelliJ using the import module facility.
+To create a single module project, do the following in IntelliJ IDEA.
+
+# On the *File* menu, select *New Project…*
+# In the *New Project* wizard, select *Create project from scratch*.
+# Click the *Next* button.
+# Change the *Project files location* to the Play application directory.
+# Set the *Project file format* to your preferred format.
+# Uncheck *Create module* (since the module already exists).
+# Click the *Finish* button.
+# On the *File* menu, select *New Module…*
+# On the *Add Module* wizard, select *Import existing module*.
+# Under *Select IDEA module file*, select the @.iml@ file in the application directory.
+# Click the *Finish* button.
!images/intellij!
@@ -62,14 +81,19 @@ The generated configuration files contains absolute references to your framework
h2(#textmate). <a>Textmate</a>
-Download and install the bundle provided for "Textmate":http://macromates.com/ to enable syntax coloring and auto-completion. The bundle also eases navigation between controllers and views.
+Install the "Textmate":http://macromates.com/ bundle provided at @$PLAY_HOME/support/textmate.zip@ to enable syntax coloring and auto-completion. The bundle also eases navigation between controllers and views.
!images/editor!
-h2(#custom). <a>Manually configure your preferred editor</a>
+h2(#vim). <a>Vim</a>
+
+Inspired by Textmate, the "snipMate":http://www.vim.org/scripts/script.php?script_id=2540 plug-in provides keyword auto-completion in "Vim":http://www.vim.org/. Play provides snippets files for HTML and Java: to use them, install snipMate and copy @$PLAY_HOME/support/vim/*.snippets@ to @~/.vim/snippets/@.
+
+
+h2(#custom). <a>Custom configuration</a>
-As Play applications are standard Java applications, you don’t need a specific plug-in to work with your preferred editor. This, however, requires a little bit of knowledge of how Play works.
+As Play applications are standard Java applications, you don’t need a specific plug-in to work with your preferred editor. This, however, requires a little bit of knowledge of how Play works.
h3. Classpath settings
View
2  documentation/manual/jobs.textile
@@ -100,7 +100,7 @@ public class Bootstrap extends Job {
p(note). **Tip**
-We use the CRON expression parser from the "Quartz library":http://www.opensymphony.com/quartz/wikidocs/CronTriggers%20Tutorial.html.
+We use the CRON expression parser from the "Quartz library":http://www.quartz-scheduler.org/docs/tutorials/crontrigger.html.
You don’t need to return a result. Even if you do it, the result will be lost.
View
4 documentation/manual/jpa.textile
@@ -142,8 +142,8 @@ or even a part of:
bc. Post.find("title", "My first post").fetch();
Post.find("title like ?", "%hello%").fetch();
Post.find("author is null").fetch();
-Post.find("title like % and author is null", "%hello%").fetch();
-Post.find("title like % and author is null order by postDate", "%hello%").fetch();
+Post.find("title like ? and author is null", "%hello%").fetch();
+Post.find("title like ? and author is null order by postDate", "%hello%").fetch();
You can even specify only the **order by** statement:
View
30 documentation/manual/lambdaj.textile
@@ -1,18 +1,18 @@
h1. Some functional programming techniques
-The 1.1 release will allow "support of the Scala programming language":scala. One great thing (but not the only one) about Scala is that it is a mixed imperative -- functional language that allow to solve a lot of problem in a functional way.
+Play supports the Scala programming language. One great thing (but not the only one) about Scala is that it is a mixed imperative/functional language that makes it possible to solve a lot of problems in a functional way.
-But now, what if you prefer keep using Java with play ? Could we bring some of the nice features of Scala to Java as well ? So let's me introduce the new "LambdaJ":http://code.google.com/p/lambdaj/ support in play.
+But now, what if you prefer keep using Java with Play? Could we bring some of the nice features of Scala to Java as well ? This section describes Play’s "lambdaj":http://code.google.com/p/lambdaj/ support.
-The main purpose of lambdaj is to partially eliminate the burden to write (often nested and poorly readable) loops while iterating over collections. From the LambdaJ website:
+The main purpose of lambdaj is to partially eliminate the burden to write (often nested and poorly readable) loops while iterating over collections. From the lambdaj website:
bq. How many times have you read or written the same two or three lines of code that frequently seem to go together, and even though they operate on different objects, feel like the same thing? And how often these repetitions involve some sort of collections iteration or more generically manipulation? These repetitions in the code is something that developers eventually learn to filter out and ignore when reading code, once they figure out where the interesting parts are placed. But even if the developers get used to it, it slows them down. Code like that is clearly written for computers to execute, not for developers to read.
lambdaj is a library that makes easier to address this issue by allowing to manipulate collections in a pseudo-functional and statically typed way. In our experience to iterate over collection, especially in nested loops, is often error prone and makes the code less readable. The purpose of this library is to alleviate these problems employing some functional programming techniques but without losing the static typing of java. We impose this last constraint to make refactoring easier and safer and allow the compiler to do its job.
-h2. <a>Let's use lambdaj</a>
+h2. <a>Lets use lambdaj</a>
-We will start with a fresh application that allow to display a **Car** catalog. The **Car** model class will just be defined as:
+We will start with a fresh application that displays a Car catalogue. The @Car@ model class is:
bc. package models;
@@ -41,18 +41,18 @@ public class Car extends Model {
}
-And let's write a simple action that retrieve all these cars:
+And lets write a simple action that retrieve all these cars:
bc. public static index() {
List<Car> cars = Car.find().fetch();
render(cars);
}
-Now in the page it would be great to be able to order all these cars by brand. So we need to extract all the brand from the car list. Let's do it the lambdaj way:
+Now in the page it would be great to be able to order all these cars by brand, so we need to extract the brands from the car list. Lets do it the lambdaj way:
bc. List<String> brands = collect(cars, on(Car.class).brand);
-This line will iterate over all the retrieved cars, collect all the brands and feed them to the returned list. The very cool things is that we are able to express that stuff in a pure statically typed way.
+This line will iterate over all the retrieved cars, collect all the brands and feed them to the returned list. The cool thing is that we are able to express that stuff in a pure statically-typed way.
Now we need to filter this list to remove brand duplication:
@@ -64,11 +64,11 @@ Very easy.
h2. <a>Batching method calls</a>
-We want to cound each time a Car has been viewed, and remember the last viewed time. As we already have the **viewed()** method that update the **Car** objects we just need to call this method on each retrieved car. Again, let's do it the lambdaj way:
+We want to cound each time a car has been viewed, and remember the last viewed time. As we already have the @viewed()@ method that updates the @Car@ objects we just need to call this method on each retrieved car. Again, lets do it the lambdaj way:
bc. forEach(cars).viewed();
-This line will iterate over each car object of the cars list and call the **viewed()** method on each.
+This line will iterate over each car object of the cars list and call the @viewed()@ method on each.
And because we modified the state of the persistent objects, we need to save them. So rewrite it as:
@@ -76,9 +76,9 @@ bc. forEach(cars).viewed().save();
h2. <a>Using closures</a>
-Hugh? But Java doesn't have closure! Wait, lambdaj partially fill this lack by a feature that allow to define, in its traditional DSL style, first-class functions with free variables.
+Huh? But Java doesnt have closures! Wait, lambdaj partially fills this omission with a feature that allows you to define, in its traditional DSL style, first-class functions with free variables.
-Let's say we have a **PriceWatcher** utility able to fetch in real time the price of each car.
+Lets say we have a @PriceWatcher@ utility able to fetch in real time the price of each car.
bc. package models;
@@ -91,18 +91,18 @@ public class PriceWatcher {
}
-Because this data need to be **in real time** we don't want store it in the database. Before displaying the car list we need to create a PriceWatcher object and ask it to resolve the current price of each car. Again, let's do it the lambdaj way:
+Because this data need to be **in real time** we dont want store it in the database. Before displaying the car list we need to create a PriceWatcher object and ask it to resolve the current price of each car. Again, lets do it the lambdaj way:
bc. PriceWatcher priceWatcher = new PriceWatcher();
Car.forEach(cars); {
of(priceWatcher).setPrice(var(Car.class));
}
-We define a function with a free variable, and then ask to the Car class to call them for each element of the cars list.
+We define a function with a free variable, and then ask to the @Car@ class to call them for each element of the @cars@ list.
h2. <a>The final action code</a>
-Using all these good lambdaj stuff, we can finally write the **index** action in a very expressive fashion:
+Using all these good lambdaj stuff, we can finally write the @index@ action in a very expressive fashion:
bc. public static void index() {
List<Car> cars = Car.find().fetch();
View
2  documentation/manual/overview.textile
@@ -88,7 +88,7 @@ The Java Persistence API (JPA) is the cleanest object-relational mapping ORM) AP
Moreover if you use the provided **play.db.jpa.Model** superclass it will help make your code prettier. Have a look:
-bc. public void messages(int page) {
+bc. public static void messages(int page) {
User connectedUser = User.find("byEmail", connected()).first();
List<Message> messages = Message.find(
"user = ? and read = false order by date desc",
View
10 documentation/manual/security.textile
@@ -80,12 +80,18 @@ bc. public static destroyMyAccount() {
Will only work when called from a form including a proper authenticity token:
-bc. #{form @ destroyMyAccount()}
+bc. <form method="post" action="/account/destroy">
#{authenticityToken /}
<input type="submit" value="destroy my account">
#{/form}
-You can of course add this as a before filter if you want to protect all actions of a hierarchy of controllers.
+For POST requests, Play’s "form tag":tags#form automatically generates the authenticity token:
+
+bc. #{form @destroyMyAccount()}
+ <input type="submit" value="destroy my account">
+#{/form}
+
+You can of course add the **checkAuthenticity()** method call as a "before filter":controllers#before if you want to protect all actions of a hierarchy of controllers.
"More on cross-site request forgery":http://en.wikipedia.org/wiki/Cross-site_request_forgery
View
35 documentation/manual/tags.textile
@@ -20,13 +20,14 @@ If the action you try to call does not have any route able to invoke it using a
h2. <a name="authenticityToken">authenticityToken</a>
-Renders a hidden input field containing a generated token that you can use in any form. See the "Cross-Site Request Forgery":security#csrf section.
+Renders a hidden input field containing a generated token that you can use in any form, to prevent "Cross-Site Request Forgery":security#csrf.
bc. #{authenticityToken /}
Rendered as:
-bc. <input type="hidden" name="authenticityToken" value="1c6d92fed96200347f06b7c5e1a3a28fa258ef7c">
+bc. <input type="hidden" name="authenticityToken"
+ value="1c6d92fed96200347f06b7c5e1a3a28fa258ef7c">
h2. <a name="cache">cache</a>
@@ -195,41 +196,55 @@ Inserts a **form** tag. Play will guess the HTTP method from the route, with POS
Charset encoding is always **utf-8**.
-bc. #{form @Client.create(), method:'POST', id:'creationForm',
- enctype:'multipart/form-data' }
+bc. #{form @Client.details(), method:'GET', id:'detailsForm'}
...
#{/form}
Rendered as:
-bc. <form action="/client/create" id="creationForm" method="POST"
- accept-charset="utf-8" enctype="multipart/form-data">
+bc. <form action="/client/details" id="detailsForm" method="GET"
+ accept-charset="utf-8">
...
</form>
You can also specify a target entity as part of the action method:
-bc. #{form @Client.update(client.id)}
+bc. #{form @Client.details(client.id)}
...
#{/form}
The HTTP parameter name name is detected from what you declared in your action method.
-bc. public static void update(String clientId){
+bc. public static void details(String clientId){
// ...
}
Play will create an action URL with clientId:
-bc. <form action="/client/update?clientId=3442" method="POST"
+bc. <form action="/client/details?clientId=3442" method="GET"
+ accept-charset="utf-8">
+ ...
+</form>
+
+The **form** tag also automatically includes an "authenticity token":#authenticityToken, for methods other than GET.
+
+bc. #{form @Client.create(), method:'POST', id:'creationForm',
+ enctype:'multipart/form-data' }
+ ...
+#{/form}
+
+Rendered as:
+
+bc. <form action="/client/create" id="creationForm" method="POST"
accept-charset="utf-8" enctype="multipart/form-data">
+<input type="hidden" name="authenticityToken"
+ value="1c6d92fed96200347f06b7c5e1a3a28fa258ef7c">
...
</form>
If your form updates a resource on the server-side, you _should_ use the **POST** method. If your form is used to filter data and does not update your domain, you can use a GET. Please read about "idempotence":http://en.wikipedia.org/wiki/Idempotence. POST is not idempotent, whereas GET, PUT and DELETE are.
-
h2. <a name="get">get</a>
Retrieves a value defined with a **set** tag. You may use the get/set mechanism to exchange values between templates, layouts and sub-templates.
View
101 documentation/manual/validation.textile
@@ -333,7 +333,7 @@ h2. <a name="builtin">Built-in validations</a>
The **play.data.validation** package contains several "built-in validations":validation-builtin that you can use on the **Validation** object or with annotations.
-h2. <a name="custom">Custom validation</a>
+h2. <a name="custom">Custom validation using @CheckWith</a>
Can’t find the validator you need in the **play.data.validation** package? Write your own. You can use the generic **@CheckWith** annotation to bind your own **Check** implementation.
@@ -350,10 +350,107 @@ bc. public class User {
public boolean isSatisfied(Object user, Object password) {
return notMatchPreviousPasswords(password);
}
-
}
}
+The default validation error message key is @validation.invalid@. To use a different key, call @Check.setMessage@ with a message key and message parameters.
+
+bc. static class MyPasswordCheck extends Check {
+
+ public boolean isSatisfied(Object user, Object password) {
+ final Date lastUsed = dateLastUsed(password);
+ setMessage("validation.used", JavaExtensions.format(lastUsed));
+ return lastUsed == null;
+ }
+}
+
+The message look up always has the field name as the first parameter, and your message parameters as subsequent parameters. So, for the example above, you could define the message like:
+
+bc. validation.used = &{%1$s} already used on date %2$s
+user.password = Password
+
+where @&{%1$s}@ uses the message argument with index 1 (the field name) as the message key for another message look-up, and @%2$s@ is the second message argument (the formatted date).
+
+p(note). The message syntax - @%s@, @%s2$s@ and @&{…}@ - is explained in the section on how to "Retrieve localized messages":i18n#retrieve.
+
+
+h2. <a name="customannotations">Custom annotations</a>
+
+You can also write your own annotation validations, which is more complex but makes your model code cleaner and allows you to introduce validator parameters.
+
+For example, suppose we want a less restrictive version of the "@URL":validation-builtin#url validation, so we can allow URLs with any scheme such as a @file://@ URL, and with a parameter that lets us specify exactly which schemes are allowed.
+
+First, we write a custom validation annotation, with a parameter for overriding the default message:
+
+bc. import net.sf.oval.configuration.annotation.Constraint;
+import java.lang.annotation.*;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.PARAMETER})
+@Constraint(checkWith = URICheck.class)
+public @interface URI {
+ String message() default URICheck.message;
+}
+
+This annotation refers to an implementation of @net.sf.oval.configuration.annotation.AbstractAnnotationCheck@.
+
+bc. public class URICheck extends AbstractAnnotationCheck<URI> {
+
+ /** Error message key. */
+ public final static String message = "validation.uri";
+
+ /** URI schemes allowed by validation. */
+ private List<String> schemes;
+
+ @Override
+ public void configure(URI uri) {
+ setMessage(uri.message());
+ this.schemes = Arrays.asList(uri.schemes());
+ }
+
+ /**
+ * Add the URI schemes to the message variables so they can be included
+ * in the error message.
+ */
+ @Override
+ public Map<String, String> createMessageVariables() {
+ final Map<String, String> variables = new TreeMap<String, String>();
+ variables.put("2", JavaExtensions.join(schemes, ", "));
+ return variables;
+ }
+
+ @Override
+ public boolean isSatisfied(Object validatedObject, Object value,
+ OValContext context, Validator validator) throws OValException {
+
+ requireMessageVariablesRecreation();
+ try {
+ final java.net.URI uri = new java.net.URI(value.toString());
+ final boolean schemeValid = schemes.contains(uri.getScheme());
+ return schemes.size() == 0 || schemeValid;
+ } catch (URISyntaxException e) {
+ return false;
+ }
+ }
+}
+
+The @isSatisfied@ method calls @requireMessageVariablesRecreation()@ to instruct OVal to call @createMessageVariables()@ before rendering the message. This returns an ordered map of variables that are passed to the message formatter. The map keys are not used; the @"2"@ in this example indicates the message parameter index. As before, the first parameter is the field name.
+
+To use this use the annotation on a model property.
+
+bc. public class User {
+
+ @URI(message = "validation.uri.schemes", schemes = {"http", "https"})
+ public String profile;
+}
+
+We can define the messages like this:
+
+bc. validation.uri = Not a valid URI
+validation.uri.schemes = &{%1$s} is not a valid URI - allowed schemes are %2$s
+
+
+
p(note). **Continuing the discussion**
The last layer of a Play application: %(next)"Domain object model":model%.
View
1  framework/dependencies.yml
@@ -50,6 +50,7 @@ require: &allDependencies
- org.hibernate.javax.persistence -> hibernate-jpa-2.0-api 1.0.0.Final
- org.javassist -> javassist 3.9.0.GA
- org.jboss.netty -> netty 3.2.4.Final
+ - org.postgresql -> postgresql 9.0
- org.slf4j -> slf4j-api 1.6.1
- org.slf4j -> slf4j-log4j12 1.6.1
- org.yaml -> snakeyaml 1.7
View
BIN  framework/lib/postgresql-9.0.jar
Binary file not shown
View
7 framework/pym/play/commands/deps.py
@@ -21,9 +21,14 @@ def execute(**kargs):
args = kargs.get("args")
play_env = kargs.get("env")
+ force = "false"
+ if args.count('--forceCopy') == 1:
+ args.remove('--forceCopy')
+ force = "true"
+
classpath = app.getClasspath()
- add_options = ['-Dapplication.path=%s' % (app.path), '-Dframework.path=%s' % (play_env['basedir']), '-Dplay.id=%s' % play_env['id'], '-Dplay.version=%s' % play_env['version']]
+ add_options = ['-Dapplication.path=%s' % (app.path), '-Dframework.path=%s' % (play_env['basedir']), '-Dplay.id=%s' % play_env['id'], '-Dplay.version=%s' % play_env['version'], '-Dplay.forcedeps=%s' % (force)]
if args.count('--verbose'):
add_options.append('-Dverbose')
if args.count('--sync'):
View
14 framework/pym/play/commands/version.py
@@ -0,0 +1,14 @@
+
+COMMANDS = ['version']
+
+HELP = {
+ 'version': 'Print the framework version'
+}
+
+def execute(**kargs):
+ env = kargs.get("env")
+ showLogo = kargs.get("showLogo")
+
+ # If we've shown the logo, then the version has already been printed
+ if not showLogo:
+ print env["version"]
View
7 framework/src/play/deps/DependenciesManager.java
@@ -193,6 +193,7 @@ public boolean problems() {
}
public File install(ArtifactDownloadReport artifact) throws Exception {
+ Boolean force = System.getProperty("play.forcedeps").equals("true");
try {
File from = artifact.getLocalFile();
if (!isPlayModule(artifact)) {
@@ -210,7 +211,11 @@ public File install(ArtifactDownloadReport artifact) throws Exception {
new File(application, "modules").mkdir();
Files.delete(to);
if (from.isDirectory()) {
- IO.writeContent(from.getAbsolutePath(), to);
+ if (force) {
+ IO.copyDirectory(from, to);
+ } else {
+ IO.writeContent(from.getAbsolutePath(), to);
+ }
System.out.println("~ \tmodules/" + to.getName() + " -> " + from.getAbsolutePath());
} else {
Files.unzip(from, to);
View
19 framework/src/play/libs/IO.java
@@ -346,4 +346,23 @@ public static void write(InputStream is, File f) {
}
}
}
+
+ // If targetLocation does not exist, it will be created.
+ public static void copyDirectory(File source, File target) {
+ if (source.isDirectory()) {
+ if (!target.exists()) {
+ target.mkdir();
+ }
+ for (String child: source.list()) {
+ copyDirectory(new File(source, child), new File(target, child));
+ }
+ } else {
+ try {
+ write(new FileInputStream(source), new FileOutputStream(target));
+ } catch (IOException e) {
+ throw new UnexpectedException(e);
+ }
+ }
+ }
+
}
View
4 modules/docviewer/public/playmanual/wiki.css
@@ -16,8 +16,8 @@
.wikistyle table td:first-child{padding-left:0!important;}
.wikistyle table td:last-child{padding-right:0!important;}
.wikistyle pre{margin:1em 0 2em 0!important;font-size:90%!important;background-color:#f8f8ff!important;border-left:4px solid #e0e0e0!important;padding:.5em!important;line-height:1.5em!important;color:#444!important;overflow:auto!important;}
-.wikistyle pre code{font-weight: normal; color: #444; display:block;padding:0!important;padding-left:10px!important;font-size:100%!important;background-color:#f8f8ff!important;border:none!important;}
-.wikistyle code{font-weight: bold; font-size:90%!important;color:#444!important;padding:0 .2em!important;}
+.wikistyle code{font-family:Consolas,"Andale Mono",monospace;font-size:90%;color:#444;background-color:#f8f8ff;border:1px solid #e0e0e0;padding:0 0.2em!important;}
+.wikistyle pre code{display:block;padding:0!important;padding-left:10px!important;font-size:100%!important;border:none!important;}
.wikistyle pre.console{margin:1em 0!important;font-size:90%!important;background-color:black!important;padding:.5em!important;line-height:1.5em!important;color:white!important;}
.wikistyle pre.console code{padding:0!important;font-size:100%!important;background-color:black!important;border:none!important;color:white!important;}
.wikistyle pre.console span{color:#888!important;}
View
2  play
@@ -53,6 +53,8 @@ try:
print r"~ | __/|_|\____|\__ (_)"
print r"~ |_| |__/ "
print r"~"
+ else:
+ sys.argv.remove("--silent")
play_version_file = os.path.join(play_env["basedir"], 'framework', 'src', 'play', 'version')
if not os.path.exists(play_version_file):

No commit comments for this range

Something went wrong with that request. Please try again.